Imported Upstream version 0.7.0 upstream/0.7.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 14 Jan 2022 04:50:17 +0000 (13:50 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 14 Jan 2022 04:50:17 +0000 (13:50 +0900)
104 files changed:
.gitignore
Makefile
Makefile.inc
getuid/usb_id [deleted file]
kpartx/Makefile
kpartx/dasd.c
kpartx/devmapper.c
kpartx/devmapper.h
kpartx/dos.c
kpartx/kpartx.c
kpartx/kpartx_id [changed mode: 0644->0755]
kpartx/lopart.c
libdmmp/DEV_NOTES [new file with mode: 0644]
libdmmp/Makefile [new file with mode: 0644]
libdmmp/docs/doc-preclean.pl [new file with mode: 0644]
libdmmp/docs/kernel-doc [new file with mode: 0644]
libdmmp/docs/libdmmp.h.3 [new file with mode: 0644]
libdmmp/docs/split-man.pl [new file with mode: 0644]
libdmmp/libdmmp.c [new file with mode: 0644]
libdmmp/libdmmp.pc.in [new file with mode: 0644]
libdmmp/libdmmp/libdmmp.h [new file with mode: 0644]
libdmmp/libdmmp_misc.c [new file with mode: 0644]
libdmmp/libdmmp_mp.c [new file with mode: 0644]
libdmmp/libdmmp_path.c [new file with mode: 0644]
libdmmp/libdmmp_pg.c [new file with mode: 0644]
libdmmp/libdmmp_private.h [new file with mode: 0644]
libdmmp/test/Makefile [new file with mode: 0644]
libdmmp/test/libdmmp_speed_test.c [new file with mode: 0644]
libdmmp/test/libdmmp_test.c [new file with mode: 0644]
libmpathcmd/Makefile
libmpathcmd/mpath_cmd.c
libmpathcmd/mpath_cmd.h
libmpathpersist/Makefile
libmpathpersist/mpath_persist.c
libmpathpersist/mpath_persist.h
libmpathpersist/mpath_pr_ioctl.c
libmpathpersist/mpathpr.h
libmultipath/Makefile
libmultipath/alias.c
libmultipath/blacklist.c
libmultipath/callout.c
libmultipath/checkers.c
libmultipath/checkers.h
libmultipath/checkers/Makefile
libmultipath/checkers/cciss_tur.c
libmultipath/checkers/directio.c
libmultipath/checkers/emc_clariion.c
libmultipath/checkers/hp_sw.c
libmultipath/checkers/rbd.c
libmultipath/checkers/rdac.c
libmultipath/checkers/tur.c
libmultipath/config.c
libmultipath/config.h
libmultipath/configure.c
libmultipath/configure.h
libmultipath/defaults.h
libmultipath/devmapper.c
libmultipath/devmapper.h
libmultipath/dict.c
libmultipath/dict.h
libmultipath/discovery.c
libmultipath/discovery.h
libmultipath/dmparser.c
libmultipath/hwtable.c
libmultipath/list.h
libmultipath/memory.c
libmultipath/pgpolicies.c
libmultipath/print.c
libmultipath/print.h
libmultipath/prioritizers/alua_spc3.h
libmultipath/prioritizers/hds.c
libmultipath/prioritizers/ontap.c
libmultipath/propsel.c
libmultipath/propsel.h
libmultipath/structs.c
libmultipath/structs.h
libmultipath/structs_vec.c
libmultipath/switchgroup.c
libmultipath/sysfs.c
libmultipath/sysfs.h
libmultipath/uevent.c
libmultipath/uevent.h
libmultipath/util.c
libmultipath/util.h
libmultipath/uxsock.c
libmultipath/uxsock.h
libmultipath/version.h
multipath/01_udev [deleted file]
multipath/02_multipath [deleted file]
multipath/11-dm-mpath.rules
multipath/main.c
multipath/multipath.8
multipath/multipath.conf.5
multipath/multipath.rules
multipathd/Makefile
multipathd/cli.c
multipathd/cli_handlers.c
multipathd/cli_handlers.h
multipathd/main.c
multipathd/main.h
multipathd/multipathd.service
multipathd/uxlsnr.c
multipathd/uxlsnr.h
third-party/valgrind/valgrind.h

index aee4ece..57cf7e6 100644 (file)
@@ -12,3 +12,9 @@ mpathpersist/mpathpersist
 .nfs*
 *.swp
 *.patch
+*.rej
+*.orig
+libdmmp/docs/man/*.3.gz
+libdmmp/*.so.*
+libdmmp/test/libdmmp_test
+libdmmp/test/libdmmp_speed_test
index 228d9ac..cfee0d0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,11 @@ BUILDDIRS = \
        mpathpersist \
        kpartx
 
+ifneq ($(ENABLE_LIBDMMP),0)
+BUILDDIRS += \
+       libdmmp
+endif
+
 all: recurse
 
 recurse:
index 1cc8f44..8361e6c 100644 (file)
@@ -8,6 +8,12 @@
 #
 # WITH_LOCAL_LIBDM     = 1
 # WITH_LOCAL_LIBSYSFS  = 1
+#
+# Uncomment to disable RADOS support (e.g. if rados headers are missing).
+# ENABLE_RADOS = 0
+#
+# Uncomment to disable libdmmp support
+# ENABLE_LIBDMMP = 0
 
 ifeq ($(TOPDIR),)
        TOPDIR  = ..
@@ -49,25 +55,42 @@ man8dir             = $(prefix)/usr/share/man/man8
 man5dir                = $(prefix)/usr/share/man/man5
 man3dir                = $(prefix)/usr/share/man/man3
 syslibdir      = $(prefix)/$(LIB)
-incdir         = $(prefix)/usr/include
 libdir         = $(prefix)/$(LIB)/multipath
 unitdir                = $(prefix)/$(SYSTEMDPATH)/systemd/system
 mpathpersistdir        = $(TOPDIR)/libmpathpersist
 mpathcmddir    = $(TOPDIR)/libmpathcmd
 thirdpartydir  = $(TOPDIR)/third-party
+libdmmpdir     = $(TOPDIR)/libdmmp
+includedir     = $(prefix)/usr/include
+pkgconfdir     = $(prefix)/$(LIB)/pkgconfig
 
 GZIP           = gzip -9 -c
 RM             = rm -f
 LN             = ln -sf
 INSTALL_PROGRAM        = install
 
-OPTFLAGS       = -O2 -g -pipe -Wall -Wextra -Wformat=2 \
+OPTFLAGS       = -O2 -g -pipe -Wall -Wextra -Wformat=2 -Werror=implicit-int \
+                 -Werror=implicit-function-declaration -Werror=format-security \
                  -Wno-sign-compare -Wno-unused-parameter -Wno-clobbered \
-                 -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector \
+                 -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector-strong \
                  --param=ssp-buffer-size=4
 
 CFLAGS         = $(OPTFLAGS) -fPIC -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\"
 SHARED_FLAGS   = -shared
 
+# Check whether a function with name $1 has been declared in header file $2.
+check_func =                                                                  \
+    $(shell                                                                   \
+       if grep -Eq "^[^[:blank:]]+[[:blank:]]+$1[[:blank:]]*(.*)*" "$2"; then \
+          found=1;                                                            \
+          status="yes";                                                       \
+       else                                                                   \
+          found=0;                                                            \
+          status="no";                                                        \
+       fi;                                                                    \
+       echo 1>&2 "Checking for $1 in $2 ... $$status";                        \
+       echo "$$found"                                                         \
+    )
+
 %.o:   %.c
        $(CC) $(CFLAGS) -c -o $@ $<
diff --git a/getuid/usb_id b/getuid/usb_id
deleted file mode 100755 (executable)
index 800e471..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-#
-# copy in /bin and add this line to a device block in multipath.conf :
-# getuid_callout          "/bin/usb_id %n"
-#
-cd /sys/block/$1 && cd $(ls -l device|cut -d">" -f2) && cd ../../../.. && cat serial
index e8a59f2..9441a2b 100644 (file)
@@ -7,9 +7,7 @@ CFLAGS += -I. -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
 
 LIBDEPS += -ldevmapper
 
-LIBDM_API_COOKIE = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_set_cookie' /usr/include/libdevmapper.h)
-
-ifneq ($(strip $(LIBDM_API_COOKIE)),0)
+ifneq ($(call check_func,dm_task_set_cookie,/usr/include/libdevmapper.h),0)
        CFLAGS += -DLIBDM_API_COOKIE
 endif
 
index faf5e2e..f50c1bd 100644 (file)
@@ -28,6 +28,7 @@
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/ioctl.h>
 #include <linux/hdreg.h>
 #include <errno.h>
@@ -261,7 +262,7 @@ read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns)
                                fmt_size = sectors512(vlabel.formatted_blocks,
                                                      blocksize);
                        } else if (!strcmp(info.type, "ECKD")) {
-                               /* formated w/o large volume support */
+                               /* formatted w/o large volume support */
                                fmt_size = geo.cylinders * geo.heads
                                        * geo.sectors * (blocksize >> 9);
                        } else {
index 474d879..cf6650c 100644 (file)
@@ -14,8 +14,7 @@
 #define MAX_PREFIX_LEN 8
 #define PARAMS_SIZE 1024
 
-extern int
-dm_prereq (char * str, int x, int y, int z)
+int dm_prereq(char * str, int x, int y, int z)
 {
        int r = 1;
        struct dm_task *dmt;
@@ -52,8 +51,8 @@ out:
        return r;
 }
 
-extern int
-dm_simplecmd (int task, const char *name, int no_flush, uint16_t udev_flags) {
+int dm_simplecmd(int task, const char *name, int no_flush, uint16_t udev_flags)
+{
        int r = 0;
        int udev_wait_flag = (task == DM_DEVICE_RESUME ||
                              task == DM_DEVICE_REMOVE);
@@ -90,10 +89,10 @@ out:
        return r;
 }
 
-extern int
-dm_addmap (int task, const char *name, const char *target,
-          const char *params, uint64_t size, int ro, const char *uuid, int part,
-          mode_t mode, uid_t uid, gid_t gid) {
+int dm_addmap(int task, const char *name, const char *target,
+             const char *params, uint64_t size, int ro, const char *uuid,
+             int part, mode_t mode, uid_t uid, gid_t gid)
+{
        int r = 0;
        struct dm_task *dmt;
        char *prefixed_uuid = NULL;
@@ -154,8 +153,7 @@ addout:
        return r;
 }
 
-extern int
-dm_map_present (char * str, char **uuid)
+int dm_map_present(char * str, char **uuid)
 {
        int r = 0;
        struct dm_task *dmt;
@@ -254,7 +252,7 @@ out:
 }
 
 char *
-dm_mapuuid(int major, int minor)
+dm_mapuuid(const char *mapname)
 {
        struct dm_task *dmt;
        const char *tmp;
@@ -263,9 +261,9 @@ dm_mapuuid(int major, int minor)
        if (!(dmt = dm_task_create(DM_DEVICE_INFO)))
                return NULL;
 
+       if (!dm_task_set_name(dmt, mapname))
+               goto out;
        dm_task_no_open_count(dmt);
-       dm_task_set_major(dmt, major);
-       dm_task_set_minor(dmt, minor);
 
        if (!dm_task_run(dmt))
                goto out;
@@ -279,7 +277,7 @@ out:
 }
 
 int
-dm_devn (char * mapname, int *major, int *minor)
+dm_devn (const char * mapname, int *major, int *minor)
 {
        int r = 1;
        struct dm_task *dmt;
@@ -306,8 +304,8 @@ out:
        return r;
 }
 
-int
-dm_get_map(int major, int minor, char * outparams)
+static int
+dm_get_map(char *mapname, char * outparams)
 {
        int r = 1;
        struct dm_task *dmt;
@@ -318,8 +316,8 @@ dm_get_map(int major, int minor, char * outparams)
        if (!(dmt = dm_task_create(DM_DEVICE_TABLE)))
                return 1;
 
-       dm_task_set_major(dmt, major);
-       dm_task_set_minor(dmt, minor);
+       if (!dm_task_set_name(dmt, mapname))
+               goto out;
        dm_task_no_open_count(dmt);
 
        if (!dm_task_run(dmt))
@@ -336,15 +334,224 @@ out:
        return r;
 }
 
+static int
+dm_get_opencount (const char * mapname)
+{
+       int r = -1;
+       struct dm_task *dmt;
+       struct dm_info info;
+
+       if (!(dmt = dm_task_create(DM_DEVICE_INFO)))
+               return 0;
+
+       if (!dm_task_set_name(dmt, mapname))
+               goto out;
+
+       if (!dm_task_run(dmt))
+               goto out;
+
+       if (!dm_task_get_info(dmt, &info))
+               goto out;
+
+       if (!info.exists)
+               goto out;
+
+       r = info.open_count;
+out:
+       dm_task_destroy(dmt);
+       return r;
+}
+
+/*
+ * returns:
+ *    1 : match
+ *    0 : no match
+ *   -1 : empty map
+ */
+static int
+dm_type(const char * name, char * type)
+{
+       int r = 0;
+       struct dm_task *dmt;
+       uint64_t start, length;
+       char *target_type = NULL;
+       char *params;
+
+       if (!(dmt = dm_task_create(DM_DEVICE_TABLE)))
+               return 0;
+
+       if (!dm_task_set_name(dmt, name))
+               goto out;
+
+       dm_task_no_open_count(dmt);
+
+       if (!dm_task_run(dmt))
+               goto out;
+
+       /* Fetch 1st target */
+       dm_get_next_target(dmt, NULL, &start, &length,
+                          &target_type, &params);
+
+       if (!target_type)
+               r = -1;
+       else if (!strcmp(target_type, type))
+               r = 1;
+
+out:
+       dm_task_destroy(dmt);
+       return r;
+}
+
+/*
+ * returns:
+ *    0 : if both uuids end with same suffix which starts with UUID_PREFIX
+ *    1 : otherwise
+ */
+int
+dm_compare_uuid(const char *mapuuid, const char *partname)
+{
+       char *partuuid;
+       int r = 1;
+
+       partuuid = dm_mapuuid(partname);
+       if (!partuuid)
+               return 1;
+
+       if (!strncmp(partuuid, "part", 4)) {
+               char *p = strstr(partuuid, "mpath-");
+               if (p && !strcmp(mapuuid, p))
+                       r = 0;
+       }
+       free(partuuid);
+       return r;
+}
+
+struct remove_data {
+       int verbose;
+};
+
+static int
+do_foreach_partmaps (const char * mapname, const char *uuid,
+                    int (*partmap_func)(const char *, void *),
+                    void *data)
+{
+       struct dm_task *dmt;
+       struct dm_names *names;
+       struct remove_data *rd = data;
+       unsigned next = 0;
+       char params[PARAMS_SIZE];
+       int major, minor;
+       char dev_t[32];
+       int r = 1;
+
+       if (!(dmt = dm_task_create(DM_DEVICE_LIST)))
+               return 1;
+
+       dm_task_no_open_count(dmt);
+
+       if (!dm_task_run(dmt))
+               goto out;
+
+       if (!(names = dm_task_get_names(dmt)))
+               goto out;
+
+       if (!names->dev) {
+               r = 0; /* this is perfectly valid */
+               goto out;
+       }
+
+       if (dm_devn(mapname, &major, &minor))
+               goto out;
+
+       sprintf(dev_t, "%d:%d", major, minor);
+       do {
+               /*
+                * skip our devmap
+                */
+               if (!strcmp(names->name, mapname))
+                       goto next;
+
+               /*
+                * skip if we cannot fetch the map table from the kernel
+                */
+               if (dm_get_map(names->name, &params[0]))
+                       goto next;
+
+               /*
+                * skip if the table does not map over the multipath map
+                */
+               if (!strstr(params, dev_t))
+                       goto next;
+
+               /*
+                * skip if devmap target is not "linear"
+                */
+               if (!dm_type(names->name, "linear")) {
+                       if (rd->verbose)
+                               printf("%s: is not a linear target. Not removing\n",
+                                      names->name);
+                       goto next;
+               }
+
+               /*
+                * skip if uuids don't match
+                */
+               if (dm_compare_uuid(uuid, names->name)) {
+                       if (rd->verbose)
+                               printf("%s: is not a kpartx partition. Not removing\n",
+                                      names->name);
+                       goto next;
+               }
+
+               if (partmap_func(names->name, data) != 0)
+                       goto out;
+       next:
+               next = names->next;
+               names = (void *) names + next;
+       } while (next);
+
+       r = 0;
+out:
+       dm_task_destroy (dmt);
+       return r;
+}
+
+static int
+remove_partmap(const char *name, void *data)
+{
+       struct remove_data *rd = (struct remove_data *)data;
+       int r = 0;
+
+       if (dm_get_opencount(name)) {
+               if (rd->verbose)
+                       printf("%s is in use. Not removing", name);
+               return 1;
+       }
+       if (!dm_simplecmd(DM_DEVICE_REMOVE, name, 0, 0)) {
+               if (rd->verbose)
+                       printf("%s: failed to remove\n", name);
+               r = 1;
+       } else if (rd->verbose)
+               printf("del devmap : %s\n", name);
+       return r;
+}
+
+int
+dm_remove_partmaps (char * mapname, char *uuid, int verbose)
+{
+       struct remove_data rd = { verbose };
+       return do_foreach_partmaps(mapname, uuid, remove_partmap, &rd);
+}
+
 #define FEATURE_NO_PART "no_partitions"
 
 int
-dm_no_partitions(int major, int minor)
+dm_no_partitions(char *mapname)
 {
        char params[PARAMS_SIZE], *ptr;
        int i, num_features;
 
-       if (dm_get_map(major, minor, params))
+       if (dm_get_map(mapname, params))
                return 0;
 
        ptr = params;
index 436efe1..9988ec0 100644 (file)
@@ -16,8 +16,9 @@ int dm_addmap (int, const char *, const char *, const char *, uint64_t,
 int dm_map_present (char *, char **);
 char * dm_mapname(int major, int minor);
 dev_t dm_get_first_dep(char *devname);
-char * dm_mapuuid(int major, int minor);
-int dm_devn (char * mapname, int *major, int *minor);
-int dm_no_partitions(int major, int minor);
+char * dm_mapuuid(const char *mapname);
+int dm_devn (const char * mapname, int *major, int *minor);
+int dm_remove_partmaps (char * mapname, char *uuid, int verbose);
+int dm_no_partitions(char * mapname);
 
 #endif /* _KPARTX_DEVMAPPER_H */
index 64b27b6..4985152 100644 (file)
@@ -46,7 +46,7 @@ read_extended_partition(int fd, struct partition *ep, int en,
                for (i=0; i<2; i++) {
                        memcpy(&p, bp + 0x1be + i * sizeof (p), sizeof (p));
                        if (is_extended(p.sys_type)) {
-                               if (p.nr_sects && !moretodo) {
+                               if (p.start_sect && p.nr_sects && !moretodo) {
                                        next = start + sector_size_mul * le32_to_cpu(p.start_sect);
                                        moretodo = 1;
                                }
index d31fea8..58e60ff 100644 (file)
@@ -28,6 +28,7 @@
 #include <stdint.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <ctype.h>
 #include <libdevmapper.h>
@@ -354,17 +355,15 @@ main(int argc, char **argv){
        off = find_devname_offset(device);
 
        if (!loopdev) {
-               uuid = dm_mapuuid(major(buf.st_rdev), minor(buf.st_rdev));
                mapname = dm_mapname(major(buf.st_rdev), minor(buf.st_rdev));
+               if (mapname)
+                       uuid = dm_mapuuid(mapname);
        }
 
-       if (!uuid)
-               uuid = device + off;
-
        if (!mapname)
                mapname = device + off;
-       else if (!force_devmap &&
-                dm_no_partitions(major(buf.st_rdev), minor(buf.st_rdev))) {
+       if (!force_devmap &&
+                dm_no_partitions(mapname)) {
                /* Feature 'no_partitions' is set, return */
                return 0;
        }
@@ -452,42 +451,12 @@ main(int argc, char **argv){
                        break;
 
                case DELETE:
-                       for (j = MAXSLICES-1; j >= 0; j--) {
-                               char *part_uuid, *reason;
-
-                               if (safe_sprintf(partname, "%s%s%d",
-                                            mapname, delim, j+1)) {
-                                       fprintf(stderr, "partname too small\n");
-                                       exit(1);
-                               }
-                               strip_slash(partname);
-
-                               if (!dm_map_present(partname, &part_uuid))
-                                       continue;
-
-                               if (part_uuid && uuid) {
-                                       if (check_uuid(uuid, part_uuid, &reason) != 0) {
-                                               fprintf(stderr, "%s is %s. Not removing\n", partname, reason);
-                                               free(part_uuid);
-                                               continue;
-                                       }
-                                       free(part_uuid);
-                               }
-
-                               if (!dm_simplecmd(DM_DEVICE_REMOVE, partname,
-                                                 0, 0)) {
-                                       r++;
-                                       continue;
-                               }
-                               if (verbose)
-                                       printf("del devmap : %s\n", partname);
-                       }
-
+                       r = dm_remove_partmaps(mapname, uuid, verbose);
                        if (loopdev) {
                                if (del_loop(loopdev)) {
                                        if (verbose)
                                                printf("can't del loop : %s\n",
-                                                       loopdev);
+                                                      loopdev);
                                        exit(1);
                                }
                                printf("loop deleted : %s\n", loopdev);
old mode 100644 (file)
new mode 100755 (executable)
index df0e6b9..b7f802d
@@ -74,16 +74,16 @@ fi
 if [ -n "$dmdeps" ] ; then
     case "$dmdeps" in
        *\(94,*)
-            echo "DM_TYPE=ccw"
+           echo "DM_TYPE=ccw"
            ;;
        *\(104,* | *\(105,* | *\(106,* | *\(107,* | *\(108,* | *\(109,* | *\(110,* | *\(112,*)
            echo "DM_TYPE=cciss"
            ;;
        *\(9*)
-            echo "DM_TYPE=raid"
+           echo "DM_TYPE=raid"
            ;;
        *)
-            echo "DM_TYPE=scsi"
+           echo "DM_TYPE=scsi"
            echo "DM_WWN=0x${dmname#?}"
            ;;
     esac
index 14af34f..2eb3f63 100644 (file)
@@ -62,8 +62,7 @@ xstrdup (const char *s)
        return t;
 }
 
-extern int
-is_loop_device (const char *device)
+int is_loop_device(const char *device)
 {
        struct stat statbuf;
        int loopmajor;
@@ -96,8 +95,7 @@ is_loop_device (const char *device)
 
 #define SIZE(a) (sizeof(a)/sizeof(a[0]))
 
-extern char *
-find_loop_by_file (const char * filename)
+char *find_loop_by_file(const char *filename)
 {
        DIR *dir;
        struct dirent *dent;
@@ -144,8 +142,7 @@ find_loop_by_file (const char * filename)
        return found;
 }
 
-extern char *
-find_unused_loop_device (void)
+char *find_unused_loop_device(void)
 {
        char dev[20], *next_loop_dev = NULL;
        int fd, next_loop = 0, somedev = 0, someloop = 0, loop_known = 0;
@@ -231,8 +228,7 @@ find_unused_loop_device (void)
        return NULL;
 }
 
-extern int
-set_loop (const char *device, const char *file, int offset, int *loopro)
+int set_loop(const char *device, const char *file, int offset, int *loopro)
 {
        struct loop_info loopinfo;
        int fd, ffd, mode;
@@ -284,8 +280,7 @@ set_loop (const char *device, const char *file, int offset, int *loopro)
        return 0;
 }
 
-extern int
-del_loop (const char *device)
+int del_loop(const char *device)
 {
        int retries = 5;
        int fd;
diff --git a/libdmmp/DEV_NOTES b/libdmmp/DEV_NOTES
new file mode 100644 (file)
index 0000000..220a9f4
--- /dev/null
@@ -0,0 +1,41 @@
+== Planed features ==
+ * Expose all properties used by /usr/bin/multipath
+
+== Code style ==
+ * Keep things as simple as possible.
+ * Linux Kernel code style.
+ * Don't use typedef.
+ * Don't use enum.
+ * We are not smarter than API user, so don't create wrapping function like:
+
+    ```
+    dmmp_mpath_search_by_id(struct dmmp_context *ctx,
+                            struct dmmp_mpath **dmmp_mp,
+                            uint32_t dmmp_mp_count, const char *id)
+
+    dmmp_path_group_id_search(struct dmmp_mpath *dmmp_mp,
+                              const char *blk_name)
+    ```
+ * The performance is the same for query single mpath and query all mpaths,
+   so no `dmmp_mpath_of_wwid(struct dmmp_context *ctx, const char *wwid)` yet.
+
+== Naming scheme ==
+ * Public constants should be named as `DMMP_XXX_YYY`.
+ * Public functions should be named as `dmmp_<noun>_<verb>`.
+ * Private constants should be named as `_DMMP_XXX_YYY`.
+ * Private functions should be named as `_dmmp_<noun>_<verb>`.
+
+== Code Layout ==
+ * libdmmp_private.h
+    Internal functions or macros.
+ * libdmmp.c
+    Handling multipathd IPC and generate dmmp_context and
+    dmmp_mpath_array_get().
+ * libdmmp_mp.c
+    For `struct dmmp_mpath`
+ * libdmmp_pg.c
+    For `struct dmmp_path_group`
+ * libdmmp_path.c
+    For `struct dmmp_path`
+ * libdmmp_misc.c
+    Misc functions.
diff --git a/libdmmp/Makefile b/libdmmp/Makefile
new file mode 100644 (file)
index 0000000..1c5329a
--- /dev/null
@@ -0,0 +1,84 @@
+# Makefile
+#
+# Copyright (C) 2015 - 2016 Red Hat, Inc.
+# Gris Ge <fge@redhat.com>
+#
+include ../Makefile.inc
+
+LIBDMMP_VERSION=0.1.0
+SONAME=$(LIBDMMP_VERSION)
+DEVLIB = libdmmp.so
+LIBS = $(DEVLIB).$(SONAME)
+PKGFILE = libdmmp.pc
+EXTRA_MAN_FILES = libdmmp.h.3
+HEADERS = libdmmp/libdmmp.h
+
+OBJS = libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o
+
+CFLAGS += -fvisibility=hidden -I$(libdmmpdir) -I$(mpathcmddir) \
+         $(shell pkg-config --cflags json-c)
+
+LIBDEPS += $(shell pkg-config --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread
+
+all: $(LIBS) doc
+
+$(LIBS): $(OBJS)
+       $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
+       $(LN) $@ $(DEVLIB)
+
+install:
+       $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
+       $(INSTALL_PROGRAM) -m 644 -D \
+               $(HEADERS) $(DESTDIR)$(includedir)/$(HEADERS)
+       $(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB)
+       $(INSTALL_PROGRAM) -m 644 -D \
+               $(PKGFILE).in $(DESTDIR)$(pkgconfdir)/$(PKGFILE)
+       perl -i -pe 's|__VERSION__|$(LIBDMMP_VERSION)|g' \
+               $(DESTDIR)$(pkgconfdir)/$(PKGFILE)
+       perl -i -pe 's|__LIBDIR__|$(syslibdir)|g' \
+               $(DESTDIR)$(pkgconfdir)/$(PKGFILE)
+       perl -i -pe 's|__INCLUDEDIR__|$(includedir)|g' \
+               $(DESTDIR)$(pkgconfdir)/$(PKGFILE)
+       @for file in docs/man/*.3.gz; do \
+               $(INSTALL_PROGRAM) -m 644 -D \
+                       $$file \
+                       $(DESTDIR)$(man3dir)/ || exit $?; \
+       done
+
+uninstall:
+       $(RM) $(DESTDIR)$(syslibdir)/$(LIBS)
+       $(RM) $(DESTDIR)$(includedir)/$(HEADERS)
+       $(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
+       @for file in $(DESTDIR)$(man3dir)/dmmp_*; do \
+               $(RM) $$file; \
+       done
+       $(RM) $(DESTDIR)$(man3dir)/libdmmp.h*
+
+clean:
+       $(RM) core *.a *.o *.gz *.so *.so.*
+       $(RM) docs/man/*.3.gz
+       $(MAKE) -C test clean
+
+check: all
+       $(MAKE) -C test check
+
+speed_test: all
+       $(MAKE) -C test speed_test
+
+doc: docs/man/$(EXTRA_MAN_FILES).gz
+
+TEMPFILE := $(shell mktemp)
+
+docs/man/$(EXTRA_MAN_FILES).gz: $(HEADERS)
+       @for file in $(EXTRA_MAN_FILES); do \
+               $(INSTALL_PROGRAM) -v -m 644 -D docs/$$file docs/man/$$file; \
+       done
+       cat $(HEADERS) | \
+           perl docs/doc-preclean.pl > $(TEMPFILE)
+       perl docs/kernel-doc -man $(TEMPFILE) | \
+           perl docs/split-man.pl docs/man
+       -rm -f $(TEMPFILE)
+       @for file in docs/man/*.3; do \
+               gzip -f $$file; \
+       done
+       find docs/man -type f -name \*[0-9].gz
diff --git a/libdmmp/docs/doc-preclean.pl b/libdmmp/docs/doc-preclean.pl
new file mode 100644 (file)
index 0000000..9a9a4ce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+# Copyright (C) 2016 Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Gris Ge <fge@redhat.com>
+
+use strict;
+
+my @REMOVE_KEY_LIST=("DMMP_DLL_EXPORT");
+
+while (<>) {
+    for my $key (@REMOVE_KEY_LIST) {
+        (s/$key//g);
+    }
+    print;
+}
diff --git a/libdmmp/docs/kernel-doc b/libdmmp/docs/kernel-doc
new file mode 100644 (file)
index 0000000..8f5b546
--- /dev/null
@@ -0,0 +1,3156 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+## Copyright (c) 1998 Michael Zucchi, All Rights Reserved        ##
+## Copyright (C) 2000, 1  Tim Waugh <twaugh@redhat.com>          ##
+## Copyright (C) 2001  Simon Huggins                             ##
+## Copyright (C) 2005-2012  Randy Dunlap                         ##
+## Copyright (C) 2012  Dan Luedtke                               ##
+##                                                              ##
+## #define enhancements by Armin Kuster <akuster@mvista.com>    ##
+## Copyright (c) 2000 MontaVista Software, Inc.                         ##
+##                                                              ##
+## This software falls under the GNU General Public License.     ##
+## Please read the COPYING file for more information             ##
+
+# 18/01/2001 -         Cleanups
+#              Functions prototyped as foo(void) same as foo()
+#              Stop eval'ing where we don't need to.
+# -- huggie@earth.li
+
+# 27/06/2001 -  Allowed whitespace after initial "/**" and
+#               allowed comments before function declarations.
+# -- Christian Kreibich <ck@whoop.org>
+
+# Still to do:
+#      - add perldoc documentation
+#      - Look more closely at some of the scarier bits :)
+
+# 26/05/2001 -         Support for separate source and object trees.
+#              Return error code.
+#              Keith Owens <kaos@ocs.com.au>
+
+# 23/09/2001 - Added support for typedefs, structs, enums and unions
+#              Support for Context section; can be terminated using empty line
+#              Small fixes (like spaces vs. \s in regex)
+# -- Tim Jansen <tim@tjansen.de>
+
+# 25/07/2012 - Added support for HTML5
+# -- Dan Luedtke <mail@danrl.de>
+
+sub usage {
+    my $message = <<"EOF";
+Usage: $0 [OPTION ...] FILE ...
+
+Read C language source or header FILEs, extract embedded documentation comments,
+and print formatted documentation to standard output.
+
+The documentation comments are identified by "/**" opening comment mark. See
+Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax.
+
+Output format selection (mutually exclusive):
+  -docbook             Output DocBook format.
+  -html                        Output HTML format.
+  -html5               Output HTML5 format.
+  -list                        Output symbol list format. This is for use by docproc.
+  -man                 Output troff manual page format. This is the default.
+  -rst                 Output reStructuredText format.
+  -text                        Output plain text format.
+
+Output selection (mutually exclusive):
+  -export              Only output documentation for symbols that have been
+                       exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL()
+                        in any input FILE or -export-file FILE.
+  -internal            Only output documentation for symbols that have NOT been
+                       exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL()
+                        in any input FILE or -export-file FILE.
+  -function NAME       Only output documentation for the given function(s)
+                       or DOC: section title(s). All other functions and DOC:
+                       sections are ignored. May be specified multiple times.
+  -nofunction NAME     Do NOT output documentation for the given function(s);
+                       only output documentation for the other functions and
+                       DOC: sections. May be specified multiple times.
+
+Output selection modifiers:
+  -no-doc-sections     Do not output DOC: sections.
+  -enable-lineno        Enable output of #define LINENO lines. Only works with
+                        reStructuredText format.
+  -export-file FILE     Specify an additional FILE in which to look for
+                        EXPORT_SYMBOL() and EXPORT_SYMBOL_GPL(). To be used with
+                        -export or -internal. May be specified multiple times.
+
+Other parameters:
+  -v                   Verbose output, more warnings and other information.
+  -h                   Print this help.
+
+EOF
+    print $message;
+    exit 1;
+}
+
+#
+# format of comments.
+# In the following table, (...)? signifies optional structure.
+#                         (...)* signifies 0 or more structure elements
+# /**
+#  * function_name(:)? (- short description)?
+# (* @parameterx: (description of parameter x)?)*
+# (* a blank line)?
+#  * (Description:)? (Description of function)?
+#  * (section header: (section description)? )*
+#  (*)?*/
+#
+# So .. the trivial example would be:
+#
+# /**
+#  * my_function
+#  */
+#
+# If the Description: header tag is omitted, then there must be a blank line
+# after the last parameter specification.
+# e.g.
+# /**
+#  * my_function - does my stuff
+#  * @my_arg: its mine damnit
+#  *
+#  * Does my stuff explained.
+#  */
+#
+#  or, could also use:
+# /**
+#  * my_function - does my stuff
+#  * @my_arg: its mine damnit
+#  * Description: Does my stuff explained.
+#  */
+# etc.
+#
+# Besides functions you can also write documentation for structs, unions,
+# enums and typedefs. Instead of the function name you must write the name
+# of the declaration;  the struct/union/enum/typedef must always precede
+# the name. Nesting of declarations is not supported.
+# Use the argument mechanism to document members or constants.
+# e.g.
+# /**
+#  * struct my_struct - short description
+#  * @a: first member
+#  * @b: second member
+#  *
+#  * Longer description
+#  */
+# struct my_struct {
+#     int a;
+#     int b;
+# /* private: */
+#     int c;
+# };
+#
+# All descriptions can be multiline, except the short function description.
+#
+# For really longs structs, you can also describe arguments inside the
+# body of the struct.
+# eg.
+# /**
+#  * struct my_struct - short description
+#  * @a: first member
+#  * @b: second member
+#  *
+#  * Longer description
+#  */
+# struct my_struct {
+#     int a;
+#     int b;
+#     /**
+#      * @c: This is longer description of C
+#      *
+#      * You can use paragraphs to describe arguments
+#      * using this method.
+#      */
+#     int c;
+# };
+#
+# This should be use only for struct/enum members.
+#
+# You can also add additional sections. When documenting kernel functions you
+# should document the "Context:" of the function, e.g. whether the functions
+# can be called form interrupts. Unlike other sections you can end it with an
+# empty line.
+# A non-void function should have a "Return:" section describing the return
+# value(s).
+# Example-sections should contain the string EXAMPLE so that they are marked
+# appropriately in DocBook.
+#
+# Example:
+# /**
+#  * user_function - function that can only be called in user context
+#  * @a: some argument
+#  * Context: !in_interrupt()
+#  *
+#  * Some description
+#  * Example:
+#  *    user_function(22);
+#  */
+# ...
+#
+#
+# All descriptive text is further processed, scanning for the following special
+# patterns, which are highlighted appropriately.
+#
+# 'funcname()' - function
+# '$ENVVAR' - environmental variable
+# '&struct_name' - name of a structure (up to two words including 'struct')
+# '@parameter' - name of a parameter
+# '%CONST' - name of a constant.
+
+## init lots of data
+
+
+my $errors = 0;
+my $warnings = 0;
+my $anon_struct_union = 0;
+
+# match expressions used to find embedded type information
+my $type_constant = '\%([-_\w]+)';
+my $type_func = '(\w+)\(\)';
+my $type_param = '\@(\w+(\.\.\.)?)';
+my $type_fp_param = '\@(\w+)\(\)';  # Special RST handling for func ptr params
+my $type_struct = '\&((struct\s*)*[_\w]+)';
+my $type_struct_xml = '\\&amp;((struct\s*)*[_\w]+)';
+my $type_env = '(\$\w+)';
+my $type_enum_full = '\&(enum)\s*([_\w]+)';
+my $type_struct_full = '\&(struct)\s*([_\w]+)';
+my $type_typedef_full = '\&(typedef)\s*([_\w]+)';
+my $type_union_full = '\&(union)\s*([_\w]+)';
+my $type_member = '\&([_\w]+)((\.|->)[_\w]+)';
+my $type_member_func = $type_member . '\(\)';
+
+# Output conversion substitutions.
+#  One for each output format
+
+# these work fairly well
+my @highlights_html = (
+                       [$type_constant, "<i>\$1</i>"],
+                       [$type_func, "<b>\$1</b>"],
+                       [$type_struct_xml, "<i>\$1</i>"],
+                       [$type_env, "<b><i>\$1</i></b>"],
+                       [$type_param, "<tt><b>\$1</b></tt>"]
+                      );
+my $local_lt = "\\\\\\\\lt:";
+my $local_gt = "\\\\\\\\gt:";
+my $blankline_html = $local_lt . "p" . $local_gt;      # was "<p>"
+
+# html version 5
+my @highlights_html5 = (
+                        [$type_constant, "<span class=\"const\">\$1</span>"],
+                        [$type_func, "<span class=\"func\">\$1</span>"],
+                        [$type_struct_xml, "<span class=\"struct\">\$1</span>"],
+                        [$type_env, "<span class=\"env\">\$1</span>"],
+                        [$type_param, "<span class=\"param\">\$1</span>]"]
+                      );
+my $blankline_html5 = $local_lt . "br /" . $local_gt;
+
+# XML, docbook format
+my @highlights_xml = (
+                      ["([^=])\\\"([^\\\"<]+)\\\"", "\$1<quote>\$2</quote>"],
+                      [$type_constant, "<constant>\$1</constant>"],
+                      [$type_struct_xml, "<structname>\$1</structname>"],
+                      [$type_param, "<parameter>\$1</parameter>"],
+                      [$type_func, "<function>\$1</function>"],
+                      [$type_env, "<envar>\$1</envar>"]
+                    );
+my $blankline_xml = $local_lt . "/para" . $local_gt . $local_lt . "para" . $local_gt . "\n";
+
+# gnome, docbook format
+my @highlights_gnome = (
+                        [$type_constant, "<replaceable class=\"option\">\$1</replaceable>"],
+                        [$type_func, "<function>\$1</function>"],
+                        [$type_struct, "<structname>\$1</structname>"],
+                        [$type_env, "<envar>\$1</envar>"],
+                        [$type_param, "<parameter>\$1</parameter>" ]
+                      );
+my $blankline_gnome = "</para><para>\n";
+
+# these are pretty rough
+my @highlights_man = (
+                      [$type_constant, "\$1"],
+                      [$type_func, "\\\\fB\$1\\\\fP"],
+                      [$type_struct, "\\\\fI\$1\\\\fP"],
+                      [$type_param, "\\\\fI\$1\\\\fP"]
+                    );
+my $blankline_man = "";
+
+# text-mode
+my @highlights_text = (
+                       [$type_constant, "\$1"],
+                       [$type_func, "\$1"],
+                       [$type_struct, "\$1"],
+                       [$type_param, "\$1"]
+                     );
+my $blankline_text = "";
+
+# rst-mode
+my @highlights_rst = (
+                       [$type_constant, "``\$1``"],
+                       # Note: need to escape () to avoid func matching later
+                       [$type_member_func, "\\:c\\:type\\:`\$1\$2\\\\(\\\\) <\$1>`"],
+                       [$type_member, "\\:c\\:type\\:`\$1\$2 <\$1>`"],
+                      [$type_fp_param, "**\$1\\\\(\\\\)**"],
+                       [$type_func, "\\:c\\:func\\:`\$1()`"],
+                       [$type_struct_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_enum_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_typedef_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_union_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       # in rst this can refer to any type
+                       [$type_struct, "\\:c\\:type\\:`\$1`"],
+                       [$type_param, "**\$1**"]
+                     );
+my $blankline_rst = "\n";
+
+# list mode
+my @highlights_list = (
+                       [$type_constant, "\$1"],
+                       [$type_func, "\$1"],
+                       [$type_struct, "\$1"],
+                       [$type_param, "\$1"]
+                     );
+my $blankline_list = "";
+
+# read arguments
+if ($#ARGV == -1) {
+    usage();
+}
+
+my $kernelversion;
+my $dohighlight = "";
+
+my $verbose = 0;
+my $output_mode = "man";
+my $output_preformatted = 0;
+my $no_doc_sections = 0;
+my $enable_lineno = 0;
+my @highlights = @highlights_man;
+my $blankline = $blankline_man;
+my $modulename = "Kernel API";
+
+use constant {
+    OUTPUT_ALL          => 0, # output all symbols and doc sections
+    OUTPUT_INCLUDE      => 1, # output only specified symbols
+    OUTPUT_EXCLUDE      => 2, # output everything except specified symbols
+    OUTPUT_EXPORTED     => 3, # output exported symbols
+    OUTPUT_INTERNAL     => 4, # output non-exported symbols
+};
+my $output_selection = OUTPUT_ALL;
+my $show_not_found = 0;
+
+my @export_file_list;
+
+my @build_time;
+if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) &&
+    (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') {
+    @build_time = gmtime($seconds);
+} else {
+    @build_time = localtime;
+}
+
+my $man_date = ('January', 'February', 'March', 'April', 'May', 'June',
+               'July', 'August', 'September', 'October',
+               'November', 'December')[$build_time[4]] .
+  " " . ($build_time[5]+1900);
+
+# Essentially these are globals.
+# They probably want to be tidied up, made more localised or something.
+# CAVEAT EMPTOR!  Some of the others I localised may not want to be, which
+# could cause "use of undefined value" or other bugs.
+my ($function, %function_table, %parametertypes, $declaration_purpose);
+my $declaration_start_line;
+my ($type, $declaration_name, $return_type);
+my ($newsection, $newcontents, $prototype, $brcount, %source_map);
+
+if (defined($ENV{'KBUILD_VERBOSE'})) {
+       $verbose = "$ENV{'KBUILD_VERBOSE'}";
+}
+
+# Generated docbook code is inserted in a template at a point where
+# docbook v3.1 requires a non-zero sequence of RefEntry's; see:
+# http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html
+# We keep track of number of generated entries and generate a dummy
+# if needs be to ensure the expanded template can be postprocessed
+# into html.
+my $section_counter = 0;
+
+my $lineprefix="";
+
+# Parser states
+use constant {
+    STATE_NORMAL        => 0, # normal code
+    STATE_NAME          => 1, # looking for function name
+    STATE_FIELD         => 2, # scanning field start
+    STATE_PROTO         => 3, # scanning prototype
+    STATE_DOCBLOCK      => 4, # documentation block
+    STATE_INLINE        => 5, # gathering documentation outside main block
+};
+my $state;
+my $in_doc_sect;
+
+# Inline documentation state
+use constant {
+    STATE_INLINE_NA     => 0, # not applicable ($state != STATE_INLINE)
+    STATE_INLINE_NAME   => 1, # looking for member name (@foo:)
+    STATE_INLINE_TEXT   => 2, # looking for member documentation
+    STATE_INLINE_END    => 3, # done
+    STATE_INLINE_ERROR  => 4, # error - Comment without header was found.
+                              # Spit a warning as it's not
+                              # proper kernel-doc and ignore the rest.
+};
+my $inline_doc_state;
+
+#declaration types: can be
+# 'function', 'struct', 'union', 'enum', 'typedef'
+my $decl_type;
+
+my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start.
+my $doc_end = '\*/';
+my $doc_com = '\s*\*\s*';
+my $doc_com_body = '\s*\* ?';
+my $doc_decl = $doc_com . '(\w+)';
+# @params and a strictly limited set of supported section names
+my $doc_sect = $doc_com .
+    '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:(.*)';
+my $doc_content = $doc_com_body . '(.*)';
+my $doc_block = $doc_com . 'DOC:\s*(.*)?';
+my $doc_inline_start = '^\s*/\*\*\s*$';
+my $doc_inline_sect = '\s*\*\s*(@[\w\s]+):(.*)';
+my $doc_inline_end = '^\s*\*/\s*$';
+my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$';
+my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;';
+
+my %parameterdescs;
+my %parameterdesc_start_lines;
+my @parameterlist;
+my %sections;
+my @sectionlist;
+my %section_start_lines;
+my $sectcheck;
+my $struct_actual;
+
+my $contents = "";
+my $new_start_line = 0;
+
+# the canonical section names. see also $doc_sect above.
+my $section_default = "Description";   # default section
+my $section_intro = "Introduction";
+my $section = $section_default;
+my $section_context = "Context";
+my $section_return = "Return";
+
+my $undescribed = "-- undescribed --";
+
+reset_state();
+
+while ($ARGV[0] =~ m/^-(.*)/) {
+    my $cmd = shift @ARGV;
+    if ($cmd eq "-html") {
+       $output_mode = "html";
+       @highlights = @highlights_html;
+       $blankline = $blankline_html;
+    } elsif ($cmd eq "-html5") {
+       $output_mode = "html5";
+       @highlights = @highlights_html5;
+       $blankline = $blankline_html5;
+    } elsif ($cmd eq "-man") {
+       $output_mode = "man";
+       @highlights = @highlights_man;
+       $blankline = $blankline_man;
+    } elsif ($cmd eq "-text") {
+       $output_mode = "text";
+       @highlights = @highlights_text;
+       $blankline = $blankline_text;
+    } elsif ($cmd eq "-rst") {
+       $output_mode = "rst";
+       @highlights = @highlights_rst;
+       $blankline = $blankline_rst;
+    } elsif ($cmd eq "-docbook") {
+       $output_mode = "xml";
+       @highlights = @highlights_xml;
+       $blankline = $blankline_xml;
+    } elsif ($cmd eq "-list") {
+       $output_mode = "list";
+       @highlights = @highlights_list;
+       $blankline = $blankline_list;
+    } elsif ($cmd eq "-gnome") {
+       $output_mode = "gnome";
+       @highlights = @highlights_gnome;
+       $blankline = $blankline_gnome;
+    } elsif ($cmd eq "-module") { # not needed for XML, inherits from calling document
+       $modulename = shift @ARGV;
+    } elsif ($cmd eq "-function") { # to only output specific functions
+       $output_selection = OUTPUT_INCLUDE;
+       $function = shift @ARGV;
+       $function_table{$function} = 1;
+    } elsif ($cmd eq "-nofunction") { # output all except specific functions
+       $output_selection = OUTPUT_EXCLUDE;
+       $function = shift @ARGV;
+       $function_table{$function} = 1;
+    } elsif ($cmd eq "-export") { # only exported symbols
+       $output_selection = OUTPUT_EXPORTED;
+       %function_table = ();
+    } elsif ($cmd eq "-internal") { # only non-exported symbols
+       $output_selection = OUTPUT_INTERNAL;
+       %function_table = ();
+    } elsif ($cmd eq "-export-file") {
+       my $file = shift @ARGV;
+       push(@export_file_list, $file);
+    } elsif ($cmd eq "-v") {
+       $verbose = 1;
+    } elsif (($cmd eq "-h") || ($cmd eq "--help")) {
+       usage();
+    } elsif ($cmd eq '-no-doc-sections') {
+           $no_doc_sections = 1;
+    } elsif ($cmd eq '-enable-lineno') {
+           $enable_lineno = 1;
+    } elsif ($cmd eq '-show-not-found') {
+       $show_not_found = 1;
+    }
+}
+
+# continue execution near EOF;
+
+# get kernel version from env
+sub get_kernel_version() {
+    my $version = 'unknown kernel version';
+
+    if (defined($ENV{'KERNELVERSION'})) {
+       $version = $ENV{'KERNELVERSION'};
+    }
+    return $version;
+}
+
+#
+sub print_lineno {
+    my $lineno = shift;
+    if ($enable_lineno && defined($lineno)) {
+        print "#define LINENO " . $lineno . "\n";
+    }
+}
+##
+# dumps section contents to arrays/hashes intended for that purpose.
+#
+sub dump_section {
+    my $file = shift;
+    my $name = shift;
+    my $contents = join "\n", @_;
+
+    if ($name =~ m/$type_param/) {
+       $name = $1;
+       $parameterdescs{$name} = $contents;
+       $sectcheck = $sectcheck . $name . " ";
+        $parameterdesc_start_lines{$name} = $new_start_line;
+        $new_start_line = 0;
+    } elsif ($name eq "@\.\.\.") {
+       $name = "...";
+       $parameterdescs{$name} = $contents;
+       $sectcheck = $sectcheck . $name . " ";
+        $parameterdesc_start_lines{$name} = $new_start_line;
+        $new_start_line = 0;
+    } else {
+       if (defined($sections{$name}) && ($sections{$name} ne "")) {
+           # Only warn on user specified duplicate section names.
+           if ($name ne $section_default) {
+               print STDERR "${file}:$.: warning: duplicate section name '$name'\n";
+               ++$warnings;
+           }
+           $sections{$name} .= $contents;
+       } else {
+           $sections{$name} = $contents;
+           push @sectionlist, $name;
+            $section_start_lines{$name} = $new_start_line;
+            $new_start_line = 0;
+       }
+    }
+}
+
+##
+# dump DOC: section after checking that it should go out
+#
+sub dump_doc_section {
+    my $file = shift;
+    my $name = shift;
+    my $contents = join "\n", @_;
+
+    if ($no_doc_sections) {
+        return;
+    }
+
+    if (($output_selection == OUTPUT_ALL) ||
+       ($output_selection == OUTPUT_INCLUDE &&
+        defined($function_table{$name})) ||
+       ($output_selection == OUTPUT_EXCLUDE &&
+        !defined($function_table{$name})))
+    {
+       dump_section($file, $name, $contents);
+       output_blockhead({'sectionlist' => \@sectionlist,
+                         'sections' => \%sections,
+                         'module' => $modulename,
+                         'content-only' => ($output_selection != OUTPUT_ALL), });
+    }
+}
+
+##
+# output function
+#
+# parameterdescs, a hash.
+#  function => "function name"
+#  parameterlist => @list of parameters
+#  parameterdescs => %parameter descriptions
+#  sectionlist => @list of sections
+#  sections => %section descriptions
+#
+
+sub output_highlight {
+    my $contents = join "\n",@_;
+    my $line;
+
+#   DEBUG
+#   if (!defined $contents) {
+#      use Carp;
+#      confess "output_highlight got called with no args?\n";
+#   }
+
+    if ($output_mode eq "html" || $output_mode eq "html5" ||
+       $output_mode eq "xml") {
+       $contents = local_unescape($contents);
+       # convert data read & converted thru xml_escape() into &xyz; format:
+       $contents =~ s/\\\\\\/\&/g;
+    }
+#   print STDERR "contents b4:$contents\n";
+    eval $dohighlight;
+    die $@ if $@;
+#   print STDERR "contents af:$contents\n";
+
+#   strip whitespaces when generating html5
+    if ($output_mode eq "html5") {
+       $contents =~ s/^\s+//;
+       $contents =~ s/\s+$//;
+    }
+    foreach $line (split "\n", $contents) {
+       if (! $output_preformatted) {
+           $line =~ s/^\s*//;
+       }
+       if ($line eq ""){
+           if (! $output_preformatted) {
+               print $lineprefix, local_unescape($blankline);
+           }
+       } else {
+           $line =~ s/\\\\\\/\&/g;
+           if ($output_mode eq "man" && substr($line, 0, 1) eq ".") {
+               print "\\&$line";
+           } else {
+               print $lineprefix, $line;
+           }
+       }
+       print "\n";
+    }
+}
+
+# output sections in html
+sub output_section_html(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "<h3>$section</h3>\n";
+       print "<blockquote>\n";
+       output_highlight($args{'sections'}{$section});
+       print "</blockquote>\n";
+    }
+}
+
+# output enum in html
+sub output_enum_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "<h2>enum " . $args{'enum'} . "</h2>\n";
+
+    print "<b>enum " . $args{'enum'} . "</b> {<br>\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print " <b>" . $parameter . "</b>";
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",\n";
+       }
+       print "<br>";
+    }
+    print "};<br>\n";
+
+    print "<h3>Constants</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "<dt><b>" . $parameter . "</b>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output typedef in html
+sub output_typedef_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "<h2>typedef " . $args{'typedef'} . "</h2>\n";
+
+    print "<b>typedef " . $args{'typedef'} . "</b>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output struct in html
+sub output_struct_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+
+    print "<h2>" . $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "</h2>\n";
+    print "<b>" . $args{'type'} . " " . $args{'struct'} . "</b> {<br>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+               print "$parameter<br>\n";
+               next;
+       }
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "&nbsp; &nbsp; <i>$1</i><b>$parameter</b>) <i>($2)</i>;<br>\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "&nbsp; &nbsp; <i>$1</i> <b>$parameter</b>$2;<br>\n";
+       } else {
+           print "&nbsp; &nbsp; <i>$type</i> <b>$parameter</b>;<br>\n";
+       }
+    }
+    print "};<br>\n";
+
+    print "<h3>Members</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print "<dt><b>" . $parameter . "</b>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output function in html
+sub output_function_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print "<h2>" . $args{'function'} . " - " . $args{'purpose'} . "</h2>\n";
+    print "<i>" . $args{'functiontype'} . "</i>\n";
+    print "<b>" . $args{'function'} . "</b>\n";
+    print "(";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "<i>$1</i><b>$parameter</b>) <i>($2)</i>";
+       } else {
+           print "<i>" . $type . "</i> <b>" . $parameter . "</b>";
+       }
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",\n";
+       }
+    }
+    print ")\n";
+
+    print "<h3>Arguments</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print "<dt><b>" . $parameter . "</b>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output DOC: block header in html
+sub output_blockhead_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "<h3>$section</h3>\n";
+       print "<ul>\n";
+       output_highlight($args{'sections'}{$section});
+       print "</ul>\n";
+    }
+    print "<hr>\n";
+}
+
+# output sections in html5
+sub output_section_html5(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "<section>\n";
+       print "<h1>$section</h1>\n";
+       print "<p>\n";
+       output_highlight($args{'sections'}{$section});
+       print "</p>\n";
+       print "</section>\n";
+    }
+}
+
+# output enum in html5
+sub output_enum_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'enum'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"enum\" id=\"enum:". $html5id . "\">";
+    print "<h1>enum " . $args{'enum'} . "</h1>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"keyword\">enum</span> ";
+    print "<span class=\"identifier\">" . $args{'enum'} . "</span> {";
+    print "</li>\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "<li class=\"indent\">";
+       print "<span class=\"param\">" . $parameter . "</span>";
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",";
+       }
+       print "</li>\n";
+    }
+    print "<li>};</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Constants</h1>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "<dt>" . $parameter . "</dt>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter});
+       print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output typedef in html5
+sub output_typedef_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'typedef'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"typedef\" id=\"typedef:" . $html5id . "\">\n";
+    print "<h1>typedef " . $args{'typedef'} . "</h1>\n";
+
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"keyword\">typedef</span> ";
+    print "<span class=\"identifier\">" . $args{'typedef'} . "</span>";
+    print "</li>\n";
+    print "</ol>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output struct in html5
+sub output_struct_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $html5id;
+
+    $html5id = $args{'struct'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"struct\" id=\"struct:" . $html5id . "\">\n";
+    print "<hgroup>\n";
+    print "<h1>" . $args{'type'} . " " . $args{'struct'} . "</h1>";
+    print "<h2>". $args{'purpose'} . "</h2>\n";
+    print "</hgroup>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"type\">" . $args{'type'} . "</span> ";
+    print "<span class=\"identifier\">" . $args{'struct'} . "</span> {";
+    print "</li>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "<li class=\"indent\">";
+       if ($parameter =~ /^#/) {
+               print "<span class=\"param\">" . $parameter ."</span>\n";
+               print "</li>\n";
+               next;
+       }
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "<span class=\"type\">$1</span> ";
+           print "<span class=\"param\">$parameter</span>";
+           print "<span class=\"type\">)</span> ";
+           print "(<span class=\"args\">$2</span>);";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "<span class=\"type\">$1</span> ";
+           print "<span class=\"param\">$parameter</span>";
+           print "<span class=\"bits\">$2</span>;";
+       } else {
+           print "<span class=\"type\">$type</span> ";
+           print "<span class=\"param\">$parameter</span>;";
+       }
+       print "</li>\n";
+    }
+    print "<li>};</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Members</h1>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print "<dt>" . $parameter . "</dt>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+       print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output function in html5
+sub output_function_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'function'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"function\" id=\"func:". $html5id . "\">\n";
+    print "<hgroup>\n";
+    print "<h1>" . $args{'function'} . "</h1>";
+    print "<h2>" . $args{'purpose'} . "</h2>\n";
+    print "</hgroup>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"type\">" . $args{'functiontype'} . "</span> ";
+    print "<span class=\"identifier\">" . $args{'function'} . "</span> (";
+    print "</li>";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "<li class=\"indent\">";
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "<span class=\"type\">$1</span> ";
+           print "<span class=\"param\">$parameter</span>";
+           print "<span class=\"type\">)</span> ";
+           print "(<span class=\"args\">$2</span>)";
+       } else {
+           print "<span class=\"type\">$type</span> ";
+           print "<span class=\"param\">$parameter</span>";
+       }
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",";
+       }
+       print "</li>\n";
+    }
+    print "<li>)</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Arguments</h1>\n";
+    print "<p>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print "<dt>" . $parameter . "</dt>\n";
+       print "<dd>";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+       print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output DOC: block header in html5
+sub output_blockhead_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $html5id;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       $html5id = $section;
+       $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+       print "<article class=\"doc\" id=\"doc:". $html5id . "\">\n";
+       print "<h1>$section</h1>\n";
+       print "<p>\n";
+       output_highlight($args{'sections'}{$section});
+       print "</p>\n";
+    }
+    print "</article>\n";
+}
+
+sub output_section_xml(%) {
+    my %args = %{$_[0]};
+    my $section;
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "<refsect1>\n";
+       print "<title>$section</title>\n";
+       if ($section =~ m/EXAMPLE/i) {
+           print "<informalexample><programlisting>\n";
+           $output_preformatted = 1;
+       } else {
+           print "<para>\n";
+       }
+       output_highlight($args{'sections'}{$section});
+       $output_preformatted = 0;
+       if ($section =~ m/EXAMPLE/i) {
+           print "</programlisting></informalexample>\n";
+       } else {
+           print "</para>\n";
+       }
+       print "</refsect1>\n";
+    }
+}
+
+# output function in XML DocBook
+sub output_function_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = "API-" . $args{'function'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>" . $args{'function'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>" . $args{'function'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <funcsynopsis><funcprototype>\n";
+    print "   <funcdef>" . $args{'functiontype'} . " ";
+    print "<function>" . $args{'function'} . " </function></funcdef>\n";
+
+    $count = 0;
+    if ($#{$args{'parameterlist'}} >= 0) {
+       foreach $parameter (@{$args{'parameterlist'}}) {
+           $type = $args{'parametertypes'}{$parameter};
+           if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+               # pointer-to-function
+               print "   <paramdef>$1<parameter>$parameter</parameter>)\n";
+               print "     <funcparams>$2</funcparams></paramdef>\n";
+           } else {
+               print "   <paramdef>" . $type;
+               print " <parameter>$parameter</parameter></paramdef>\n";
+           }
+       }
+    } else {
+       print "  <void/>\n";
+    }
+    print "  </funcprototype></funcsynopsis>\n";
+    print "</refsynopsisdiv>\n";
+
+    # print parameters
+    print "<refsect1>\n <title>Arguments</title>\n";
+    if ($#{$args{'parameterlist'}} >= 0) {
+       print " <variablelist>\n";
+       foreach $parameter (@{$args{'parameterlist'}}) {
+           my $parameter_name = $parameter;
+           $parameter_name =~ s/\[.*//;
+
+           print "  <varlistentry>\n   <term><parameter>$parameter</parameter></term>\n";
+           print "   <listitem>\n    <para>\n";
+           $lineprefix="     ";
+           output_highlight($args{'parameterdescs'}{$parameter_name});
+           print "    </para>\n   </listitem>\n  </varlistentry>\n";
+       }
+       print " </variablelist>\n";
+    } else {
+       print " <para>\n  None\n </para>\n";
+    }
+    print "</refsect1>\n";
+
+    output_section_xml(@_);
+    print "</refentry>\n\n";
+}
+
+# output struct in XML DocBook
+sub output_struct_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $id;
+
+    $id = "API-struct-" . $args{'struct'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>" . $args{'type'} . " " . $args{'struct'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>" . $args{'type'} . " " . $args{'struct'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <programlisting>\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+           my $prm = $parameter;
+           # convert data read & converted thru xml_escape() into &xyz; format:
+           # This allows us to have #define macros interspersed in a struct.
+           $prm =~ s/\\\\\\/\&/g;
+           print "$prm\n";
+           next;
+       }
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       defined($args{'parameterdescs'}{$parameter_name}) || next;
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "  $1 $parameter) ($2);\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "  $1 $parameter$2;\n";
+       } else {
+           print "  " . $type . " " . $parameter . ";\n";
+       }
+    }
+    print "};";
+    print "  </programlisting>\n";
+    print "</refsynopsisdiv>\n";
+
+    print " <refsect1>\n";
+    print "  <title>Members</title>\n";
+
+    if ($#{$args{'parameterlist'}} >= 0) {
+    print "  <variablelist>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+      ($parameter =~ /^#/) && next;
+
+      my $parameter_name = $parameter;
+      $parameter_name =~ s/\[.*//;
+
+      defined($args{'parameterdescs'}{$parameter_name}) || next;
+      ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+      print "    <varlistentry>";
+      print "      <term>$parameter</term>\n";
+      print "      <listitem><para>\n";
+      output_highlight($args{'parameterdescs'}{$parameter_name});
+      print "      </para></listitem>\n";
+      print "    </varlistentry>\n";
+    }
+    print "  </variablelist>\n";
+    } else {
+       print " <para>\n  None\n </para>\n";
+    }
+    print " </refsect1>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output enum in XML DocBook
+sub output_enum_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = "API-enum-" . $args{'enum'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>enum " . $args{'enum'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>enum " . $args{'enum'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <programlisting>\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "  $parameter";
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",";
+       }
+       print "\n";
+    }
+    print "};";
+    print "  </programlisting>\n";
+    print "</refsynopsisdiv>\n";
+
+    print "<refsect1>\n";
+    print " <title>Constants</title>\n";
+    print "  <variablelist>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+      my $parameter_name = $parameter;
+      $parameter_name =~ s/\[.*//;
+
+      print "    <varlistentry>";
+      print "      <term>$parameter</term>\n";
+      print "      <listitem><para>\n";
+      output_highlight($args{'parameterdescs'}{$parameter_name});
+      print "      </para></listitem>\n";
+      print "    </varlistentry>\n";
+    }
+    print "  </variablelist>\n";
+    print "</refsect1>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output typedef in XML DocBook
+sub output_typedef_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $id;
+
+    $id = "API-typedef-" . $args{'typedef'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>typedef " . $args{'typedef'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>typedef " . $args{'typedef'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <synopsis>typedef " . $args{'typedef'} . ";</synopsis>\n";
+    print "</refsynopsisdiv>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output in XML DocBook
+sub output_blockhead_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    my $id = $args{'module'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+       if (!$args{'content-only'}) {
+               print "<refsect1>\n <title>$section</title>\n";
+       }
+       if ($section =~ m/EXAMPLE/i) {
+           print "<example><para>\n";
+           $output_preformatted = 1;
+       } else {
+           print "<para>\n";
+       }
+       output_highlight($args{'sections'}{$section});
+       $output_preformatted = 0;
+       if ($section =~ m/EXAMPLE/i) {
+           print "</para></example>\n";
+       } else {
+           print "</para>";
+       }
+       if (!$args{'content-only'}) {
+               print "\n</refsect1>\n";
+       }
+    }
+
+    print "\n\n";
+}
+
+# output in XML DocBook
+sub output_function_gnome {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = $args{'module'} . "-" . $args{'function'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<sect2>\n";
+    print " <title id=\"$id\">" . $args{'function'} . "</title>\n";
+
+    print "  <funcsynopsis>\n";
+    print "   <funcdef>" . $args{'functiontype'} . " ";
+    print "<function>" . $args{'function'} . " ";
+    print "</function></funcdef>\n";
+
+    $count = 0;
+    if ($#{$args{'parameterlist'}} >= 0) {
+       foreach $parameter (@{$args{'parameterlist'}}) {
+           $type = $args{'parametertypes'}{$parameter};
+           if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+               # pointer-to-function
+               print "   <paramdef>$1 <parameter>$parameter</parameter>)\n";
+               print "     <funcparams>$2</funcparams></paramdef>\n";
+           } else {
+               print "   <paramdef>" . $type;
+               print " <parameter>$parameter</parameter></paramdef>\n";
+           }
+       }
+    } else {
+       print "  <void>\n";
+    }
+    print "  </funcsynopsis>\n";
+    if ($#{$args{'parameterlist'}} >= 0) {
+       print " <informaltable pgwide=\"1\" frame=\"none\" role=\"params\">\n";
+       print "<tgroup cols=\"2\">\n";
+       print "<colspec colwidth=\"2*\">\n";
+       print "<colspec colwidth=\"8*\">\n";
+       print "<tbody>\n";
+       foreach $parameter (@{$args{'parameterlist'}}) {
+           my $parameter_name = $parameter;
+           $parameter_name =~ s/\[.*//;
+
+           print "  <row><entry align=\"right\"><parameter>$parameter</parameter></entry>\n";
+           print "   <entry>\n";
+           $lineprefix="     ";
+           output_highlight($args{'parameterdescs'}{$parameter_name});
+           print "    </entry></row>\n";
+       }
+       print " </tbody></tgroup></informaltable>\n";
+    } else {
+       print " <para>\n  None\n </para>\n";
+    }
+
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "<simplesect>\n <title>$section</title>\n";
+       if ($section =~ m/EXAMPLE/i) {
+           print "<example><programlisting>\n";
+           $output_preformatted = 1;
+       } else {
+       }
+       print "<para>\n";
+       output_highlight($args{'sections'}{$section});
+       $output_preformatted = 0;
+       print "</para>\n";
+       if ($section =~ m/EXAMPLE/i) {
+           print "</programlisting></example>\n";
+       } else {
+       }
+       print " </simplesect>\n";
+    }
+
+    print "</sect2>\n\n";
+}
+
+##
+# output function in man
+sub output_function_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print $args{'function'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    if ($args{'functiontype'} ne "") {
+       print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n";
+    } else {
+       print ".B \"" . $args{'function'} . "\n";
+    }
+    $count = 0;
+    my $parenth = "(";
+    my $post = ",";
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       if ($count == $#{$args{'parameterlist'}}) {
+           $post = ");";
+       }
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n";
+       } else {
+           $type =~ s/([^\*])$/$1 /;
+           print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n";
+       }
+       $count++;
+       $parenth = "";
+    }
+
+    print ".SH ARGUMENTS\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       print ".IP \"" . $parameter . "\" 12\n";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ".SH \"", uc $section, "\"\n";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output enum in man
+sub output_enum_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       print ".br\n.BI \"    $parameter\"\n";
+       if ($count == $#{$args{'parameterlist'}}) {
+           print "\n};\n";
+           last;
+       }
+       else {
+           print ", \n.br\n";
+       }
+       $count++;
+    }
+
+    print ".SH Constants\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       print ".IP \"" . $parameter . "\" 12\n";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ".SH \"$section\"\n";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output struct in man
+sub output_struct_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n.br\n";
+
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+           print ".BI \"$parameter\"\n.br\n";
+           next;
+       }
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print ".BI \"    " . $1 . "\" " . $parameter . " \") (" . $2 . ")" . "\"\n;\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print ".BI \"    " . $1 . "\ \" " . $parameter . $2 . " \"" . "\"\n;\n";
+       } else {
+           $type =~ s/([^\*])$/$1 /;
+           print ".BI \"    " . $type . "\" " . $parameter . " \"" . "\"\n;\n";
+       }
+       print "\n.br\n";
+    }
+    print "};\n.br\n";
+
+    print ".SH Members\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print ".IP \"" . $parameter . "\" 12\n";
+       output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ".SH \"$section\"\n";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output typedef in man
+sub output_typedef_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ".SH \"$section\"\n";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+sub output_blockhead_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ".SH \"$section\"\n";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output in text
+sub output_function_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $start;
+
+    print "Name:\n\n";
+    print $args{'function'} . " - " . $args{'purpose'} . "\n";
+
+    print "\nSynopsis:\n\n";
+    if ($args{'functiontype'} ne "") {
+       $start = $args{'functiontype'} . " " . $args{'function'} . " (";
+    } else {
+       $start = $args{'function'} . " (";
+    }
+    print $start;
+
+    my $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print $1 . $parameter . ") (" . $2;
+       } else {
+           print $type . " " . $parameter;
+       }
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",\n";
+           print " " x length($start);
+       } else {
+           print ");\n\n";
+       }
+    }
+
+    print "Arguments:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       print $parameter . "\n\t" . $args{'parameterdescs'}{$parameter_name} . "\n";
+    }
+    output_section_text(@_);
+}
+
+#output sections in text
+sub output_section_text(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    print "\n";
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "$section:\n\n";
+       output_highlight($args{'sections'}{$section});
+    }
+    print "\n\n";
+}
+
+# output enum in text
+sub output_enum_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "Enum:\n\n";
+
+    print "enum " . $args{'enum'} . " - " . $args{'purpose'} . "\n\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "\t$parameter";
+       if ($count != $#{$args{'parameterlist'}}) {
+           $count++;
+           print ",";
+       }
+       print "\n";
+    }
+    print "};\n\n";
+
+    print "Constants:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "$parameter\n\t";
+       print $args{'parameterdescs'}{$parameter} . "\n";
+    }
+
+    output_section_text(@_);
+}
+
+# output typedef in text
+sub output_typedef_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "Typedef:\n\n";
+
+    print "typedef " . $args{'typedef'} . " - " . $args{'purpose'} . "\n";
+    output_section_text(@_);
+}
+
+# output struct as text
+sub output_struct_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+
+    print $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "\n\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+           print "$parameter\n";
+           next;
+       }
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "\t$1 $parameter) ($2);\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "\t$1 $parameter$2;\n";
+       } else {
+           print "\t" . $type . " " . $parameter . ";\n";
+       }
+    }
+    print "};\n\n";
+
+    print "Members:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       print "$parameter\n\t";
+       print $args{'parameterdescs'}{$parameter_name} . "\n";
+    }
+    print "\n";
+    output_section_text(@_);
+}
+
+sub output_blockhead_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print " $section:\n";
+       print "    -> ";
+       output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output in restructured text
+#
+
+#
+# This could use some work; it's used to output the DOC: sections, and
+# starts by putting out the name of the doc section itself, but that tends
+# to duplicate a header already in the template file.
+#
+sub output_blockhead_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       if ($output_selection != OUTPUT_INCLUDE) {
+           print "**$section**\n\n";
+       }
+        print_lineno($section_start_lines{$section});
+       output_highlight_rst($args{'sections'}{$section});
+       print "\n";
+    }
+}
+
+sub output_highlight_rst {
+    my $contents = join "\n",@_;
+    my $line;
+
+    # undo the evil effects of xml_escape() earlier
+    $contents = xml_unescape($contents);
+
+    eval $dohighlight;
+    die $@ if $@;
+
+    foreach $line (split "\n", $contents) {
+       print $lineprefix . $line . "\n";
+    }
+}
+
+sub output_function_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $oldprefix = $lineprefix;
+    my $start = "";
+
+    if ($args{'typedef'}) {
+       print ".. c:type:: ". $args{'function'} . "\n\n";
+       print_lineno($declaration_start_line);
+       print "   **Typedef**: ";
+       $lineprefix = "";
+       output_highlight_rst($args{'purpose'});
+       $start = "\n\n**Syntax**\n\n  ``";
+    } else {
+       print ".. c:function:: ";
+    }
+    if ($args{'functiontype'} ne "") {
+       $start .= $args{'functiontype'} . " " . $args{'function'} . " (";
+    } else {
+       $start .= $args{'function'} . " (";
+    }
+    print $start;
+
+    my $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       if ($count ne 0) {
+           print ", ";
+       }
+       $count++;
+       $type = $args{'parametertypes'}{$parameter};
+
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print $1 . $parameter . ") (" . $2;
+       } else {
+           print $type . " " . $parameter;
+       }
+    }
+    if ($args{'typedef'}) {
+       print ");``\n\n";
+    } else {
+       print ")\n\n";
+       print_lineno($declaration_start_line);
+       $lineprefix = "   ";
+       output_highlight_rst($args{'purpose'});
+       print "\n";
+    }
+
+    print "**Parameters**\n\n";
+    $lineprefix = "  ";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       #$parameter_name =~ s/\[.*//;
+       $type = $args{'parametertypes'}{$parameter};
+
+       if ($type ne "") {
+           print "``$type $parameter``\n";
+       } else {
+           print "``$parameter``\n";
+       }
+
+        print_lineno($parameterdesc_start_lines{$parameter_name});
+
+       if (defined($args{'parameterdescs'}{$parameter_name}) &&
+           $args{'parameterdescs'}{$parameter_name} ne $undescribed) {
+           output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+       } else {
+           print "  *undescribed*\n";
+       }
+       print "\n";
+    }
+
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+sub output_section_rst(%) {
+    my %args = %{$_[0]};
+    my $section;
+    my $oldprefix = $lineprefix;
+    $lineprefix = "";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "**$section**\n\n";
+        print_lineno($section_start_lines{$section});
+       output_highlight_rst($args{'sections'}{$section});
+       print "\n";
+    }
+    print "\n";
+    $lineprefix = $oldprefix;
+}
+
+sub output_enum_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $oldprefix = $lineprefix;
+    my $count;
+    my $name = "enum " . $args{'enum'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print_lineno($declaration_start_line);
+    $lineprefix = "   ";
+    output_highlight_rst($args{'purpose'});
+    print "\n";
+
+    print "**Constants**\n\n";
+    $lineprefix = "  ";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "``$parameter``\n";
+       if ($args{'parameterdescs'}{$parameter} ne $undescribed) {
+           output_highlight_rst($args{'parameterdescs'}{$parameter});
+       } else {
+           print "  *undescribed*\n";
+       }
+       print "\n";
+    }
+
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+sub output_typedef_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $oldprefix = $lineprefix;
+    my $name = "typedef " . $args{'typedef'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print_lineno($declaration_start_line);
+    $lineprefix = "   ";
+    output_highlight_rst($args{'purpose'});
+    print "\n";
+
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+sub output_struct_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $oldprefix = $lineprefix;
+    my $name = $args{'type'} . " " . $args{'struct'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print_lineno($declaration_start_line);
+    $lineprefix = "   ";
+    output_highlight_rst($args{'purpose'});
+    print "\n";
+
+    print "**Definition**\n\n";
+    print "::\n\n";
+    print "  " . $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+           print "  " . "$parameter\n";
+           next;
+       }
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "    $1 $parameter) ($2);\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "    $1 $parameter$2;\n";
+       } else {
+           print "    " . $type . " " . $parameter . ";\n";
+       }
+    }
+    print "  };\n\n";
+
+    print "**Members**\n\n";
+    $lineprefix = "  ";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+        print_lineno($parameterdesc_start_lines{$parameter_name});
+       print "``" . $parameter . "``\n";
+       output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+       print "\n";
+    }
+    print "\n";
+
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+
+## list mode output functions
+
+sub output_function_list(%) {
+    my %args = %{$_[0]};
+
+    print $args{'function'} . "\n";
+}
+
+# output enum in list
+sub output_enum_list(%) {
+    my %args = %{$_[0]};
+    print $args{'enum'} . "\n";
+}
+
+# output typedef in list
+sub output_typedef_list(%) {
+    my %args = %{$_[0]};
+    print $args{'typedef'} . "\n";
+}
+
+# output struct as list
+sub output_struct_list(%) {
+    my %args = %{$_[0]};
+
+    print $args{'struct'} . "\n";
+}
+
+sub output_blockhead_list(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "DOC: $section\n";
+    }
+}
+
+##
+# generic output function for all types (function, struct/union, typedef, enum);
+# calls the generated, variable output_ function name based on
+# functype and output_mode
+sub output_declaration {
+    no strict 'refs';
+    my $name = shift;
+    my $functype = shift;
+    my $func = "output_${functype}_$output_mode";
+    if (($output_selection == OUTPUT_ALL) ||
+       (($output_selection == OUTPUT_INCLUDE ||
+         $output_selection == OUTPUT_EXPORTED) &&
+        defined($function_table{$name})) ||
+       (($output_selection == OUTPUT_EXCLUDE ||
+         $output_selection == OUTPUT_INTERNAL) &&
+        !($functype eq "function" && defined($function_table{$name}))))
+    {
+       &$func(@_);
+       $section_counter++;
+    }
+}
+
+##
+# generic output function - calls the right one based on current output mode.
+sub output_blockhead {
+    no strict 'refs';
+    my $func = "output_blockhead_" . $output_mode;
+    &$func(@_);
+    $section_counter++;
+}
+
+##
+# takes a declaration (struct, union, enum, typedef) and
+# invokes the right handler. NOT called for functions.
+sub dump_declaration($$) {
+    no strict 'refs';
+    my ($prototype, $file) = @_;
+    my $func = "dump_" . $decl_type;
+    &$func(@_);
+}
+
+sub dump_union($$) {
+    dump_struct(@_);
+}
+
+sub dump_struct($$) {
+    my $x = shift;
+    my $file = shift;
+    my $nested;
+
+    if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) {
+       #my $decl_type = $1;
+       $declaration_name = $2;
+       my $members = $3;
+
+       # ignore embedded structs or unions
+       $members =~ s/({.*})//g;
+       $nested = $1;
+
+       # ignore members marked private:
+       $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi;
+       $members =~ s/\/\*\s*private:.*//gosi;
+       # strip comments:
+       $members =~ s/\/\*.*?\*\///gos;
+       $nested =~ s/\/\*.*?\*\///gos;
+       # strip kmemcheck_bitfield_{begin,end}.*;
+       $members =~ s/kmemcheck_bitfield_.*?;//gos;
+       # strip attributes
+       $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i;
+       $members =~ s/__aligned\s*\([^;]*\)//gos;
+       $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos;
+       # replace DECLARE_BITMAP
+       $members =~ s/DECLARE_BITMAP\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos;
+
+       create_parameterlist($members, ';', $file);
+       check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested);
+
+       output_declaration($declaration_name,
+                          'struct',
+                          {'struct' => $declaration_name,
+                           'module' => $modulename,
+                           'parameterlist' => \@parameterlist,
+                           'parameterdescs' => \%parameterdescs,
+                           'parametertypes' => \%parametertypes,
+                           'sectionlist' => \@sectionlist,
+                           'sections' => \%sections,
+                           'purpose' => $declaration_purpose,
+                           'type' => $decl_type
+                          });
+    }
+    else {
+       print STDERR "${file}:$.: error: Cannot parse struct or union!\n";
+       ++$errors;
+    }
+}
+
+sub dump_enum($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@/\*.*?\*/@@gos;    # strip comments.
+    # strip #define macros inside enums
+    $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos;
+
+    if ($x =~ /enum\s+(\w+)\s*{(.*)}/) {
+       $declaration_name = $1;
+       my $members = $2;
+
+       foreach my $arg (split ',', $members) {
+           $arg =~ s/^\s*(\w+).*/$1/;
+           push @parameterlist, $arg;
+           if (!$parameterdescs{$arg}) {
+               $parameterdescs{$arg} = $undescribed;
+               print STDERR "${file}:$.: warning: Enum value '$arg' ".
+                   "not described in enum '$declaration_name'\n";
+           }
+
+       }
+
+       output_declaration($declaration_name,
+                          'enum',
+                          {'enum' => $declaration_name,
+                           'module' => $modulename,
+                           'parameterlist' => \@parameterlist,
+                           'parameterdescs' => \%parameterdescs,
+                           'sectionlist' => \@sectionlist,
+                           'sections' => \%sections,
+                           'purpose' => $declaration_purpose
+                          });
+    }
+    else {
+       print STDERR "${file}:$.: error: Cannot parse enum!\n";
+       ++$errors;
+    }
+}
+
+sub dump_typedef($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@/\*.*?\*/@@gos;    # strip comments.
+
+    # Parse function prototypes
+    if ($x =~ /typedef\s+(\w+)\s*\(\*\s*(\w\S+)\s*\)\s*\((.*)\);/ ||
+       $x =~ /typedef\s+(\w+)\s*(\w\S+)\s*\s*\((.*)\);/) {
+
+       # Function typedefs
+       $return_type = $1;
+       $declaration_name = $2;
+       my $args = $3;
+
+       create_parameterlist($args, ',', $file);
+
+       output_declaration($declaration_name,
+                          'function',
+                          {'function' => $declaration_name,
+                           'typedef' => 1,
+                           'module' => $modulename,
+                           'functiontype' => $return_type,
+                           'parameterlist' => \@parameterlist,
+                           'parameterdescs' => \%parameterdescs,
+                           'parametertypes' => \%parametertypes,
+                           'sectionlist' => \@sectionlist,
+                           'sections' => \%sections,
+                           'purpose' => $declaration_purpose
+                          });
+       return;
+    }
+
+    while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) {
+       $x =~ s/\(*.\)\s*;$/;/;
+       $x =~ s/\[*.\]\s*;$/;/;
+    }
+
+    if ($x =~ /typedef.*\s+(\w+)\s*;/) {
+       $declaration_name = $1;
+
+       output_declaration($declaration_name,
+                          'typedef',
+                          {'typedef' => $declaration_name,
+                           'module' => $modulename,
+                           'sectionlist' => \@sectionlist,
+                           'sections' => \%sections,
+                           'purpose' => $declaration_purpose
+                          });
+    }
+    else {
+       print STDERR "${file}:$.: error: Cannot parse typedef!\n";
+       ++$errors;
+    }
+}
+
+sub save_struct_actual($) {
+    my $actual = shift;
+
+    # strip all spaces from the actual param so that it looks like one string item
+    $actual =~ s/\s*//g;
+    $struct_actual = $struct_actual . $actual . " ";
+}
+
+sub create_parameterlist($$$) {
+    my $args = shift;
+    my $splitter = shift;
+    my $file = shift;
+    my $type;
+    my $param;
+
+    # temporarily replace commas inside function pointer definition
+    while ($args =~ /(\([^\),]+),/) {
+       $args =~ s/(\([^\),]+),/$1#/g;
+    }
+
+    foreach my $arg (split($splitter, $args)) {
+       # strip comments
+       $arg =~ s/\/\*.*\*\///;
+       # strip leading/trailing spaces
+       $arg =~ s/^\s*//;
+       $arg =~ s/\s*$//;
+       $arg =~ s/\s+/ /;
+
+       if ($arg =~ /^#/) {
+           # Treat preprocessor directive as a typeless variable just to fill
+           # corresponding data structures "correctly". Catch it later in
+           # output_* subs.
+           push_parameter($arg, "", $file);
+       } elsif ($arg =~ m/\(.+\)\s*\(/) {
+           # pointer-to-function
+           $arg =~ tr/#/,/;
+           $arg =~ m/[^\(]+\(\*?\s*(\w*)\s*\)/;
+           $param = $1;
+           $type = $arg;
+           $type =~ s/([^\(]+\(\*?)\s*$param/$1/;
+           save_struct_actual($param);
+           push_parameter($param, $type, $file);
+       } elsif ($arg) {
+           $arg =~ s/\s*:\s*/:/g;
+           $arg =~ s/\s*\[/\[/g;
+
+           my @args = split('\s*,\s*', $arg);
+           if ($args[0] =~ m/\*/) {
+               $args[0] =~ s/(\*+)\s*/ $1/;
+           }
+
+           my @first_arg;
+           if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) {
+                   shift @args;
+                   push(@first_arg, split('\s+', $1));
+                   push(@first_arg, $2);
+           } else {
+                   @first_arg = split('\s+', shift @args);
+           }
+
+           unshift(@args, pop @first_arg);
+           $type = join " ", @first_arg;
+
+           foreach $param (@args) {
+               if ($param =~ m/^(\*+)\s*(.*)/) {
+                   save_struct_actual($2);
+                   push_parameter($2, "$type $1", $file);
+               }
+               elsif ($param =~ m/(.*?):(\d+)/) {
+                   if ($type ne "") { # skip unnamed bit-fields
+                       save_struct_actual($1);
+                       push_parameter($1, "$type:$2", $file)
+                   }
+               }
+               else {
+                   save_struct_actual($param);
+                   push_parameter($param, $type, $file);
+               }
+           }
+       }
+    }
+}
+
+sub push_parameter($$$) {
+       my $param = shift;
+       my $type = shift;
+       my $file = shift;
+
+       if (($anon_struct_union == 1) && ($type eq "") &&
+           ($param eq "}")) {
+               return;         # ignore the ending }; from anon. struct/union
+       }
+
+       $anon_struct_union = 0;
+       my $param_name = $param;
+       $param_name =~ s/\[.*//;
+
+       if ($type eq "" && $param =~ /\.\.\.$/)
+       {
+           if (!$param =~ /\w\.\.\.$/) {
+             # handles unnamed variable parameters
+             $param = "...";
+           }
+           if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") {
+               $parameterdescs{$param} = "variable arguments";
+           }
+       }
+       elsif ($type eq "" && ($param eq "" or $param eq "void"))
+       {
+           $param="void";
+           $parameterdescs{void} = "no arguments";
+       }
+       elsif ($type eq "" && ($param eq "struct" or $param eq "union"))
+       # handle unnamed (anonymous) union or struct:
+       {
+               $type = $param;
+               $param = "{unnamed_" . $param . "}";
+               $parameterdescs{$param} = "anonymous\n";
+               $anon_struct_union = 1;
+       }
+
+       # warn if parameter has no description
+       # (but ignore ones starting with # as these are not parameters
+       # but inline preprocessor statements);
+       # also ignore unnamed structs/unions;
+       if (!$anon_struct_union) {
+       if (!defined $parameterdescs{$param_name} && $param_name !~ /^#/) {
+
+           $parameterdescs{$param_name} = $undescribed;
+
+           if (($type eq 'function') || ($type eq 'enum')) {
+               print STDERR "${file}:$.: warning: Function parameter ".
+                   "or member '$param' not " .
+                   "described in '$declaration_name'\n";
+           }
+           print STDERR "${file}:$.: warning:" .
+                        " No description found for parameter '$param'\n";
+           ++$warnings;
+       }
+       }
+
+       $param = xml_escape($param);
+
+       # strip spaces from $param so that it is one continuous string
+       # on @parameterlist;
+       # this fixes a problem where check_sections() cannot find
+       # a parameter like "addr[6 + 2]" because it actually appears
+       # as "addr[6", "+", "2]" on the parameter list;
+       # but it's better to maintain the param string unchanged for output,
+       # so just weaken the string compare in check_sections() to ignore
+       # "[blah" in a parameter string;
+       ###$param =~ s/\s*//g;
+       push @parameterlist, $param;
+       $parametertypes{$param} = $type;
+}
+
+sub check_sections($$$$$$) {
+       my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_;
+       my @sects = split ' ', $sectcheck;
+       my @prms = split ' ', $prmscheck;
+       my $err;
+       my ($px, $sx);
+       my $prm_clean;          # strip trailing "[array size]" and/or beginning "*"
+
+       foreach $sx (0 .. $#sects) {
+               $err = 1;
+               foreach $px (0 .. $#prms) {
+                       $prm_clean = $prms[$px];
+                       $prm_clean =~ s/\[.*\]//;
+                       $prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i;
+                       # ignore array size in a parameter string;
+                       # however, the original param string may contain
+                       # spaces, e.g.:  addr[6 + 2]
+                       # and this appears in @prms as "addr[6" since the
+                       # parameter list is split at spaces;
+                       # hence just ignore "[..." for the sections check;
+                       $prm_clean =~ s/\[.*//;
+
+                       ##$prm_clean =~ s/^\**//;
+                       if ($prm_clean eq $sects[$sx]) {
+                               $err = 0;
+                               last;
+                       }
+               }
+               if ($err) {
+                       if ($decl_type eq "function") {
+                               print STDERR "${file}:$.: warning: " .
+                                       "Excess function parameter " .
+                                       "'$sects[$sx]' " .
+                                       "description in '$decl_name'\n";
+                               ++$warnings;
+                       } else {
+                               if ($nested !~ m/\Q$sects[$sx]\E/) {
+                                   print STDERR "${file}:$.: warning: " .
+                                       "Excess struct/union/enum/typedef member " .
+                                       "'$sects[$sx]' " .
+                                       "description in '$decl_name'\n";
+                                   ++$warnings;
+                               }
+                       }
+               }
+       }
+}
+
+##
+# Checks the section describing the return value of a function.
+sub check_return_section {
+        my $file = shift;
+        my $declaration_name = shift;
+        my $return_type = shift;
+
+        # Ignore an empty return type (It's a macro)
+        # Ignore functions with a "void" return type. (But don't ignore "void *")
+        if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) {
+                return;
+        }
+
+        if (!defined($sections{$section_return}) ||
+            $sections{$section_return} eq "") {
+                print STDERR "${file}:$.: warning: " .
+                        "No description found for return value of " .
+                        "'$declaration_name'\n";
+                ++$warnings;
+        }
+}
+
+##
+# takes a function prototype and the name of the current file being
+# processed and spits out all the details stored in the global
+# arrays/hashes.
+sub dump_function($$) {
+    my $prototype = shift;
+    my $file = shift;
+    my $noret = 0;
+
+    $prototype =~ s/^static +//;
+    $prototype =~ s/^extern +//;
+    $prototype =~ s/^asmlinkage +//;
+    $prototype =~ s/^inline +//;
+    $prototype =~ s/^__inline__ +//;
+    $prototype =~ s/^__inline +//;
+    $prototype =~ s/^__always_inline +//;
+    $prototype =~ s/^noinline +//;
+    $prototype =~ s/__init +//;
+    $prototype =~ s/__init_or_module +//;
+    $prototype =~ s/__meminit +//;
+    $prototype =~ s/__must_check +//;
+    $prototype =~ s/__weak +//;
+    my $define = $prototype =~ s/^#\s*define\s+//; #ak added
+    $prototype =~ s/__attribute__\s*\(\([a-z,]*\)\)//;
+
+    # Yes, this truly is vile.  We are looking for:
+    # 1. Return type (may be nothing if we're looking at a macro)
+    # 2. Function name
+    # 3. Function parameters.
+    #
+    # All the while we have to watch out for function pointer parameters
+    # (which IIRC is what the two sections are for), C types (these
+    # regexps don't even start to express all the possibilities), and
+    # so on.
+    #
+    # If you mess with these regexps, it's a good idea to check that
+    # the following functions' documentation still comes out right:
+    # - parport_register_device (function pointer parameters)
+    # - atomic_set (macro)
+    # - pci_match_device, __copy_to_user (long return type)
+
+    if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) {
+        # This is an object-like macro, it has no return type and no parameter
+        # list.
+        # Function-like macros are not allowed to have spaces between
+        # declaration_name and opening parenthesis (notice the \s+).
+        $return_type = $1;
+        $declaration_name = $2;
+        $noret = 1;
+    } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+       $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+       $prototype =~ m/^(\w+\s+\w+\s*\*\s*\w+\s*\*\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/)  {
+       $return_type = $1;
+       $declaration_name = $2;
+       my $args = $3;
+
+       create_parameterlist($args, ',', $file);
+    } else {
+       print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n";
+       return;
+    }
+
+       my $prms = join " ", @parameterlist;
+       check_sections($file, $declaration_name, "function", $sectcheck, $prms, "");
+
+        # This check emits a lot of warnings at the moment, because many
+        # functions don't have a 'Return' doc section. So until the number
+        # of warnings goes sufficiently down, the check is only performed in
+        # verbose mode.
+        # TODO: always perform the check.
+        if ($verbose && !$noret) {
+                check_return_section($file, $declaration_name, $return_type);
+        }
+
+    output_declaration($declaration_name,
+                      'function',
+                      {'function' => $declaration_name,
+                       'module' => $modulename,
+                       'functiontype' => $return_type,
+                       'parameterlist' => \@parameterlist,
+                       'parameterdescs' => \%parameterdescs,
+                       'parametertypes' => \%parametertypes,
+                       'sectionlist' => \@sectionlist,
+                       'sections' => \%sections,
+                       'purpose' => $declaration_purpose
+                      });
+}
+
+sub reset_state {
+    $function = "";
+    %parameterdescs = ();
+    %parametertypes = ();
+    @parameterlist = ();
+    %sections = ();
+    @sectionlist = ();
+    $sectcheck = "";
+    $struct_actual = "";
+    $prototype = "";
+
+    $state = STATE_NORMAL;
+    $inline_doc_state = STATE_INLINE_NA;
+}
+
+sub tracepoint_munge($) {
+       my $file = shift;
+       my $tracepointname = 0;
+       my $tracepointargs = 0;
+
+       if ($prototype =~ m/TRACE_EVENT\((.*?),/) {
+               $tracepointname = $1;
+       }
+       if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) {
+               $tracepointname = $1;
+       }
+       if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) {
+               $tracepointname = $2;
+       }
+       $tracepointname =~ s/^\s+//; #strip leading whitespace
+       if ($prototype =~ m/TP_PROTO\((.*?)\)/) {
+               $tracepointargs = $1;
+       }
+       if (($tracepointname eq 0) || ($tracepointargs eq 0)) {
+               print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n".
+                            "$prototype\n";
+       } else {
+               $prototype = "static inline void trace_$tracepointname($tracepointargs)";
+       }
+}
+
+sub syscall_munge() {
+       my $void = 0;
+
+       $prototype =~ s@[\r\n\t]+@ @gos; # strip newlines/CR's/tabs
+##     if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) {
+       if ($prototype =~ m/SYSCALL_DEFINE0/) {
+               $void = 1;
+##             $prototype = "long sys_$1(void)";
+       }
+
+       $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name
+       if ($prototype =~ m/long (sys_.*?),/) {
+               $prototype =~ s/,/\(/;
+       } elsif ($void) {
+               $prototype =~ s/\)/\(void\)/;
+       }
+
+       # now delete all of the odd-number commas in $prototype
+       # so that arg types & arg names don't have a comma between them
+       my $count = 0;
+       my $len = length($prototype);
+       if ($void) {
+               $len = 0;       # skip the for-loop
+       }
+       for (my $ix = 0; $ix < $len; $ix++) {
+               if (substr($prototype, $ix, 1) eq ',') {
+                       $count++;
+                       if ($count % 2 == 1) {
+                               substr($prototype, $ix, 1) = ' ';
+                       }
+               }
+       }
+}
+
+sub process_proto_function($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line
+
+    if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) {
+       # do nothing
+    }
+    elsif ($x =~ /([^\{]*)/) {
+       $prototype .= $1;
+    }
+
+    if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) {
+       $prototype =~ s@/\*.*?\*/@@gos; # strip comments.
+       $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's.
+       $prototype =~ s@^\s+@@gos; # strip leading spaces
+       if ($prototype =~ /SYSCALL_DEFINE/) {
+               syscall_munge();
+       }
+       if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ ||
+           $prototype =~ /DEFINE_SINGLE_EVENT/)
+       {
+               tracepoint_munge($file);
+       }
+       dump_function($prototype, $file);
+       reset_state();
+    }
+}
+
+sub process_proto_type($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's.
+    $x =~ s@^\s+@@gos; # strip leading spaces
+    $x =~ s@\s+$@@gos; # strip trailing spaces
+    $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line
+
+    if ($x =~ /^#/) {
+       # To distinguish preprocessor directive from regular declaration later.
+       $x .= ";";
+    }
+
+    while (1) {
+       if ( $x =~ /([^{};]*)([{};])(.*)/ ) {
+           $prototype .= $1 . $2;
+           ($2 eq '{') && $brcount++;
+           ($2 eq '}') && $brcount--;
+           if (($2 eq ';') && ($brcount == 0)) {
+               dump_declaration($prototype, $file);
+               reset_state();
+               last;
+           }
+           $x = $3;
+       } else {
+           $prototype .= $x;
+           last;
+       }
+    }
+}
+
+# xml_escape: replace <, >, and & in the text stream;
+#
+# however, formatting controls that are generated internally/locally in the
+# kernel-doc script are not escaped here; instead, they begin life like
+# $blankline_html (4 of '\' followed by a mnemonic + ':'), then these strings
+# are converted to their mnemonic-expected output, without the 4 * '\' & ':',
+# just before actual output; (this is done by local_unescape())
+sub xml_escape($) {
+       my $text = shift;
+       if (($output_mode eq "text") || ($output_mode eq "man")) {
+               return $text;
+       }
+       $text =~ s/\&/\\\\\\amp;/g;
+       $text =~ s/\</\\\\\\lt;/g;
+       $text =~ s/\>/\\\\\\gt;/g;
+       return $text;
+}
+
+# xml_unescape: reverse the effects of xml_escape
+sub xml_unescape($) {
+       my $text = shift;
+       if (($output_mode eq "text") || ($output_mode eq "man")) {
+               return $text;
+       }
+       $text =~ s/\\\\\\amp;/\&/g;
+       $text =~ s/\\\\\\lt;/</g;
+       $text =~ s/\\\\\\gt;/>/g;
+       return $text;
+}
+
+# convert local escape strings to html
+# local escape strings look like:  '\\\\menmonic:' (that's 4 backslashes)
+sub local_unescape($) {
+       my $text = shift;
+       if (($output_mode eq "text") || ($output_mode eq "man")) {
+               return $text;
+       }
+       $text =~ s/\\\\\\\\lt:/</g;
+       $text =~ s/\\\\\\\\gt:/>/g;
+       return $text;
+}
+
+sub map_filename($) {
+    my $file;
+    my ($orig_file) = @_;
+
+    if (defined($ENV{'SRCTREE'})) {
+       $file = "$ENV{'SRCTREE'}" . "/" . $orig_file;
+    } else {
+       $file = $orig_file;
+    }
+
+    if (defined($source_map{$file})) {
+       $file = $source_map{$file};
+    }
+
+    return $file;
+}
+
+sub process_export_file($) {
+    my ($orig_file) = @_;
+    my $file = map_filename($orig_file);
+
+    if (!open(IN,"<$file")) {
+       print STDERR "Error: Cannot open file $file\n";
+       ++$errors;
+       return;
+    }
+
+    while (<IN>) {
+       if (/$export_symbol/) {
+           $function_table{$2} = 1;
+       }
+    }
+
+    close(IN);
+}
+
+sub process_file($) {
+    my $file;
+    my $identifier;
+    my $func;
+    my $descr;
+    my $in_purpose = 0;
+    my $initial_section_counter = $section_counter;
+    my ($orig_file) = @_;
+    my $leading_space;
+
+    $file = map_filename($orig_file);
+
+    if (!open(IN,"<$file")) {
+       print STDERR "Error: Cannot open file $file\n";
+       ++$errors;
+       return;
+    }
+
+    $. = 1;
+
+    $section_counter = 0;
+    while (<IN>) {
+       while (s/\\\s*$//) {
+           $_ .= <IN>;
+       }
+       if ($state == STATE_NORMAL) {
+           if (/$doc_start/o) {
+               $state = STATE_NAME;    # next line is always the function name
+               $in_doc_sect = 0;
+               $declaration_start_line = $. + 1;
+           }
+       } elsif ($state == STATE_NAME) {# this line is the function name (always)
+           if (/$doc_block/o) {
+               $state = STATE_DOCBLOCK;
+               $contents = "";
+                $new_start_line = $. + 1;
+
+               if ( $1 eq "" ) {
+                       $section = $section_intro;
+               } else {
+                       $section = $1;
+               }
+           }
+           elsif (/$doc_decl/o) {
+               $identifier = $1;
+               if (/\s*([\w\s]+?)\s*-/) {
+                   $identifier = $1;
+               }
+
+               $state = STATE_FIELD;
+               # if there's no @param blocks need to set up default section
+               # here
+               $contents = "";
+               $section = $section_default;
+               $new_start_line = $. + 1;
+               if (/-(.*)/) {
+                   # strip leading/trailing/multiple spaces
+                   $descr= $1;
+                   $descr =~ s/^\s*//;
+                   $descr =~ s/\s*$//;
+                   $descr =~ s/\s+/ /g;
+                   $declaration_purpose = xml_escape($descr);
+                   $in_purpose = 1;
+               } else {
+                   $declaration_purpose = "";
+               }
+
+               if (($declaration_purpose eq "") && $verbose) {
+                       print STDERR "${file}:$.: warning: missing initial short description on line:\n";
+                       print STDERR $_;
+                       ++$warnings;
+               }
+
+               if ($identifier =~ m/^struct/) {
+                   $decl_type = 'struct';
+               } elsif ($identifier =~ m/^union/) {
+                   $decl_type = 'union';
+               } elsif ($identifier =~ m/^enum/) {
+                   $decl_type = 'enum';
+               } elsif ($identifier =~ m/^typedef/) {
+                   $decl_type = 'typedef';
+               } else {
+                   $decl_type = 'function';
+               }
+
+               if ($verbose) {
+                   print STDERR "${file}:$.: info: Scanning doc for $identifier\n";
+               }
+           } else {
+               print STDERR "${file}:$.: warning: Cannot understand $_ on line $.",
+               " - I thought it was a doc line\n";
+               ++$warnings;
+               $state = STATE_NORMAL;
+           }
+       } elsif ($state == STATE_FIELD) {       # look for head: lines, and include content
+           if (/$doc_sect/i) { # case insensitive for supported section names
+               $newsection = $1;
+               $newcontents = $2;
+
+               # map the supported section names to the canonical names
+               if ($newsection =~ m/^description$/i) {
+                   $newsection = $section_default;
+               } elsif ($newsection =~ m/^context$/i) {
+                   $newsection = $section_context;
+               } elsif ($newsection =~ m/^returns?$/i) {
+                   $newsection = $section_return;
+               } elsif ($newsection =~ m/^\@return$/) {
+                   # special: @return is a section, not a param description
+                   $newsection = $section_return;
+               }
+
+               if (($contents ne "") && ($contents ne "\n")) {
+                   if (!$in_doc_sect && $verbose) {
+                       print STDERR "${file}:$.: warning: contents before sections\n";
+                       ++$warnings;
+                   }
+                   dump_section($file, $section, xml_escape($contents));
+                   $section = $section_default;
+               }
+
+               $in_doc_sect = 1;
+               $in_purpose = 0;
+               $contents = $newcontents;
+                $new_start_line = $.;
+               while ((substr($contents, 0, 1) eq " ") ||
+                      substr($contents, 0, 1) eq "\t") {
+                   $contents = substr($contents, 1);
+               }
+               if ($contents ne "") {
+                   $contents .= "\n";
+               }
+               $section = $newsection;
+               $leading_space = undef;
+           } elsif (/$doc_end/) {
+               if (($contents ne "") && ($contents ne "\n")) {
+                   dump_section($file, $section, xml_escape($contents));
+                   $section = $section_default;
+                   $contents = "";
+               }
+               # look for doc_com + <text> + doc_end:
+               if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') {
+                   print STDERR "${file}:$.: warning: suspicious ending line: $_";
+                   ++$warnings;
+               }
+
+               $prototype = "";
+               $state = STATE_PROTO;
+               $brcount = 0;
+#              print STDERR "end of doc comment, looking for prototype\n";
+           } elsif (/$doc_content/) {
+               # miguel-style comment kludge, look for blank lines after
+               # @parameter line to signify start of description
+               if ($1 eq "") {
+                   if ($section =~ m/^@/ || $section eq $section_context) {
+                       dump_section($file, $section, xml_escape($contents));
+                       $section = $section_default;
+                       $contents = "";
+                        $new_start_line = $.;
+                   } else {
+                       $contents .= "\n";
+                   }
+                   $in_purpose = 0;
+               } elsif ($in_purpose == 1) {
+                   # Continued declaration purpose
+                   chomp($declaration_purpose);
+                   $declaration_purpose .= " " . xml_escape($1);
+                   $declaration_purpose =~ s/\s+/ /g;
+               } else {
+                   my $cont = $1;
+                   if ($section =~ m/^@/ || $section eq $section_context) {
+                       if (!defined $leading_space) {
+                           if ($cont =~ m/^(\s+)/) {
+                               $leading_space = $1;
+                           } else {
+                               $leading_space = "";
+                           }
+                       }
+
+                       $cont =~ s/^$leading_space//;
+                   }
+                   $contents .= $cont . "\n";
+               }
+           } else {
+               # i dont know - bad line?  ignore.
+               print STDERR "${file}:$.: warning: bad line: $_";
+               ++$warnings;
+           }
+       } elsif ($state == STATE_INLINE) { # scanning for inline parameters
+           # First line (state 1) needs to be a @parameter
+           if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) {
+               $section = $1;
+               $contents = $2;
+                $new_start_line = $.;
+               if ($contents ne "") {
+                   while ((substr($contents, 0, 1) eq " ") ||
+                          substr($contents, 0, 1) eq "\t") {
+                       $contents = substr($contents, 1);
+                   }
+                   $contents .= "\n";
+               }
+               $inline_doc_state = STATE_INLINE_TEXT;
+           # Documentation block end */
+           } elsif (/$doc_inline_end/) {
+               if (($contents ne "") && ($contents ne "\n")) {
+                   dump_section($file, $section, xml_escape($contents));
+                   $section = $section_default;
+                   $contents = "";
+               }
+               $state = STATE_PROTO;
+               $inline_doc_state = STATE_INLINE_NA;
+           # Regular text
+           } elsif (/$doc_content/) {
+               if ($inline_doc_state == STATE_INLINE_TEXT) {
+                   $contents .= $1 . "\n";
+                   # nuke leading blank lines
+                   if ($contents =~ /^\s*$/) {
+                       $contents = "";
+                   }
+               } elsif ($inline_doc_state == STATE_INLINE_NAME) {
+                   $inline_doc_state = STATE_INLINE_ERROR;
+                   print STDERR "${file}:$.: warning: ";
+                   print STDERR "Incorrect use of kernel-doc format: $_";
+                   ++$warnings;
+               }
+           }
+       } elsif ($state == STATE_PROTO) {       # scanning for function '{' (end of prototype)
+           if (/$doc_inline_oneline/) {
+               $section = $1;
+               $contents = $2;
+               if ($contents ne "") {
+                   $contents .= "\n";
+                   dump_section($file, $section, xml_escape($contents));
+                   $section = $section_default;
+                   $contents = "";
+               }
+           } elsif (/$doc_inline_start/) {
+               $state = STATE_INLINE;
+               $inline_doc_state = STATE_INLINE_NAME;
+           } elsif ($decl_type eq 'function') {
+               process_proto_function($_, $file);
+           } else {
+               process_proto_type($_, $file);
+           }
+       } elsif ($state == STATE_DOCBLOCK) {
+               if (/$doc_end/)
+               {
+                       dump_doc_section($file, $section, xml_escape($contents));
+                       $section = $section_default;
+                       $contents = "";
+                       $function = "";
+                       %parameterdescs = ();
+                       %parametertypes = ();
+                       @parameterlist = ();
+                       %sections = ();
+                       @sectionlist = ();
+                       $prototype = "";
+                       $state = STATE_NORMAL;
+               }
+               elsif (/$doc_content/)
+               {
+                       if ( $1 eq "" )
+                       {
+                               $contents .= $blankline;
+                       }
+                       else
+                       {
+                               $contents .= $1 . "\n";
+                       }
+               }
+       }
+    }
+    if ($initial_section_counter == $section_counter) {
+       print STDERR "${file}:1: warning: no structured comments found\n";
+       if (($output_selection == OUTPUT_INCLUDE) && ($show_not_found == 1)) {
+           print STDERR "    Was looking for '$_'.\n" for keys %function_table;
+       }
+       if ($output_mode eq "xml") {
+           # The template wants at least one RefEntry here; make one.
+           print "<refentry>\n";
+           print " <refnamediv>\n";
+           print "  <refname>\n";
+           print "   ${orig_file}\n";
+           print "  </refname>\n";
+           print "  <refpurpose>\n";
+           print "   Document generation inconsistency\n";
+           print "  </refpurpose>\n";
+           print " </refnamediv>\n";
+           print " <refsect1>\n";
+           print "  <title>\n";
+           print "   Oops\n";
+           print "  </title>\n";
+           print "  <warning>\n";
+           print "   <para>\n";
+           print "    The template for this document tried to insert\n";
+           print "    the structured comment from the file\n";
+           print "    <filename>${orig_file}</filename> at this point,\n";
+           print "    but none was found.\n";
+           print "    This dummy section is inserted to allow\n";
+           print "    generation to continue.\n";
+           print "   </para>\n";
+           print "  </warning>\n";
+           print " </refsect1>\n";
+           print "</refentry>\n";
+       }
+    }
+}
+
+
+$kernelversion = get_kernel_version();
+
+# generate a sequence of code that will splice in highlighting information
+# using the s// operator.
+for (my $k = 0; $k < @highlights; $k++) {
+    my $pattern = $highlights[$k][0];
+    my $result = $highlights[$k][1];
+#   print STDERR "scanning pattern:$pattern, highlight:($result)\n";
+    $dohighlight .=  "\$contents =~ s:$pattern:$result:gs;\n";
+}
+
+# Read the file that maps relative names to absolute names for
+# separate source and object directories and for shadow trees.
+if (open(SOURCE_MAP, "<.tmp_filelist.txt")) {
+       my ($relname, $absname);
+       while(<SOURCE_MAP>) {
+               chop();
+               ($relname, $absname) = (split())[0..1];
+               $relname =~ s:^/+::;
+               $source_map{$relname} = $absname;
+       }
+       close(SOURCE_MAP);
+}
+
+if ($output_selection == OUTPUT_EXPORTED ||
+    $output_selection == OUTPUT_INTERNAL) {
+
+    push(@export_file_list, @ARGV);
+
+    foreach (@export_file_list) {
+       chomp;
+       process_export_file($_);
+    }
+}
+
+foreach (@ARGV) {
+    chomp;
+    process_file($_);
+}
+if ($verbose && $errors) {
+  print STDERR "$errors errors\n";
+}
+if ($verbose && $warnings) {
+  print STDERR "$warnings warnings\n";
+}
+
+exit($errors);
diff --git a/libdmmp/docs/libdmmp.h.3 b/libdmmp/docs/libdmmp.h.3
new file mode 100644 (file)
index 0000000..45d5be3
--- /dev/null
@@ -0,0 +1,113 @@
+.TH "libdmmp.h" 3 "January 2016" "Device Mapper Multipath API - libdmmp Manual"
+
+.SH NAME
+libdmmp.h \- Device Mapper Multipath API.
+
+.SH SYNOPSIS
+#include <libdmmp/libdmmp.h>
+
+.SH "DESCRIPTION"
+
+All the libdmmp public functions ships its own man pages.
+Use 'man 3 <function_name>' to check the detail usage.
+
+.SH "USAGE"
+
+To use libdmmp in your project, we suggest to use the 'pkg-config' way:
+
+ * Add this line into your configure.ac:
+
+    PKG_CHECK_MODULES([LIBDMMP], [libdmmp])
+
+ * Add these lines into your Makefile.am:
+
+    foo_LDFLAGS += $(LIBDMMP_LIBS)
+    foo_CFLAGS += $(LIBDMMP_CFLAGS)
+
+.SH LOG HANDLING
+
+The log handler function could be set via 'dmmp_context_log_func_set()'.
+The log priority could be set via 'dmmp_context_log_priority_set()'.
+
+By default, the log priorities is 'DMMP_LOG_PRIORITY_WARNING'.
+By default, the log handler is print log to STDERR, and its code is listed
+below in case you want to create your own log handler.
+
+        static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80;
+
+        static void _log_stderr(struct dmmp_context *ctx,
+                                enum dmmp_log_priority priority,
+                                const char *file, int line,
+                                const char *func_name,
+                                const char *format, va_list args)
+        {
+            int printed_bytes = 0;
+
+            printed_bytes += fprintf(stderr, "libdmmp %s: ",
+                                     dmmp_log_priority_str(priority));
+            printed_bytes += vfprintf(stderr, format, args);
+            userdata = dmmp_context_userdata_get(ctx);
+            if (userdata != NULL)
+                fprintf(stderr, "(with user data at memory address %p)",
+                        userdata);
+
+            if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) {
+                fprintf(stderr, "%*s # %s:%s():%d\n",
+                        _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file,
+                        func_name, line);
+            } else {
+                fprintf(stderr, " # %s:%s():%d\n", file, func_name, line);
+            }
+        }
+
+
+.SH "SAMPLE CODE"
+
+    #include <libdmmp/libdmmp.h>
+
+    int main(int argc, char *argv[]) {
+        struct dmmp_context *ctx = NULL;
+        struct dmmp_mpath **dmmp_mps = NULL;
+        struct dmmp_path_group **dmmp_pgs = NULL;
+        struct dmmp_path **dmmp_ps = NULL;
+        uint32_t dmmp_mp_count = 0;
+        uint32_t dmmp_pg_count = 0;
+        uint32_t dmmp_p_count = 0;
+        const char *name = NULL;
+        const char *wwid = NULL;
+        uint32_t i = 0;
+        int rc = DMMP_OK;
+
+        ctx = dmmp_context_new();
+        dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG);
+        // By default, log will be printed to STDERR, you could
+        // change that via dmmp_context_log_func_set()
+        rc = dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count);
+        if (rc != DMMP_OK) {
+            printf("dmmp_mpath_array_get() failed with %d: %s", rc,
+                   dmmp_strerror(rc));
+            goto out;
+        }
+        for (i = 0; i < dmmp_mp_count; ++i) {
+                name = dmmp_mpath_name_get(dmmp_mps[i]);
+                wwid = dmmp_mpath_wwid_get(dmmp_mps[i]);
+                printf("dmmp_mpath_array_get(): Got mpath: %s %s\n", name,
+                       wwid);
+                // You could use dmmp_path_group_array_get() to retrieve
+                // path group information and then invoke dmmp_path_array_get()
+                // for path information.
+        }
+
+     out:
+        dmmp_context_free(ctx);
+        dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+        if (rc != DMMP_OK)
+            exit(1);
+        exit(0);
+    }
+
+.SH "LICENSE"
+GPLv2+
+
+.SH "BUG"
+Please report bug to <dm-devel@redhat.com>
diff --git a/libdmmp/docs/split-man.pl b/libdmmp/docs/split-man.pl
new file mode 100644 (file)
index 0000000..a97acc1
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+# Originally From:
+# https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt
+#
+# Changes:
+#   * Create manpage section 3 instead of 9.
+#   * Replace 'Kernel Hackers Manual' to
+#       'Device Mapper Multipath API - libdmmp Manual'
+#   * Remove LINUX from header.
+#   * Remove DMMP_DLL_EXPORT.
+$man_sec_num = 3;
+$title = 'Device Mapper Multipath API - libdmmp Manual';
+
+if ( $#ARGV < 0 ) {
+    die "where do I put the results?\n";
+}
+
+mkdir $ARGV[0], 0777;
+$state = 0;
+while (<STDIN>) {
+    if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) {
+        if ( $state == 1 ) { close OUT }
+        $state = 1;
+        $fn    = "$ARGV[0]/$1.$man_sec_num";
+        print STDERR "Creating $fn\n";
+        open OUT, ">$fn" or die "can't open $fn: $!\n";
+
+        # Change man page code from 9 to $man_sec_num;
+        s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/;
+        s/Kernel Hacker's Manual/$title/g;
+        s/LINUX//g;
+
+        print OUT $_;
+    }
+    elsif ( $state != 0 ) {
+        print OUT $_;
+    }
+}
+
+close OUT;
diff --git a/libdmmp/libdmmp.c b/libdmmp/libdmmp.c
new file mode 100644 (file)
index 0000000..3906335
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <libudev.h>
+#include <errno.h>
+#include <libdevmapper.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <assert.h>
+#include <json.h>
+#include <mpath_cmd.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DEFAULT_UXSOCK_TIMEOUT                60000
+/* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get()
+ *   only take 3.5 seconds, so this default value should be OK for most users.
+ */
+
+#define _DMMP_IPC_SHOW_JSON_CMD                        "show maps json"
+#define _DMMP_JSON_MAJOR_KEY                   "major_version"
+#define _DMMP_JSON_MAJOR_VERSION               0
+#define _DMMP_JSON_MAPS_KEY                    "maps"
+#define _ERRNO_STR_BUFF_SIZE                   256
+
+struct dmmp_context {
+       void (*log_func)(struct dmmp_context *ctx, int priority,
+                        const char *file, int line, const char *func_name,
+                        const char *format, va_list args);
+       int log_priority;
+       void *userdata;
+       unsigned int tmo;
+};
+
+_dmmp_getter_func_gen(dmmp_context_log_priority_get,
+                     struct dmmp_context, ctx, log_priority,
+                     int);
+
+_dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
+                     userdata, void *);
+
+_dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
+                     unsigned int);
+
+_dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
+                         _dmmp_mpath_free);
+
+void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
+              int line, const char *func_name, const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       ctx->log_func(ctx, priority, file, line, func_name, format, args);
+       va_end(args);
+}
+
+struct dmmp_context *dmmp_context_new(void)
+{
+       struct dmmp_context *ctx = NULL;
+
+       ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
+
+       if (ctx == NULL)
+               return NULL;
+
+       ctx->log_func = _dmmp_log_stderr;
+       ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
+       ctx->userdata = NULL;
+       ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
+
+       return ctx;
+}
+
+void dmmp_context_free(struct dmmp_context *ctx)
+{
+       free(ctx);
+}
+
+void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
+{
+       assert(ctx != NULL);
+       ctx->log_priority = priority;
+}
+
+void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
+{
+       assert(ctx != NULL);
+       ctx->tmo = tmo;
+}
+
+void dmmp_context_log_func_set
+       (struct dmmp_context *ctx,
+        void (*log_func)(struct dmmp_context *ctx, int priority,
+                         const char *file, int line, const char *func_name,
+                         const char *format, va_list args))
+{
+       assert(ctx != NULL);
+       ctx->log_func = log_func;
+}
+
+void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
+{
+       assert(ctx != NULL);
+       ctx->userdata = userdata;
+}
+
+int dmmp_mpath_array_get(struct dmmp_context *ctx,
+                        struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
+{
+       struct dmmp_mpath *dmmp_mp = NULL;
+       int rc = DMMP_OK;
+       char *j_str = NULL;
+       json_object *j_obj = NULL;
+       json_object *j_obj_map = NULL;
+       enum json_tokener_error j_err = json_tokener_success;
+       json_tokener *j_token = NULL;
+       struct array_list *ar_maps = NULL;
+       uint32_t i = 0;
+       int cur_json_major_version = -1;
+       int ar_maps_len = -1;
+       int socket_fd = -1;
+       int errno_save = 0;
+       char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
+
+       assert(ctx != NULL);
+       assert(dmmp_mps != NULL);
+       assert(dmmp_mp_count != NULL);
+
+       *dmmp_mps = NULL;
+       *dmmp_mp_count = 0;
+
+       socket_fd = mpath_connect();
+       if (socket_fd == -1) {
+               errno_save = errno;
+               memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
+               strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
+               if (errno_save == ECONNREFUSED) {
+                       rc = DMMP_ERR_NO_DAEMON;
+                       _error(ctx, "Socket connection refuse. "
+                              "Maybe multipathd daemon is not running");
+               } else {
+                       _error(ctx, "IPC failed with error %d(%s)", errno_save,
+                              errno_str_buff);
+                       rc = DMMP_ERR_IPC_ERROR;
+               }
+               goto out;
+       }
+
+       if (mpath_process_cmd(socket_fd, _DMMP_IPC_SHOW_JSON_CMD,
+                             &j_str, ctx->tmo) != 0) {
+               errno_save = errno;
+               memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
+               strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
+               if (errno_save == ETIMEDOUT) {
+                       rc = DMMP_ERR_IPC_TIMEOUT;
+                       _error(ctx, "IPC communication timeout, try to "
+                              "increase it via dmmp_context_timeout_set()");
+                       goto out;
+               }
+               _error(ctx, "IPC failed when process command '%s' with "
+                      "error %d(%s)", _DMMP_IPC_SHOW_JSON_CMD, errno_save,
+                      errno_str_buff);
+               rc = DMMP_ERR_IPC_ERROR;
+               goto out;
+       }
+
+       if ((j_str == NULL) || (strlen(j_str) == 0)) {
+               _error(ctx, "IPC return empty reply for command %s",
+                      _DMMP_IPC_SHOW_JSON_CMD);
+               rc = DMMP_ERR_IPC_ERROR;
+               goto out;
+       }
+
+       _debug(ctx, "Got json output from multipathd: '%s'", j_str);
+       j_token = json_tokener_new();
+       if (j_token == NULL) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: json_tokener_new() retuned NULL");
+               goto out;
+       }
+       j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
+
+       if (j_obj == NULL) {
+               rc = DMMP_ERR_IPC_ERROR;
+               j_err = json_tokener_get_error(j_token);
+               _error(ctx, "Failed to parse JSON output from multipathd IPC: "
+                      "%s", json_tokener_error_desc(j_err));
+               goto out;
+       }
+
+       _json_obj_get_value(ctx, j_obj, cur_json_major_version,
+                           _DMMP_JSON_MAJOR_KEY, json_type_int,
+                           json_object_get_int, rc, out);
+
+       if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) {
+               rc = DMMP_ERR_INCOMPATIBLE;
+               _error(ctx, "Incompatible multipathd JSON major version %d, "
+                      "should be %d", cur_json_major_version,
+                      _DMMP_JSON_MAJOR_VERSION);
+               goto out;
+       }
+       _debug(ctx, "multipathd JSON major version(%d) check pass",
+              _DMMP_JSON_MAJOR_VERSION);
+
+       _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
+                           json_type_array, json_object_get_array, rc, out);
+
+       if (ar_maps == NULL) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: Got NULL map array from "
+                      "_json_obj_get_value()");
+               goto out;
+       }
+
+       ar_maps_len = array_list_length(ar_maps);
+       if (ar_maps_len < 0) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: Got negative length for ar_maps");
+               goto out;
+       }
+       else if (ar_maps_len == 0)
+               goto out;
+       else
+               *dmmp_mp_count = ar_maps_len & UINT32_MAX;
+
+       *dmmp_mps = (struct dmmp_mpath **)
+               malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count));
+       _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out);
+       for (; i < *dmmp_mp_count; ++i)
+               (*dmmp_mps)[i] = NULL;
+
+       for (i = 0; i < *dmmp_mp_count; ++i) {
+               j_obj_map = array_list_get_idx(ar_maps, i);
+               if (j_obj_map == NULL) {
+                       rc = DMMP_ERR_BUG;
+                       _error(ctx, "BUG: array_list_get_idx() return NULL");
+                       goto out;
+               }
+
+               dmmp_mp = _dmmp_mpath_new();
+               _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out);
+               (*dmmp_mps)[i] = dmmp_mp;
+               _good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out);
+       }
+
+out:
+       if (socket_fd >= 0)
+               mpath_disconnect(socket_fd);
+       free(j_str);
+       if (j_token != NULL)
+               json_tokener_free(j_token);
+       if (j_obj != NULL)
+               json_object_put(j_obj);
+
+       if (rc != DMMP_OK) {
+               dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
+               *dmmp_mps = NULL;
+               *dmmp_mp_count = 0;
+       }
+
+       return rc;
+}
diff --git a/libdmmp/libdmmp.pc.in b/libdmmp/libdmmp.pc.in
new file mode 100644 (file)
index 0000000..ebb8cad
--- /dev/null
@@ -0,0 +1,9 @@
+includedir=__INCLUDEDIR__
+libdir=__LIBDIR__
+
+Name: libdmmp
+Version: __VERSION__
+Description: Device mapper multipath management library
+Requires:
+Libs: -L${libdir} -ldmmp
+Cflags: -I${includedir}
diff --git a/libdmmp/libdmmp/libdmmp.h b/libdmmp/libdmmp/libdmmp.h
new file mode 100644 (file)
index 0000000..0679158
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2015 - 2017 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+
+#ifndef _LIB_DMMP_H_
+#define _LIB_DMMP_H_
+
+#include <stdint.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DMMP_DLL_EXPORT                __attribute__ ((visibility ("default")))
+#define DMMP_DLL_LOCAL         __attribute__ ((visibility ("hidden")))
+
+#define DMMP_OK                                0
+#define DMMP_ERR_BUG                   1
+#define DMMP_ERR_NO_MEMORY             2
+#define DMMP_ERR_IPC_TIMEOUT           3
+#define DMMP_ERR_IPC_ERROR             4
+#define DMMP_ERR_NO_DAEMON             5
+#define DMMP_ERR_INCOMPATIBLE          6
+
+/*
+ * Use the syslog severity level as log priority
+ */
+#define DMMP_LOG_PRIORITY_ERROR                3
+#define DMMP_LOG_PRIORITY_WARNING      4
+#define DMMP_LOG_PRIORITY_INFO         6
+#define DMMP_LOG_PRIORITY_DEBUG                7
+
+#define DMMP_LOG_PRIORITY_DEFAULT      DMMP_LOG_PRIORITY_WARNING
+
+/**
+ * dmmp_log_priority_str() - Convert log priority to string.
+ *
+ * Convert log priority to string (const char *).
+ *
+ * @priority:
+ *     int. Log priority.
+ *
+ * Return:
+ *     const char *. Valid string are:
+ *
+ *     * "ERROR" for DMMP_LOG_PRIORITY_ERROR
+ *
+ *     * "WARN " for DMMP_LOG_PRIORITY_WARNING
+ *
+ *     * "INFO " for DMMP_LOG_PRIORITY_INFO
+ *
+ *     * "DEBUG" for DMMP_LOG_PRIORITY_DEBUG
+ *
+ *     * "Invalid argument" for invalid log priority.
+ */
+DMMP_DLL_EXPORT const char *dmmp_log_priority_str(int priority);
+
+struct DMMP_DLL_EXPORT dmmp_context;
+
+struct DMMP_DLL_EXPORT dmmp_mpath;
+
+struct DMMP_DLL_EXPORT dmmp_path_group;
+
+#define DMMP_PATH_GROUP_STATUS_UNKNOWN 0
+#define DMMP_PATH_GROUP_STATUS_ENABLED 1
+#define DMMP_PATH_GROUP_STATUS_DISABLED        2
+#define DMMP_PATH_GROUP_STATUS_ACTIVE  3
+
+struct DMMP_DLL_EXPORT dmmp_path;
+
+#define DMMP_PATH_STATUS_UNKNOWN       0
+//#define DMMP_PATH_STATUS_UNCHECKED   1
+// ^ print.h does not expose this.
+#define DMMP_PATH_STATUS_DOWN          2
+#define DMMP_PATH_STATUS_UP            3
+#define DMMP_PATH_STATUS_SHAKY         4
+#define DMMP_PATH_STATUS_GHOST         5
+#define DMMP_PATH_STATUS_PENDING       6
+#define DMMP_PATH_STATUS_TIMEOUT       7
+//#define DMMP_PATH_STATUS_REMOVED     8
+// ^ print.h does not expose this.
+#define DMMP_PATH_STATUS_DELAYED       9
+
+/**
+ * dmmp_strerror() - Convert error code to string.
+ *
+ * Convert error code (int) to string (const char *):
+ *
+ *     * DMMP_OK -- "OK"
+ *
+ *     * DMMP_ERR_BUG -- "BUG of libdmmp library"
+ *
+ *     * DMMP_ERR_NO_MEMORY -- "Out of memory"
+ *
+ *     * DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd,
+ *       try to set bigger timeout value via dmmp_context_timeout_set ()"
+ *
+ *     * DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon"
+ *
+ *     * DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started"
+ *
+ *     * DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not
+ *       compatible with current library"
+ *
+ *     * Other invalid error number -- "Invalid argument"
+ *
+ * @rc:
+ *     int. Return code by libdmmp functions. When provided error code is not a
+ *     valid error code, return "Invalid argument".
+ *
+ * Return:
+ *     const char *. The meaning of provided error code.
+ *
+ */
+DMMP_DLL_EXPORT const char *dmmp_strerror(int rc);
+
+/**
+ * dmmp_context_new() - Create struct dmmp_context.
+ *
+ * The default logging level (DMMP_LOG_PRIORITY_DEFAULT) is
+ * DMMP_LOG_PRIORITY_WARNING which means only warning and error message will be
+ * forward to log handler function.  The default log handler function will print
+ * log message to STDERR, to change so, please use dmmp_context_log_func_set()
+ * to set your own log handler, check manpage libdmmp.h(3) for detail.
+ *
+ * Return:
+ *     Pointer of 'struct dmmp_context'. Should be freed by
+ *     dmmp_context_free().
+ */
+DMMP_DLL_EXPORT struct dmmp_context *dmmp_context_new(void);
+
+/**
+ * dmmp_context_free() - Release the memory of struct dmmp_context.
+ *
+ * Release the memory of struct dmmp_context, but the userdata memory defined
+ * via dmmp_context_userdata_set() will not be touched.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_context_free(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_timeout_set() - Set IPC timeout.
+ *
+ * By default, the IPC to multipathd daemon will timeout after 60 seconds.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * @tmo:
+ *     Timeout in milliseconds(1 seconds equal 1000 milliseconds).
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_context_timeout_set(struct dmmp_context *ctx,
+                                             unsigned int tmo);
+
+/**
+ * dmmp_context_timeout_get() - Get IPC timeout.
+ *
+ * Retrieve timeout value of IPC connection to multipathd daemon.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     unsigned int. Timeout in milliseconds.
+ */
+DMMP_DLL_EXPORT unsigned int dmmp_context_timeout_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_log_priority_set() - Set log priority.
+ *
+ *
+ * When library generates log message, only equal or more important(less value)
+ * message will be forwarded to log handler function. Valid log priority values
+ * are:
+ *
+ *     * DMMP_LOG_PRIORITY_ERROR -- 3
+ *
+ *     * DMMP_LOG_PRIORITY_WARNING -- 4
+ *
+ *     * DMMP_LOG_PRIORITY_INFO -- 5
+ *
+ *     * DMMP_LOG_PRIORITY_DEBUG -- 7
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * @priority:
+ *     int, log priority.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_context_log_priority_set(struct dmmp_context *ctx,
+                                                  int priority);
+
+/**
+ * dmmp_context_log_priority_get() - Get log priority.
+ *
+ * Retrieve current log priority. Valid log priority values are:
+ *
+ *     * DMMP_LOG_PRIORITY_ERROR -- 3
+ *
+ *     * DMMP_LOG_PRIORITY_WARNING -- 4
+ *
+ *     * DMMP_LOG_PRIORITY_INFO -- 5
+ *
+ *     * DMMP_LOG_PRIORITY_DEBUG -- 7
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     int, log priority.
+ */
+DMMP_DLL_EXPORT int dmmp_context_log_priority_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_log_func_set() - Set log handler function.
+ *
+ * Set custom log handler. The log handler will be invoked when log message
+ * is equal or more important(less value) than log priority setting.
+ * Please check manpage libdmmp.h(3) for detail usage.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @log_func:
+ *     Pointer of log handler function.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_context_log_func_set
+       (struct dmmp_context *ctx,
+        void (*log_func)
+        (struct dmmp_context *ctx, int priority,
+         const char *file, int line, const char *func_name,
+         const char *format, va_list args));
+
+/**
+ * dmmp_context_userdata_set() - Set user data pointer.
+ *
+ * Store user data pointer into 'struct dmmp_context'.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @userdata:
+ *     Pointer of user defined data.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_context_userdata_set(struct dmmp_context *ctx,
+                                              void *userdata);
+
+/**
+ * dmmp_context_userdata_get() - Get user data pointer.
+ *
+ * Retrieve user data pointer from 'struct dmmp_context'.
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     void *. Pointer of user defined data.
+ */
+DMMP_DLL_EXPORT void *dmmp_context_userdata_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_mpath_array_get() - Query all existing multipath devices.
+ *
+ * Query all existing multipath devices and store them into a pointer array.
+ * The memory of 'dmmp_mps' should be freed via dmmp_mpath_array_free().
+ *
+ * @ctx:
+ *     Pointer of 'struct dmmp_context'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_mps:
+ *     Output pointer array of 'struct dmmp_mpath'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_mp_count:
+ *     Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     int. Valid error codes are:
+ *
+ *     * DMMP_OK
+ *
+ *     * DMMP_ERR_BUG
+ *
+ *     * DMMP_ERR_NO_MEMORY
+ *
+ *     * DMMP_ERR_NO_DAEMON
+ *
+ *     * DMMP_ERR_INCONSISTENT_DATA
+ *
+ *     Error number could be converted to string by dmmp_strerror().
+ */
+DMMP_DLL_EXPORT int dmmp_mpath_array_get(struct dmmp_context *ctx,
+                                        struct dmmp_mpath ***dmmp_mps,
+                                        uint32_t *dmmp_mp_count);
+
+/**
+ * dmmp_mpath_array_free() - Free 'struct dmmp_mpath' pointer array.
+ *
+ * Free the 'dmmp_mps' pointer array generated by dmmp_mpath_array_get().
+ * If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing.
+ *
+ * @dmmp_mps:
+ *     Pointer of 'struct dmmp_mpath' array.
+ * @dmmp_mp_count:
+ *     uint32_t, the size of 'dmmp_mps' pointer array.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_mpath_array_free(struct dmmp_mpath **dmmp_mps,
+                                          uint32_t dmmp_mp_count);
+
+/**
+ * dmmp_mpath_wwid_get() - Retrieve WWID of certain mpath.
+ *
+ * @dmmp_mp:
+ *     Pointer of 'struct dmmp_mpath'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     const char *. No need to free this memory, the resources will get
+ *     freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_mpath_wwid_get(struct dmmp_mpath *dmmp_mp);
+
+/**
+ * dmmp_mpath_name_get() - Retrieve name(alias) of certain mpath.
+ *
+ * Retrieve the name (also known as alias) of certain mpath.
+ * When the config 'user_friendly_names' been set 'no', the name will be
+ * identical to WWID retrieved by dmmp_mpath_wwid_get().
+ *
+ * @dmmp_mp:
+ *     Pointer of 'struct dmmp_mpath'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     const char *. No need to free this memory, the resources will get
+ *     freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_mpath_name_get(struct dmmp_mpath *dmmp_mp);
+
+/**
+ * dmmp_mpath_kdev_name_get() - Retrieve kernel DEVNAME of certain mpath.
+ *
+ * Retrieve DEVNAME name used by kernel uevent of specified mpath.
+ * Example: 'dm-1'.
+ *
+ * @dmmp_mp:
+ *     Pointer of 'struct dmmp_mpath'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     const char *. No need to free this memory, the resources will get
+ *     freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_mpath_kdev_name_get
+       (struct dmmp_mpath *dmmp_mp);
+
+/**
+ * dmmp_path_group_array_get() - Retrieve path groups pointer array.
+ *
+ * Retrieve the path groups of certain mpath.
+ *
+ * The memory of output pointer array is hold by 'struct dmmp_mpath', no
+ * need to free this memory, the resources will got freed when
+ * dmmp_mpath_array_free().
+ *
+ * @dmmp_mp:
+ *     Pointer of 'struct dmmp_mpath'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_pgs:
+ *     Output pointer of 'struct dmmp_path_group' pointer array.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_pg_count:
+ *     Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_path_group_array_get
+       (struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs,
+        uint32_t *dmmp_pg_count);
+
+/**
+ * dmmp_path_group_id_get() - Retrieve path group ID.
+ *
+ * Retrieve the path group ID which could be used to switch active path group
+ * via command:
+ *
+ *     multipathd -k'switch multipath mpathb group $id'
+ *
+ * @dmmp_pg:
+ *     Pointer of 'struct dmmp_path_group'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_id_get
+       (struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_priority_get() - Retrieve path group priority.
+ *
+ * The enabled path group with highest priority will be next active path group
+ * if active path group down.
+ *
+ * @dmmp_pg:
+ *     Pointer of 'struct dmmp_path_group'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_priority_get
+       (struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_status_get() - Retrieve path group status.
+ *
+ * The valid path group statuses are:
+ *
+ *     * DMMP_PATH_GROUP_STATUS_UNKNOWN
+ *
+ *     * DMMP_PATH_GROUP_STATUS_ENABLED  -- standby to be active
+ *
+ *     * DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down
+ *
+ *     * DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O
+ *
+ * @dmmp_pg:
+ *     Pointer of 'struct dmmp_path_group'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_status_get
+       (struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_status_str() - Convert path group status to string.
+ *
+ * Convert path group status uint32_t to string (const char *).
+ *
+ * @pg_status:
+ *     uint32_t. Path group status.
+ *     When provided value is not a valid path group status, return "Invalid
+ *     argument".
+ *
+ * Return:
+ *     const char *. Valid string are:
+ *
+ *             * "Invalid argument"
+ *
+ *             * "undef"
+ *
+ *             * "enabled"
+ *
+ *             * "disabled"
+ *
+ *             * "active"
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_group_status_str(uint32_t pg_status);
+
+/**
+ * dmmp_path_group_selector_get() - Retrieve path group selector.
+ *
+ * Path group selector determine which path in active path group will be
+ * use to next I/O.
+ *
+ * @dmmp_pg:
+ *     Pointer of 'struct dmmp_path_group'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     const char *.
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_group_selector_get
+       (struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_array_get() - Retrieve path pointer array.
+ *
+ * The memory of output pointer array is hold by 'struct dmmp_mpath', no
+ * need to free this memory, the resources will got freed when
+ * dmmp_mpath_array_free().
+ *
+ * @dmmp_pg:
+ *     Pointer of 'struct dmmp_path_group'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_ps:
+ *     Output pointer of 'struct dmmp_path' pointer array.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_p_count:
+ *     Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     void
+ */
+DMMP_DLL_EXPORT void dmmp_path_array_get(struct dmmp_path_group *dmmp_pg,
+                                        struct dmmp_path ***dmmp_ps,
+                                        uint32_t *dmmp_p_count);
+
+/**
+ * dmmp_path_blk_name_get() - Retrieve block name.
+ *
+ * Retrieve block name of certain path. The example of block names are 'sda',
+ * 'nvme0n1'.
+ *
+ * @dmmp_p:
+ *     Pointer of 'struct dmmp_path'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     const char *. No need to free this memory, the resources will get
+ *     freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_blk_name_get(struct dmmp_path *dmmp_p);
+
+/**
+ * dmmp_path_status_get() - Retrieve the path status.
+ *
+ * The valid path statuses are:
+ *
+ *     * DMMP_PATH_STATUS_UNKNOWN
+ *
+ *     * DMMP_PATH_STATUS_DOWN
+ *
+ *     Path is down and you shouldn't try to send commands to it.
+ *
+ *     * DMMP_PATH_STATUS_UP
+ *
+ *     Path is up and I/O can be sent to it.
+ *
+ *     * DMMP_PATH_STATUS_SHAKY
+ *
+ *     Only emc_clariion checker when path not available for "normal"
+ *     operations.
+ *
+ *     * DMMP_PATH_STATUS_GHOST
+ *
+ *             Only hp_sw and rdac checkers.  Indicates a "passive/standby"
+ *             path on active/passive HP arrays. These paths will return valid
+ *             answers to certain SCSI commands (tur, read_capacity, inquiry,
+ *             start_stop), but will fail I/O commands.  The path needs an
+ *             initialization command to be sent to it in order for I/Os to
+ *             succeed.
+ *
+ *     * DMMP_PATH_STATUS_PENDING
+ *
+ *     Available for all async checkers when a check IO is in flight.
+ *
+ *     * DMMP_PATH_STATUS_TIMEOUT
+ *
+ *     Only tur checker when command timed out.
+ *
+ *     * DMMP_PATH_STATUS_DELAYED
+ *
+ *     If a path fails after being up for less than delay_watch_checks checks,
+ *     when it comes back up again, it will not be marked as up until it has
+ *     been up for delay_wait_checks checks. During this time, it is marked as
+ *     "delayed".
+ *
+ * @dmmp_p:
+ *     Pointer of 'struct dmmp_path'.
+ *     If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *     uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_status_get(struct dmmp_path *dmmp_p);
+
+/**
+ * dmmp_path_status_str() - Convert path status to string.
+ *
+ * Convert path status uint32_t to string (const char *):
+ *
+ *     * DMMP_PATH_STATUS_UNKNOWN -- "undef"
+ *
+ *     * DMMP_PATH_STATUS_DOWN -- "faulty"
+ *
+ *     * DMMP_PATH_STATUS_UP -- "ready"
+ *
+ *     * DMMP_PATH_STATUS_SHAKY -- "shaky"
+ *
+ *     * DMMP_PATH_STATUS_GHOST -- "ghost"
+ *
+ *     * DMMP_PATH_STATUS_PENDING -- "pending"
+ *
+ *     * DMMP_PATH_STATUS_TIMEOUT -- "timeout"
+ *
+ *     * DMMP_PATH_STATUS_REMOVED -- "removed"
+ *
+ *     * DMMP_PATH_STATUS_DELAYED -- "delayed"
+ *
+ * @path_status:
+ *     uint32_t. Path status.
+ *     When provided value is not a valid path status, return
+ *     "Invalid argument".
+ *
+ * Return:
+ *     const char *. The meaning of status value.
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_status_str(uint32_t path_status);
+
+#ifdef __cplusplus
+} /* End of extern "C" */
+#endif
+
+#endif /* End of _LIB_DMMP_H_ */
diff --git a/libdmmp/libdmmp_misc.c b/libdmmp/libdmmp_misc.c
new file mode 100644 (file)
index 0000000..27f1161
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_LOG_STRERR_ALIGN_WIDTH   80
+/* ^ Only used in _dmmp_log_stderr() for pretty log output.
+ *   When provided log message is less than 80 bytes, fill it with space, then
+ *   print code file name, function name, line after the 80th bytes.
+ */
+
+static const struct _num_str_conv _DMMP_RC_MSG_CONV[] = {
+       {DMMP_OK, "OK"},
+       {DMMP_ERR_NO_MEMORY, "Out of memory"},
+       {DMMP_ERR_BUG, "BUG of libdmmp library"},
+       {DMMP_ERR_IPC_TIMEOUT, "Timeout when communicate with multipathd, "
+                              "try to increase it via "
+                               "dmmp_context_timeout_set()"},
+       {DMMP_ERR_IPC_ERROR, "Error when communicate with multipathd daemon"},
+       {DMMP_ERR_NO_DAEMON, "The multipathd daemon not started"},
+       {DMMP_ERR_INCOMPATIBLE, "Incompatible multipathd daemon version"},
+};
+
+_dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG_CONV);
+
+static const struct _num_str_conv _DMMP_PRI_CONV[] = {
+       {DMMP_LOG_PRIORITY_DEBUG, "DEBUG"},
+       {DMMP_LOG_PRIORITY_INFO, "INFO"},
+       {DMMP_LOG_PRIORITY_WARNING, "WARNING"},
+       {DMMP_LOG_PRIORITY_ERROR, "ERROR"},
+};
+_dmmp_str_func_gen(dmmp_log_priority_str, int, priority, _DMMP_PRI_CONV);
+
+void _dmmp_log_stderr(struct dmmp_context *ctx, int priority,
+                     const char *file, int line, const char *func_name,
+                     const char *format, va_list args)
+{
+       int printed_bytes = 0;
+       void *userdata = NULL;
+
+       printed_bytes += fprintf(stderr, "libdmmp %s: ",
+                                dmmp_log_priority_str(priority));
+       printed_bytes += vfprintf(stderr, format, args);
+
+       userdata = dmmp_context_userdata_get(ctx);
+       if (userdata != NULL)
+               fprintf(stderr, "(userdata address: %p)",
+                       userdata);
+       /* ^ Just demonstrate how userdata could be used and
+        *   bypass clang static analyzer about unused ctx argument warning
+        */
+
+       if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) {
+               fprintf(stderr, "%*s # %s:%s():%d\n",
+                       _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file,
+                       func_name, line);
+       } else {
+               fprintf(stderr, " # %s:%s():%d\n", file, func_name, line);
+       }
+}
diff --git a/libdmmp/libdmmp_mp.c b/libdmmp/libdmmp_mp.c
new file mode 100644 (file)
index 0000000..bc48d0e
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+struct dmmp_mpath {
+       char *wwid;
+       char *alias;
+       uint32_t dmmp_pg_count;
+       struct dmmp_path_group **dmmp_pgs;
+       char *kdev_name;
+};
+
+_dmmp_getter_func_gen(dmmp_mpath_name_get, struct dmmp_mpath, dmmp_mp,
+                     alias, const char *);
+_dmmp_getter_func_gen(dmmp_mpath_wwid_get, struct dmmp_mpath, dmmp_mp,
+                     wwid, const char *);
+_dmmp_getter_func_gen(dmmp_mpath_kdev_name_get, struct dmmp_mpath, dmmp_mp,
+                     kdev_name, const char *);
+
+struct dmmp_mpath *_dmmp_mpath_new(void)
+{
+       struct dmmp_mpath *dmmp_mp = NULL;
+
+       dmmp_mp = (struct dmmp_mpath *) malloc(sizeof(struct dmmp_mpath));
+
+       if (dmmp_mp != NULL) {
+               dmmp_mp->wwid = NULL;
+               dmmp_mp->alias = NULL;
+               dmmp_mp->dmmp_pg_count = 0;
+               dmmp_mp->dmmp_pgs = NULL;
+       }
+       return dmmp_mp;
+}
+
+int _dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp,
+                      json_object *j_obj_map)
+{
+       int rc = DMMP_OK;
+       const char *wwid = NULL;
+       const char *alias = NULL;
+       struct array_list *ar_pgs = NULL;
+       int ar_pgs_len = -1;
+       uint32_t i = 0;
+       struct dmmp_path_group *dmmp_pg = NULL;
+       const char *kdev_name = NULL;
+
+       assert(ctx != NULL);
+       assert(dmmp_mp != NULL);
+       assert(j_obj_map != NULL);
+
+       _json_obj_get_value(ctx, j_obj_map, wwid, "uuid", json_type_string,
+                           json_object_get_string, rc, out);
+       _json_obj_get_value(ctx, j_obj_map, alias, "name", json_type_string,
+                           json_object_get_string, rc, out);
+       _json_obj_get_value(ctx, j_obj_map, kdev_name, "sysfs",
+                           json_type_string, json_object_get_string, rc, out);
+
+       _dmmp_null_or_empty_str_check(ctx, wwid, rc, out);
+       _dmmp_null_or_empty_str_check(ctx, alias, rc, out);
+
+       dmmp_mp->wwid = strdup(wwid);
+       _dmmp_alloc_null_check(ctx, dmmp_mp->wwid, rc, out);
+       dmmp_mp->alias = strdup(alias);
+       _dmmp_alloc_null_check(ctx, dmmp_mp->alias, rc, out);
+       dmmp_mp->kdev_name = strdup(kdev_name);
+       _dmmp_alloc_null_check(ctx, dmmp_mp->kdev_name, rc, out);
+
+       _json_obj_get_value(ctx, j_obj_map, ar_pgs, "path_groups",
+                           json_type_array, json_object_get_array, rc, out);
+       ar_pgs_len = array_list_length(ar_pgs);
+       if (ar_pgs_len < 0) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: Got negative length for ar_pgs");
+               goto out;
+       }
+       else if (ar_pgs_len == 0)
+               goto out;
+       else
+               dmmp_mp->dmmp_pg_count = ar_pgs_len & UINT32_MAX;
+
+       dmmp_mp->dmmp_pgs = (struct dmmp_path_group **)
+               malloc(sizeof(struct dmmp_path_group *) *
+                      dmmp_mp->dmmp_pg_count);
+       _dmmp_alloc_null_check(ctx, dmmp_mp->dmmp_pgs, rc, out);
+       for (; i < dmmp_mp->dmmp_pg_count; ++i)
+               dmmp_mp->dmmp_pgs[i] = NULL;
+
+       for (i = 0; i < dmmp_mp->dmmp_pg_count; ++i) {
+               dmmp_pg = _dmmp_path_group_new();
+               _dmmp_alloc_null_check(ctx, dmmp_pg, rc, out);
+               dmmp_mp->dmmp_pgs[i] = dmmp_pg;
+               _good(_dmmp_path_group_update(ctx, dmmp_pg,
+                                             array_list_get_idx(ar_pgs, i)),
+                     rc, out);
+       }
+
+       _debug(ctx, "Got mpath wwid: '%s', alias: '%s'", dmmp_mp->wwid,
+              dmmp_mp->alias);
+
+out:
+       if (rc != DMMP_OK)
+               _dmmp_mpath_free(dmmp_mp);
+       return rc;
+}
+
+void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp)
+{
+       if (dmmp_mp == NULL)
+               return ;
+
+       free((char *) dmmp_mp->alias);
+       free((char *) dmmp_mp->wwid);
+       free((char *) dmmp_mp->kdev_name);
+
+       if (dmmp_mp->dmmp_pgs != NULL)
+               _dmmp_path_group_array_free(dmmp_mp->dmmp_pgs,
+                                           dmmp_mp->dmmp_pg_count);
+
+       free(dmmp_mp);
+}
+
+void dmmp_path_group_array_get(struct dmmp_mpath *dmmp_mp,
+                              struct dmmp_path_group ***dmmp_pgs,
+                              uint32_t *dmmp_pg_count)
+{
+       assert(dmmp_mp != NULL);
+       assert(dmmp_pgs != NULL);
+       assert(dmmp_pg_count != NULL);
+
+       *dmmp_pgs = dmmp_mp->dmmp_pgs;
+       *dmmp_pg_count = dmmp_mp->dmmp_pg_count;
+}
diff --git a/libdmmp/libdmmp_path.c b/libdmmp/libdmmp_path.c
new file mode 100644 (file)
index 0000000..47a2162
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_SHOW_PS_INDEX_BLK_NAME   0
+#define _DMMP_SHOW_PS_INDEX_SATAUS     1
+#define _DMMP_SHOW_PS_INDEX_WWID       2
+#define _DMMP_SHOW_PS_INDEX_PGID       3
+
+struct dmmp_path {
+       char *blk_name;
+       uint32_t status;
+};
+
+static const struct _num_str_conv _DMMP_PATH_STATUS_CONV[] = {
+       {DMMP_PATH_STATUS_UNKNOWN, "undef"},
+       {DMMP_PATH_STATUS_UP, "ready"},
+       {DMMP_PATH_STATUS_DOWN, "faulty"},
+       {DMMP_PATH_STATUS_SHAKY, "shaky"},
+       {DMMP_PATH_STATUS_GHOST, "ghost"},
+       {DMMP_PATH_STATUS_PENDING, "i/o pending"},
+       {DMMP_PATH_STATUS_TIMEOUT, "i/o timeout"},
+       {DMMP_PATH_STATUS_DELAYED, "delayed"},
+};
+
+_dmmp_str_func_gen(dmmp_path_status_str, uint32_t, path_status,
+                  _DMMP_PATH_STATUS_CONV);
+_dmmp_str_conv_func_gen(_dmmp_path_status_str_conv, ctx, path_status_str,
+                       uint32_t, DMMP_PATH_STATUS_UNKNOWN,
+                       _DMMP_PATH_STATUS_CONV);
+
+_dmmp_getter_func_gen(dmmp_path_blk_name_get, struct dmmp_path, dmmp_p,
+                     blk_name, const char *);
+_dmmp_getter_func_gen(dmmp_path_status_get, struct dmmp_path, dmmp_p,
+                     status, uint32_t);
+
+struct dmmp_path *_dmmp_path_new(void)
+{
+       struct dmmp_path *dmmp_p = NULL;
+
+       dmmp_p = (struct dmmp_path *) malloc(sizeof(struct dmmp_path));
+
+       if (dmmp_p != NULL) {
+               dmmp_p->blk_name = NULL;
+               dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN;
+       }
+       return dmmp_p;
+}
+
+int _dmmp_path_update(struct dmmp_context *ctx, struct dmmp_path *dmmp_p,
+                     json_object *j_obj_p)
+{
+       int rc = DMMP_OK;
+       const char *blk_name = NULL;
+       const char *status_str = NULL;
+
+       assert(ctx != NULL);
+       assert(dmmp_p != NULL);
+       assert(j_obj_p != NULL);
+
+       _json_obj_get_value(ctx, j_obj_p, blk_name, "dev",
+                           json_type_string, json_object_get_string, rc, out);
+       _json_obj_get_value(ctx, j_obj_p, status_str, "chk_st",
+                           json_type_string, json_object_get_string, rc, out);
+
+       _dmmp_null_or_empty_str_check(ctx, blk_name, rc, out);
+       _dmmp_null_or_empty_str_check(ctx, status_str, rc, out);
+
+       dmmp_p->blk_name = strdup(blk_name);
+       _dmmp_alloc_null_check(ctx, dmmp_p->blk_name, rc, out);
+
+       dmmp_p->status = _dmmp_path_status_str_conv(ctx, status_str);
+
+       _debug(ctx, "Got path blk_name: '%s'", dmmp_p->blk_name);
+       _debug(ctx, "Got path status: %s(%" PRIu32 ")",
+              dmmp_path_status_str(dmmp_p->status), dmmp_p->status);
+
+out:
+       if (rc != DMMP_OK)
+               _dmmp_path_free(dmmp_p);
+       return rc;
+}
+
+void _dmmp_path_free(struct dmmp_path *dmmp_p)
+{
+       if (dmmp_p == NULL)
+               return;
+       free(dmmp_p->blk_name);
+       free(dmmp_p);
+}
diff --git a/libdmmp/libdmmp_pg.c b/libdmmp/libdmmp_pg.c
new file mode 100644 (file)
index 0000000..5149161
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_SHOW_PGS_CMD "show groups raw format %w|%g|%p|%t|%s"
+#define _DMMP_SHOW_PG_INDEX_WWID       0
+#define _DMMP_SHOW_PG_INDEX_PG_ID      1
+#define _DMMP_SHOW_PG_INDEX_PRI                2
+#define _DMMP_SHOW_PG_INDEX_STATUS     3
+#define _DMMP_SHOW_PG_INDEX_SELECTOR   4
+
+struct dmmp_path_group {
+       uint32_t id;
+       /* ^ pgindex of struct path, will be used for path group switch */
+       uint32_t status;
+       uint32_t priority;
+       char *selector;
+       uint32_t dmmp_p_count;
+       struct dmmp_path **dmmp_ps;
+};
+
+static const struct _num_str_conv _DMMP_PATH_GROUP_STATUS_CONV[] = {
+       {DMMP_PATH_GROUP_STATUS_UNKNOWN, "undef"},
+       {DMMP_PATH_GROUP_STATUS_ACTIVE, "active"},
+       {DMMP_PATH_GROUP_STATUS_DISABLED, "disabled"},
+       {DMMP_PATH_GROUP_STATUS_ENABLED, "enabled"},
+};
+
+_dmmp_str_func_gen(dmmp_path_group_status_str, uint32_t, pg_status,
+                  _DMMP_PATH_GROUP_STATUS_CONV);
+_dmmp_str_conv_func_gen(_dmmp_path_group_status_str_conv, ctx, pg_status_str,
+                       uint32_t, DMMP_PATH_GROUP_STATUS_UNKNOWN,
+                       _DMMP_PATH_GROUP_STATUS_CONV);
+
+_dmmp_getter_func_gen(dmmp_path_group_id_get, struct dmmp_path_group, dmmp_pg,
+                     id, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_status_get, struct dmmp_path_group,
+                     dmmp_pg, status, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_priority_get, struct dmmp_path_group,
+                     dmmp_pg, priority, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_selector_get, struct dmmp_path_group,
+                     dmmp_pg, selector, const char *);
+_dmmp_array_free_func_gen(_dmmp_path_group_array_free, struct dmmp_path_group,
+                         _dmmp_path_group_free);
+
+
+struct dmmp_path_group *_dmmp_path_group_new(void)
+{
+       struct dmmp_path_group *dmmp_pg = NULL;
+
+       dmmp_pg = (struct dmmp_path_group *)
+               malloc(sizeof(struct dmmp_path_group));
+
+       if (dmmp_pg != NULL) {
+               dmmp_pg->id = _DMMP_PATH_GROUP_ID_UNKNOWN;
+               dmmp_pg->status = DMMP_PATH_GROUP_STATUS_UNKNOWN;
+               dmmp_pg->priority = 0;
+               dmmp_pg->selector = NULL;
+               dmmp_pg->dmmp_p_count = 0;
+               dmmp_pg->dmmp_ps = NULL;
+       }
+       return dmmp_pg;
+}
+int _dmmp_path_group_update(struct dmmp_context *ctx,
+                           struct dmmp_path_group *dmmp_pg,
+                           json_object *j_obj_pg)
+{
+       int rc = DMMP_OK;
+       uint32_t id = 0;
+       int priority_int = -1 ;
+       const char *status_str = NULL;
+       const char *selector = NULL;
+       struct array_list *ar_ps = NULL;
+       int ar_ps_len = -1;
+       uint32_t i = 0;
+       struct dmmp_path *dmmp_p = NULL;
+
+       assert(ctx != NULL);
+       assert(dmmp_pg != NULL);
+       assert(j_obj_pg != NULL);
+
+       _json_obj_get_value(ctx, j_obj_pg, status_str, "dm_st",
+                           json_type_string, json_object_get_string, rc, out);
+
+       _json_obj_get_value(ctx, j_obj_pg, selector, "selector",
+                           json_type_string, json_object_get_string, rc, out);
+
+       _json_obj_get_value(ctx, j_obj_pg, priority_int, "pri",
+                           json_type_int, json_object_get_int, rc, out);
+
+       _json_obj_get_value(ctx, j_obj_pg, id, "group",
+                           json_type_int, json_object_get_int, rc, out);
+
+       dmmp_pg->priority = (priority_int <= 0) ? 0 : priority_int & UINT32_MAX;
+
+       _dmmp_null_or_empty_str_check(ctx, status_str, rc, out);
+       _dmmp_null_or_empty_str_check(ctx, selector, rc, out);
+
+       dmmp_pg->selector = strdup(selector);
+       _dmmp_alloc_null_check(ctx, dmmp_pg->selector, rc, out);
+
+       dmmp_pg->id = id;
+
+       if (dmmp_pg->id == _DMMP_PATH_GROUP_ID_UNKNOWN) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: Got unknown(%d) path group ID",
+                      _DMMP_PATH_GROUP_ID_UNKNOWN);
+               goto out;
+       }
+
+       dmmp_pg->status = _dmmp_path_group_status_str_conv(ctx, status_str);
+
+       _json_obj_get_value(ctx, j_obj_pg, ar_ps, "paths",
+                           json_type_array, json_object_get_array, rc, out);
+
+       ar_ps_len = array_list_length(ar_ps);
+       if (ar_ps_len < 0) {
+               rc = DMMP_ERR_BUG;
+               _error(ctx, "BUG: Got negative length for ar_ps");
+               goto out;
+       }
+       else if (ar_ps_len == 0)
+               goto out;
+       else
+               dmmp_pg->dmmp_p_count = ar_ps_len & UINT32_MAX;
+
+       dmmp_pg->dmmp_ps = (struct dmmp_path **)
+               malloc(sizeof(struct dmmp_path *) * dmmp_pg->dmmp_p_count);
+       _dmmp_alloc_null_check(ctx, dmmp_pg->dmmp_ps, rc, out);
+       for (; i < dmmp_pg->dmmp_p_count; ++i)
+               dmmp_pg->dmmp_ps[i] = NULL;
+
+       for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) {
+               dmmp_p = _dmmp_path_new();
+               _dmmp_alloc_null_check(ctx, dmmp_p, rc, out);
+               dmmp_pg->dmmp_ps[i] = dmmp_p;
+               _good(_dmmp_path_update(ctx, dmmp_p,
+                                       array_list_get_idx(ar_ps, i)),
+                     rc, out);
+       }
+
+       _debug(ctx, "Got path group id: %" PRIu32 "", dmmp_pg->id);
+       _debug(ctx, "Got path group priority: %" PRIu32 "", dmmp_pg->priority);
+       _debug(ctx, "Got path group status: %s(%" PRIu32 ")",
+              dmmp_path_group_status_str(dmmp_pg->status), dmmp_pg->status);
+       _debug(ctx, "Got path group selector: '%s'", dmmp_pg->selector);
+
+out:
+       if (rc != DMMP_OK)
+               _dmmp_path_group_free(dmmp_pg);
+       return rc;
+}
+
+void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg)
+{
+       uint32_t i = 0;
+
+       if (dmmp_pg == NULL)
+               return;
+
+       free((char *) dmmp_pg->selector);
+
+       if (dmmp_pg->dmmp_ps != NULL) {
+               for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) {
+                       _dmmp_path_free(dmmp_pg->dmmp_ps[i]);
+               }
+               free(dmmp_pg->dmmp_ps);
+       }
+       free(dmmp_pg);
+}
+
+void dmmp_path_array_get(struct dmmp_path_group *mp_pg,
+                        struct dmmp_path ***mp_paths,
+                        uint32_t *dmmp_p_count)
+{
+       assert(mp_pg != NULL);
+       assert(mp_paths != NULL);
+       assert(dmmp_p_count != NULL);
+
+       *mp_paths = mp_pg->dmmp_ps;
+       *dmmp_p_count = mp_pg->dmmp_p_count;
+}
diff --git a/libdmmp/libdmmp_private.h b/libdmmp/libdmmp_private.h
new file mode 100644 (file)
index 0000000..3e813cb
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#ifndef _LIB_DMMP_PRIVATE_H_
+#define _LIB_DMMP_PRIVATE_H_
+
+/*
+ * Notes:
+ *     Internal/Private functions does not check input argument but using
+ *     assert() to abort if NULL pointer found in argument.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _good(rc, rc_val, out) \
+       do { \
+               rc_val = rc; \
+               if (rc_val != DMMP_OK) \
+                       goto out; \
+       } while(0)
+
+#define _DMMP_PATH_GROUP_ID_UNKNOWN    0
+
+struct DMMP_DLL_LOCAL _num_str_conv;
+struct _num_str_conv {
+       const uint32_t value;
+       const char *str;
+};
+
+#define _dmmp_str_func_gen(func_name, var_type, var, conv_array) \
+const char *func_name(var_type var) { \
+       size_t i = 0; \
+       uint32_t tmp_var = var & UINT32_MAX; \
+       /* In the whole libdmmp, we don't have negative value */ \
+       for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \
+               if ((conv_array[i].value) == tmp_var) \
+                       return conv_array[i].str; \
+       } \
+       return "Invalid argument"; \
+}
+
+#define _dmmp_str_conv_func_gen(func_name, ctx, var_name, out_type, \
+                               unknown_value, conv_array) \
+static out_type func_name(struct dmmp_context *ctx, const char *var_name) { \
+       size_t i = 0; \
+       for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \
+               if (strcmp(conv_array[i].str, var_name) == 0) \
+                       return conv_array[i].value; \
+       } \
+       _warn(ctx, "Got unknown " #var_name ": '%s'", var_name); \
+       return unknown_value; \
+}
+
+#define _json_obj_get_value(ctx, j_obj, out_value, key, value_type, \
+                           value_func, rc, out) \
+do { \
+       json_type j_type = json_type_null; \
+       json_object *j_obj_tmp = NULL; \
+       if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != TRUE) { \
+               _error(ctx, "Invalid JSON output from multipathd IPC: " \
+                      "key '%s' not found", key); \
+               rc = DMMP_ERR_IPC_ERROR; \
+               goto out; \
+       } \
+       if (j_obj_tmp == NULL) { \
+               _error(ctx, "BUG: Got NULL j_obj_tmp from " \
+                      "json_object_object_get_ex() while it return TRUE"); \
+               rc = DMMP_ERR_BUG; \
+               goto out; \
+       } \
+       j_type = json_object_get_type(j_obj_tmp); \
+       if (j_type != value_type) { \
+               _error(ctx, "Invalid value type for key'%s' of JSON output " \
+                      "from multipathd IPC. Should be %s(%d), " \
+                      "but got %s(%d)", key, json_type_to_name(value_type), \
+                      value_type, json_type_to_name(j_type), j_type); \
+               rc = DMMP_ERR_IPC_ERROR; \
+               goto out; \
+       } \
+       out_value = value_func(j_obj_tmp); \
+} while(0);
+
+DMMP_DLL_LOCAL int _dmmp_ipc_exec(struct dmmp_context *ctx, const char *cmd,
+                                 char **output);
+
+DMMP_DLL_LOCAL struct dmmp_mpath *_dmmp_mpath_new(void);
+DMMP_DLL_LOCAL struct dmmp_path_group *_dmmp_path_group_new(void);
+DMMP_DLL_LOCAL struct dmmp_path *_dmmp_path_new(void);
+
+DMMP_DLL_LOCAL int _dmmp_mpath_update(struct dmmp_context *ctx,
+                                     struct dmmp_mpath *dmmp_mp,
+                                     json_object *j_obj_map);
+DMMP_DLL_LOCAL int _dmmp_path_group_update(struct dmmp_context *ctx,
+                                          struct dmmp_path_group *dmmp_pg,
+                                          json_object *j_obj_pg);
+DMMP_DLL_LOCAL int _dmmp_path_update(struct dmmp_context *ctx,
+                                    struct dmmp_path *dmmp_p,
+                                    json_object *j_obj_p);
+
+DMMP_DLL_LOCAL void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp);
+DMMP_DLL_LOCAL void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg);
+DMMP_DLL_LOCAL void _dmmp_path_group_array_free
+       (struct dmmp_path_group **dmmp_pgs, uint32_t dmmp_pg_count);
+DMMP_DLL_LOCAL void _dmmp_path_free(struct dmmp_path *dmmp_p);
+DMMP_DLL_LOCAL void _dmmp_log(struct dmmp_context *ctx, int priority,
+                             const char *file, int line,
+                             const char *func_name,
+                             const char *format, ...);
+DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc);
+
+DMMP_DLL_LOCAL void _dmmp_log_stderr(struct dmmp_context *ctx, int priority,
+                                    const char *file, int line,
+                                    const char *func_name, const char *format,
+                                    va_list args);
+
+
+#define _dmmp_log_cond(ctx, prio, arg...) \
+       do { \
+               if (dmmp_context_log_priority_get(ctx) >= prio) \
+                       _dmmp_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, \
+                                 ## arg); \
+       } while (0)
+
+#define _debug(ctx, arg...) \
+       _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_DEBUG, ## arg)
+#define _info(ctx, arg...) \
+       _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_INFO, ## arg)
+#define _warn(ctx, arg...) \
+       _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_WARNING, ## arg)
+#define _error(ctx, arg...) \
+       _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_ERROR, ## arg)
+
+/*
+ * Check pointer returned by malloc() or strdup(), if NULL, set
+ * rc as DMMP_ERR_NO_MEMORY, report error and goto goto_out.
+ */
+#define _dmmp_alloc_null_check(ctx, ptr, rc, goto_out) \
+       do { \
+               if (ptr == NULL) { \
+                       rc = DMMP_ERR_NO_MEMORY; \
+                       _error(ctx, dmmp_strerror(rc)); \
+                       goto goto_out; \
+               } \
+       } while(0)
+
+#define _dmmp_null_or_empty_str_check(ctx, var, rc, goto_out) \
+       do { \
+               if (var == NULL) { \
+                       rc = DMMP_ERR_BUG; \
+                       _error(ctx, "BUG: Got NULL " #var); \
+                       goto goto_out; \
+               } \
+               if (strlen(var) == 0) { \
+                       rc = DMMP_ERR_BUG; \
+                       _error(ctx, "BUG: Got empty " #var); \
+                       goto goto_out; \
+               } \
+       } while(0)
+
+#define _dmmp_getter_func_gen(func_name, struct_name, struct_data, \
+                             prop_name, prop_type) \
+       prop_type func_name(struct_name *struct_data) \
+       { \
+               assert(struct_data != NULL); \
+               return struct_data->prop_name; \
+       }
+
+#define _dmmp_array_free_func_gen(func_name, struct_name, struct_free_func) \
+       void func_name(struct_name **ptr_array, uint32_t ptr_count) \
+       { \
+               uint32_t i = 0; \
+               if (ptr_array == NULL) \
+                       return; \
+               for (; i < ptr_count; ++i) \
+                       struct_free_func(ptr_array[i]); \
+               free(ptr_array); \
+       }
+
+#ifdef __cplusplus
+} /* End of extern "C" */
+#endif
+
+#endif /* End of _LIB_DMMP_PRIVATE_H_ */
diff --git a/libdmmp/test/Makefile b/libdmmp/test/Makefile
new file mode 100644 (file)
index 0000000..acfb3bf
--- /dev/null
@@ -0,0 +1,30 @@
+# Makefile
+#
+# Copyright (C) 2015-2016 Gris Ge <fge@redhat.com>
+#
+include ../../Makefile.inc
+
+_libdmmpdir=../$(libdmmpdir)
+_mpathcmddir=../$(mpathcmddir)
+
+TEST_EXEC = libdmmp_test
+SPD_TEST_EXEC = libdmmp_speed_test
+CFLAGS += -I$(_libdmmpdir)
+LDFLAGS += -L$(_libdmmpdir) -ldmmp
+
+all: $(TEST_EXEC) $(SPD_TEST_EXEC)
+
+check: $(TEST_EXEC) $(SPD_TEST_EXEC)
+       sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \
+               valgrind --quiet --leak-check=full \
+               --show-reachable=no --show-possibly-lost=no \
+               --trace-children=yes --error-exitcode=1 \
+               ./$(TEST_EXEC)
+       $(MAKE) speed_test
+
+speed_test: $(SPD_TEST_EXEC)
+       sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \
+               time -p ./$(SPD_TEST_EXEC)
+
+clean:
+       rm -f $(TEST_EXEC) $(SPD_TEST_EXEC)
diff --git a/libdmmp/test/libdmmp_speed_test.c b/libdmmp/test/libdmmp_speed_test.c
new file mode 100644 (file)
index 0000000..372cd39
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015-2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <libdmmp/libdmmp.h>
+
+int main(int argc, char *argv[])
+{
+       struct dmmp_context *ctx = NULL;
+       struct dmmp_mpath **dmmp_mps = NULL;
+       uint32_t dmmp_mp_count = 0;
+       int rc = EXIT_SUCCESS;
+
+       ctx = dmmp_context_new();
+       dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_WARNING);
+
+       if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) {
+               printf("FAILED\n");
+               rc = EXIT_FAILURE;
+       } else {
+               printf("Got %" PRIu32 " mpath\n", dmmp_mp_count);
+               dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+       }
+       dmmp_context_free(ctx);
+       exit(rc);
+}
diff --git a/libdmmp/test/libdmmp_test.c b/libdmmp/test/libdmmp_test.c
new file mode 100644 (file)
index 0000000..00b40e9
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015-2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <libdmmp/libdmmp.h>
+
+#define FAIL(rc, out, ...) \
+       do { \
+               rc = EXIT_FAILURE; \
+               fprintf(stderr, "FAIL: "__VA_ARGS__ ); \
+               goto out; \
+       } while(0)
+#define PASS(...) fprintf(stdout, "PASS: "__VA_ARGS__ );
+#define FILE_NAME_SIZE 256
+#define TMO 10000      /* Forcing timeout to 10 seconds */
+
+int test_paths(struct dmmp_path_group *mp_pg)
+{
+       struct dmmp_path **mp_ps = NULL;
+       uint32_t mp_p_count = 0;
+       uint32_t i = 0;
+       const char *blk_name = NULL;
+       int rc = EXIT_SUCCESS;
+
+       dmmp_path_array_get(mp_pg, &mp_ps, &mp_p_count);
+       if (mp_p_count == 0)
+               FAIL(rc, out, "dmmp_path_array_get(): Got no path\n");
+       for (i = 0; i < mp_p_count; ++i) {
+               blk_name = dmmp_path_blk_name_get(mp_ps[i]);
+               if (blk_name == NULL)
+                       FAIL(rc, out, "dmmp_path_blk_name_get(): Got NULL\n");
+               PASS("dmmp_path_blk_name_get(): %s\n", blk_name);
+               PASS("dmmp_path_status_get(): %" PRIu32 " -- %s\n",
+                    dmmp_path_status_get(mp_ps[i]),
+                    dmmp_path_status_str(dmmp_path_status_get(mp_ps[i])));
+       }
+out:
+       return rc;
+}
+
+int test_path_groups(struct dmmp_mpath *dmmp_mp)
+{
+       struct dmmp_path_group **dmmp_pgs = NULL;
+       uint32_t dmmp_pg_count = 0;
+       uint32_t i = 0;
+       int rc = EXIT_SUCCESS;
+
+       dmmp_path_group_array_get(dmmp_mp, &dmmp_pgs, &dmmp_pg_count);
+       if ((dmmp_pg_count == 0) && (dmmp_pgs != NULL))
+               FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is not NULL "
+                    "but mp_pg_count is 0\n");
+       if ((dmmp_pg_count != 0) && (dmmp_pgs == NULL))
+               FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is NULL "
+                    "but mp_pg_count is not 0\n");
+       if (dmmp_pg_count == 0)
+               FAIL(rc, out, "dmmp_path_group_array_get(): "
+                    "Got 0 path group\n");
+
+       PASS("dmmp_path_group_array_get(): Got %" PRIu32 " path groups\n",
+            dmmp_pg_count);
+
+       for (i = 0; i < dmmp_pg_count; ++i) {
+               PASS("dmmp_path_group_id_get(): %" PRIu32 "\n",
+                    dmmp_path_group_id_get(dmmp_pgs[i]));
+               PASS("dmmp_path_group_priority_get(): %" PRIu32 "\n",
+                    dmmp_path_group_priority_get(dmmp_pgs[i]));
+               PASS("dmmp_path_group_status_get(): %" PRIu32 " -- %s\n",
+                    dmmp_path_group_status_get(dmmp_pgs[i]),
+                    dmmp_path_group_status_str
+                       (dmmp_path_group_status_get(dmmp_pgs[i])));
+               PASS("dmmp_path_group_selector_get(): %s\n",
+                    dmmp_path_group_selector_get(dmmp_pgs[i]));
+               rc = test_paths(dmmp_pgs[i]);
+               if (rc != 0)
+                       goto out;
+       }
+out:
+       return rc;
+}
+
+int main(int argc, char *argv[])
+{
+       struct dmmp_context *ctx = NULL;
+       struct dmmp_mpath **dmmp_mps = NULL;
+       uint32_t dmmp_mp_count = 0;
+       const char *name = NULL;
+       const char *wwid = NULL;
+       const char *kdev = NULL;
+       uint32_t i = 0;
+       int rc = EXIT_SUCCESS;
+
+       ctx = dmmp_context_new();
+       dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG);
+       dmmp_context_userdata_set(ctx, ctx);
+       dmmp_context_userdata_set(ctx, NULL);
+       dmmp_context_timeout_set(ctx, TMO);
+       if (dmmp_context_timeout_get(ctx) != TMO)
+               FAIL(rc, out, "dmmp_context_timeout_set(): Failed to set "
+                    "timeout to %u", TMO);
+
+       if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0)
+               FAIL(rc, out, "dmmp_mpath_array_get(): rc != 0\n");
+       if (dmmp_mp_count == 0)
+               FAIL(rc, out, "dmmp_mpath_array_get(): "
+                    "Got no multipath devices\n");
+       PASS("dmmp_mpath_array_get(): Got %" PRIu32 " mpath\n", dmmp_mp_count);
+       for (i = 0; i < dmmp_mp_count; ++i) {
+               name = dmmp_mpath_name_get(dmmp_mps[i]);
+               wwid = dmmp_mpath_wwid_get(dmmp_mps[i]);
+               kdev = dmmp_mpath_kdev_name_get(dmmp_mps[i]);
+               if ((name == NULL) ||(wwid == NULL) || (kdev == NULL))
+                       FAIL(rc, out,
+                            "dmmp_mpath_array_get(): Got NULL name or wwid");
+               PASS("dmmp_mpath_array_get(): Got mpath(%s): %s %s\n",
+                    kdev, name, wwid);
+               rc = test_path_groups(dmmp_mps[i]);
+               if (rc != 0)
+                       goto out;
+       }
+       dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+out:
+       dmmp_context_free(ctx);
+       exit(rc);
+}
index 457c4ca..9cda94c 100644 (file)
@@ -9,20 +9,20 @@ OBJS = mpath_cmd.o
 all: $(LIBS)
 
 $(LIBS): $(OBJS)
-       $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS)
+       $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
        $(LN) $@ $(DEVLIB)
 
 install: $(LIBS)
        $(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir)
        $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
        $(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB)
-       $(INSTALL_PROGRAM) -d $(DESTDIR)$(incdir)
-       $(INSTALL_PROGRAM) -m 644 mpath_cmd.h $(DESTDIR)$(incdir)
+       $(INSTALL_PROGRAM) -d $(DESTDIR)$(includedir)
+       $(INSTALL_PROGRAM) -m 644 mpath_cmd.h $(DESTDIR)$(includedir)
 
 uninstall:
        $(RM) $(DESTDIR)$(syslibdir)/$(LIBS)
        $(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
-       $(RM) $(DESTDIR)$(incdir)/mpath_cmd.h
+       $(RM) $(DESTDIR)$(includedir)/mpath_cmd.h
 
 clean:
        $(RM) core *.a *.o *.so *.so.* *.gz
index d9c5790..1496b68 100644 (file)
@@ -142,8 +142,10 @@ int mpath_recv_reply(int fd, char **reply, unsigned int timeout)
        len = mpath_recv_reply_len(fd, timeout);
        if (len <= 0)
                return len;
-       if (len > MAX_REPLY_LEN)
-               return -EINVAL;
+       if (len > MAX_REPLY_LEN) {
+               errno = EINVAL;
+               return -1;
+       }
        *reply = malloc(len);
        if (!*reply)
                return -1;
@@ -151,7 +153,7 @@ int mpath_recv_reply(int fd, char **reply, unsigned int timeout)
        if (err) {
                free(*reply);
                *reply = NULL;
-               return err;
+               return -1;
        }
        return 0;
 }
index 7293d91..6534474 100644 (file)
@@ -25,7 +25,7 @@ extern "C" {
 #endif
 
 #define DEFAULT_SOCKET         "/org/kernel/linux/storage/multipathd"
-#define DEFAULT_REPLY_TIMEOUT  1000
+#define DEFAULT_REPLY_TIMEOUT  4000
 #define MAX_REPLY_LEN          65536
 
 
index 6e43427..857c8d8 100644 (file)
@@ -13,9 +13,8 @@ OBJS = mpath_persist.o mpath_updatepr.o mpath_pr_ioctl.o
 
 all: $(LIBS)
 
-$(LIBS):
-       $(CC) -c $(CFLAGS) *.c
-       $(CC) $(LDFLAGS) $(SHARED_FLAGS) $(LIBDEPS) -Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS)
+$(LIBS): $(OBJS)
+       $(CC) $(LDFLAGS) $(SHARED_FLAGS) $(LIBDEPS) -Wl,-soname=$@ -o $@ $(OBJS)
        $(LN) $(LIBS) $(DEVLIB)
        $(GZIP) mpath_persistent_reserve_in.3 > mpath_persistent_reserve_in.3.gz
        $(GZIP) mpath_persistent_reserve_out.3 > mpath_persistent_reserve_out.3.gz
@@ -25,17 +24,17 @@ install: $(LIBS)
        $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
        $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(syslibdir)
        $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(man3dir)
-       $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(incdir)
+       $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(includedir)
        $(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB)
        $(INSTALL_PROGRAM) -m 644 mpath_persistent_reserve_in.3.gz $(DESTDIR)$(man3dir)
        $(INSTALL_PROGRAM) -m 644 mpath_persistent_reserve_out.3.gz $(DESTDIR)$(man3dir)
-       $(INSTALL_PROGRAM) -m 644 mpath_persist.h $(DESTDIR)$(incdir)
+       $(INSTALL_PROGRAM) -m 644 mpath_persist.h $(DESTDIR)$(includedir)
 
 uninstall:
        $(RM) $(DESTDIR)$(syslibdir)/$(LIBS)
        $(RM) $(DESTDIR)$(man3dir)/mpath_persistent_reserve_in.3.gz
        $(RM) $(DESTDIR)$(man3dir)/mpath_persistent_reserve_out.3.gz
-       $(RM) $(DESTDIR)$(incdir)/mpath_persist.h
+       $(RM) $(DESTDIR)$(includedir)/mpath_persist.h
        $(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
 
 clean:
index faea2b7..982c795 100644 (file)
@@ -35,7 +35,6 @@
 #define __STDC_FORMAT_MACROS 1
 
 struct udev *udev;
-struct config *conf;
 
 struct config *
 mpath_lib_init (struct udev *udev)
@@ -79,6 +78,7 @@ updatepaths (struct multipath * mpp)
        int i, j;
        struct pathgroup * pgp;
        struct path * pp;
+       struct config *conf;
 
        if (!mpp->pg)
                return 0;
@@ -98,16 +98,24 @@ updatepaths (struct multipath * mpp)
                                        continue;
                                }
                                pp->mpp = mpp;
+                               conf = get_multipath_config();
                                pathinfo(pp, conf, DI_ALL);
+                               put_multipath_config(conf);
                                continue;
                        }
                        pp->mpp = mpp;
                        if (pp->state == PATH_UNCHECKED ||
-                                       pp->state == PATH_WILD)
+                                       pp->state == PATH_WILD) {
+                               conf = get_multipath_config();
                                pathinfo(pp, conf, DI_CHECKER);
+                               put_multipath_config(conf);
+                       }
 
-                       if (pp->priority == PRIO_UNDEF)
+                       if (pp->priority == PRIO_UNDEF) {
+                               conf = get_multipath_config();
                                pathinfo(pp, conf, DI_PRIO);
+                               put_multipath_config(conf);
+                       }
                }
        }
        return 0;
@@ -160,8 +168,11 @@ int mpath_persistent_reserve_in (int fd, int rq_servact,
        int map_present;
        int major, minor;
        int ret;
+       struct config *conf;
 
+       conf = get_multipath_config();
        conf->verbosity = verbose;
+       put_multipath_config(conf);
 
        if (fstat( fd, &info) != 0){
                condlog(0, "stat error %d", fd);
@@ -253,8 +264,11 @@ int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
        int j;
        unsigned char *keyp;
        uint64_t prkey;
+       struct config *conf;
 
+       conf = get_multipath_config();
        conf->verbosity = verbose;
+       put_multipath_config(conf);
 
        if (fstat( fd, &info) != 0){
                condlog(0, "stat error fd=%d", fd);
@@ -321,7 +335,9 @@ int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
                goto out1;
        }
 
+       conf = get_multipath_config();
        select_reservation_key(conf, mpp);
+       put_multipath_config(conf);
 
        switch(rq_servact)
        {
index fde0416..79de5b5 100644 (file)
@@ -13,7 +13,7 @@ extern "C" {
 #define MPATH_MAX_PARAM_LEN    8192
 
 #define MPATH_MX_TIDS          32        /* Max number of transport ids"*/
-#define MPATH_MX_TID_LEN       256       /* Max lenght of transport id */
+#define MPATH_MX_TID_LEN       256       /* Max length of transport id */
 
 /* PRIN Service Actions */
 #define MPATH_PRIN_RKEY_SA     0x00       /* READ KEYS SA*/
index 8b9ac3d..31b2fe6 100644 (file)
@@ -36,7 +36,7 @@ void decode_transport_id(struct prin_fulldescr *fdesc, unsigned char * p, int le
 int get_prin_length(int rq_servact);
 int mpath_isLittleEndian(void);
 
-extern unsigned int mpath_mx_alloc_len;
+unsigned int mpath_mx_alloc_len;
 
 int prout_do_scsi_ioctl(char * dev, int rq_servact, int rq_scope,
                unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy)
index 056c547..e6c2ded 100644 (file)
@@ -25,10 +25,6 @@ struct threadinfo {
        struct prout_param param;
 };
 
-
-extern struct config *conf;
-
-
 int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int noisy);
 int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope,
                unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy);
index 495cebe..1f5ec25 100644 (file)
@@ -9,7 +9,7 @@ LIBS = $(DEVLIB).$(SONAME)
 
 CFLAGS += -I$(mpathcmddir)
 
-LIBDEPS += -lpthread -ldl -ldevmapper -ludev -L$(mpathcmddir) -lmpathcmd
+LIBDEPS += -lpthread -ldl -ldevmapper -ludev -L$(mpathcmddir) -lmpathcmd -lurcu
 
 ifdef SYSTEMD
        CFLAGS += -DUSE_SYSTEMD=$(SYSTEMD)
@@ -20,27 +20,19 @@ ifdef SYSTEMD
        endif
 endif
 
-LIBDM_API_FLUSH = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_no_flush' /usr/include/libdevmapper.h)
-
-ifneq ($(strip $(LIBDM_API_FLUSH)),0)
+ifneq ($(call check_func,dm_task_no_flush,/usr/include/libdevmapper.h),0)
        CFLAGS += -DLIBDM_API_FLUSH -D_GNU_SOURCE
 endif
 
-LIBDM_API_COOKIE = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_set_cookie' /usr/include/libdevmapper.h)
-
-ifneq ($(strip $(LIBDM_API_COOKIE)),0)
+ifneq ($(call check_func,dm_task_set_cookie,/usr/include/libdevmapper.h),0)
        CFLAGS += -DLIBDM_API_COOKIE
 endif
 
-LIBUDEV_API_RECVBUF = $(shell grep -Ecs '^[a-z]*[[:space:]]+udev_monitor_set_receive_buffer_size' /usr/include/libudev.h)
-
-ifneq ($(strip $(LIBUDEV_API_RECVBUF)),0)
+ifneq ($(call check_func,udev_monitor_set_receive_buffer_size,/usr/include/libudev.h),0)
        CFLAGS += -DLIBUDEV_API_RECVBUF
 endif
 
-LIBDM_API_DEFERRED = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_deferred_remove' /usr/include/libdevmapper.h)
-
-ifneq ($(strip $(LIBDM_API_DEFERRED)),0)
+ifneq ($(call check_func,dm_task_deferred_remove,/usr/include/libdevmapper.h),0)
        CFLAGS += -DLIBDM_API_DEFERRED
 endif
 
@@ -55,7 +47,7 @@ OBJS = memory.o parser.o vector.o devmapper.o callout.o \
 all: $(LIBS)
 
 $(LIBS): $(OBJS)
-       $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS)
+       $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
        $(LN) $@ $(DEVLIB)
 
 install:
index 12afef8..fd6b7f9 100644 (file)
@@ -107,6 +107,7 @@ lookup_binding(FILE *f, char *map_wwid, char **map_alias, char *prefix)
 
        *map_alias = NULL;
 
+       rewind(f);
        while (fgets(buf, LINE_MAX, f)) {
                char *c, *alias, *wwid;
                int curr_id;
@@ -280,6 +281,13 @@ use_existing_alias (char *wwid, char *file, char *alias_old,
                goto out;
        }
 
+       id = lookup_binding(f, wwid, &alias, NULL);
+       if (alias) {
+               condlog(3, "Use existing binding [%s] for WWID [%s]",
+                       alias, wwid);
+               goto out;
+       }
+
        /* allocate the existing alias in the bindings file */
        id = scan_devname(alias_old, prefix);
        if (id <= 0)
index f6c4506..ee396e2 100644 (file)
@@ -13,8 +13,7 @@
 #include "config.h"
 #include "blacklist.h"
 
-extern int
-store_ble (vector blist, char * str, int origin)
+int store_ble(vector blist, char * str, int origin)
 {
        struct blentry * ble;
 
@@ -47,8 +46,7 @@ out:
 }
 
 
-extern int
-alloc_ble_device (vector blist)
+int alloc_ble_device(vector blist)
 {
        struct blentry_device * ble = MALLOC(sizeof(struct blentry_device));
 
@@ -63,8 +61,7 @@ alloc_ble_device (vector blist)
        return 0;
 }
 
-extern int
-set_ble_device (vector blist, char * vendor, char * product, int origin)
+int set_ble_device(vector blist, char * vendor, char * product, int origin)
 {
        struct blentry_device * ble;
 
@@ -184,12 +181,6 @@ setup_default_blist (struct config * conf)
        if (store_ble(conf->blist_devnode, str, ORIGIN_DEFAULT))
                return 1;
 
-       str = STRDUP("^nvme");
-       if (!str)
-               return 1;
-       if (store_ble(conf->blist_devnode, str, ORIGIN_DEFAULT))
-               return 1;
-
        str = STRDUP("(SCSI_IDENT_|ID_WWN)");
        if (!str)
                return 1;
@@ -336,6 +327,10 @@ _filter_path (struct config * conf, struct path * pp)
 {
        int r;
 
+       r = filter_property(conf, pp->udev);
+       if (r > 0)
+               return r;
+
        r = _filter_devnode(conf->blist_devnode, conf->elist_devnode,pp->dev);
        if (r > 0)
                return r;
index 4d1b067..dc18e02 100644 (file)
@@ -137,8 +137,7 @@ int execute_program(char *path, char *value, int len)
        return retval;
 }
 
-extern int
-apply_format (char * string, char * cmd, struct path * pp)
+int apply_format(char * string, char * cmd, struct path * pp)
 {
        char * pos;
        char * dst;
index fd999b0..05e024f 100644 (file)
@@ -213,7 +213,7 @@ void checker_put (struct checker * dst)
 
 void checker_repair (struct checker * c)
 {
-       if (!c || !checker_selected(c))
+       if (!checker_selected(c))
                return;
 
        c->message[0] = '\0';
index fedc330..1d225de 100644 (file)
@@ -112,8 +112,8 @@ struct checker {
                                                multipath-wide. Use MALLOC if
                                                you want to stuff data in. */
        int (*check)(struct checker *);
-       void (*repair)(struct checker *);     /* called if check returns
-                                               PATH_DOWN to bring path into
+       void (*repair)(struct checker *);    /* called if check returns
+                                               PATH_DOWN to bring path into
                                                usable state */
        int (*init)(struct checker *);       /* to allocate the context */
        void (*free)(struct checker *);      /* to free the context */
index 11ab76f..4970fc0 100644 (file)
@@ -13,8 +13,10 @@ LIBS= \
        libcheckdirectio.so \
        libcheckemc_clariion.so \
        libcheckhp_sw.so \
-       libcheckrdac.so \
-       libcheckrbd.so
+       libcheckrdac.so
+ifneq ($(ENABLE_RADOS),0)
+LIBS += libcheckrbd.so
+endif
 
 all: $(LIBS)
 
index a0ffffe..9d79f96 100644 (file)
@@ -64,8 +64,7 @@ void libcheck_repair (struct checker * c)
        return;
 }
 
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
 {
        int rc;
        int ret;
index eec12d5..ce60e4c 100644 (file)
@@ -82,7 +82,7 @@ int libcheck_init (struct checker * c)
        ct->ptr = (unsigned char *) (((unsigned long)ct->buf + pgsize - 1) &
                  (~(pgsize - 1)));
 
-       /* Sucessfully initialized, return the context. */
+       /* Successfully initialized, return the context. */
        c->context = (void *) ct;
        return 0;
 
index a7b9f86..9c1ffed 100644 (file)
@@ -50,8 +50,7 @@ struct emc_clariion_checker_LU_context {
        int inactive_snap;
 };
 
-extern void
-hexadecimal_to_ascii(char * wwn, char *wwnstr)
+void hexadecimal_to_ascii(char * wwn, char *wwnstr)
 {
        int i,j, nbl;
 
index 0cc1111..6019c9d 100644 (file)
@@ -128,8 +128,7 @@ do_tur (int fd, unsigned int timeout)
        return 0;
 }
 
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
 {
        char buff[MX_ALLOC_LEN];
 
index 481d860..9ea0572 100644 (file)
@@ -174,7 +174,7 @@ int libcheck_init(struct checker * c)
 
        ct->image = strdup(image);
        if (!ct->image)
-               goto free_info;
+               goto free_username;
 
        pool = udev_device_get_sysattr_value(bus_dev, "pool");
        if (!pool)
index 68682c8..a643a4a 100644 (file)
@@ -262,8 +262,7 @@ const char
        }
 }
 
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
 {
        struct volume_access_inq inq;
        int ret, inqfail;
index 92200aa..b4a5cb2 100644 (file)
@@ -11,6 +11,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
+#include <sys/sysmacros.h>
 #include <errno.h>
 #include <sys/time.h>
 #include <pthread.h>
@@ -267,6 +268,7 @@ static void *tur_thread(void *ctx)
        pthread_mutex_unlock(&ct->lock);
 
        state = tur_check(ct->fd, ct->timeout, copy_msg_to_tcc, ct->message);
+       pthread_testcancel();
 
        /* TUR checker done */
        pthread_mutex_lock(&ct->lock);
@@ -314,8 +316,7 @@ static void copy_msg_to_checker(void *c_p, const char *msg)
        strlcpy(c->message, msg, sizeof(c->message));
 }
 
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
 {
        struct tur_checker_context *ct = c->context;
        struct timespec tsp;
index 2d629ef..bb6619b 100644 (file)
@@ -125,8 +125,7 @@ find_hwe (vector hwtable, char * vendor, char * product, char * revision)
        return ret;
 }
 
-extern struct mpentry *
-find_mpe (vector mptable, char * wwid)
+struct mpentry *find_mpe(vector mptable, char *wwid)
 {
        int i;
        struct mpentry * mpe;
@@ -141,8 +140,7 @@ find_mpe (vector mptable, char * wwid)
        return NULL;
 }
 
-extern char *
-get_mpe_wwid (vector mptable, char * alias)
+char *get_mpe_wwid(vector mptable, char *alias)
 {
        int i;
        struct mpentry * mpe;
@@ -345,10 +343,15 @@ merge_hwe (struct hwentry * dst, struct hwentry * src)
        merge_num(user_friendly_names);
        merge_num(retain_hwhandler);
        merge_num(detect_prio);
+       merge_num(detect_checker);
        merge_num(deferred_remove);
        merge_num(delay_watch_checks);
        merge_num(delay_wait_checks);
        merge_num(skip_kpartx);
+       merge_num(max_sectors_kb);
+       merge_num(san_path_err_threshold);
+       merge_num(san_path_err_forget_rate);
+       merge_num(san_path_err_recovery_time);
 
        /*
         * Make sure features is consistent with
@@ -421,6 +424,7 @@ store_hwe (vector hwtable, struct hwentry * dhwe)
        hwe->user_friendly_names = dhwe->user_friendly_names;
        hwe->retain_hwhandler = dhwe->retain_hwhandler;
        hwe->detect_prio = dhwe->detect_prio;
+       hwe->detect_checker = dhwe->detect_checker;
 
        if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product)))
                goto out;
@@ -489,6 +493,9 @@ free_config (struct config * conf)
        if (conf->uid_attribute)
                FREE(conf->uid_attribute);
 
+       if (conf->uid_attrs)
+               FREE(conf->uid_attrs);
+
        if (conf->getuid)
                FREE(conf->getuid);
 
@@ -608,6 +615,7 @@ load_config (char * file)
        conf->fast_io_fail = DEFAULT_FAST_IO_FAIL;
        conf->retain_hwhandler = DEFAULT_RETAIN_HWHANDLER;
        conf->detect_prio = DEFAULT_DETECT_PRIO;
+       conf->detect_checker = DEFAULT_DETECT_CHECKER;
        conf->force_sync = DEFAULT_FORCE_SYNC;
        conf->partition_delim = DEFAULT_PARTITION_DELIM;
        conf->processed_main_config = 0;
@@ -620,6 +628,11 @@ load_config (char * file)
        conf->deferred_remove = DEFAULT_DEFERRED_REMOVE;
        conf->skip_kpartx = DEFAULT_SKIP_KPARTX;
        conf->disable_changed_wwids = DEFAULT_DISABLE_CHANGED_WWIDS;
+       conf->remove_retries = 0;
+       conf->max_sectors_kb = DEFAULT_MAX_SECTORS_KB;
+       conf->san_path_err_threshold = DEFAULT_ERR_CHECKS;
+       conf->san_path_err_forget_rate = DEFAULT_ERR_CHECKS;
+       conf->san_path_err_recovery_time = DEFAULT_ERR_CHECKS;
 
        /*
         * preload default hwtable
index dbdaa44..ffc69b5 100644 (file)
@@ -36,6 +36,12 @@ enum mpath_cmds {
        CMD_ADD_WWID,
 };
 
+enum force_reload_types {
+       FORCE_RELOAD_NONE,
+       FORCE_RELOAD_YES,
+       FORCE_RELOAD_WEAK,
+};
+
 struct hwentry {
        char * vendor;
        char * product;
@@ -62,10 +68,15 @@ struct hwentry {
        int user_friendly_names;
        int retain_hwhandler;
        int detect_prio;
+       int detect_checker;
        int deferred_remove;
        int delay_watch_checks;
        int delay_wait_checks;
+       int san_path_err_threshold;
+       int san_path_err_forget_rate;
+       int san_path_err_recovery_time;
        int skip_kpartx;
+       int max_sectors_kb;
        char * bl_product;
 };
 
@@ -92,7 +103,11 @@ struct mpentry {
        int deferred_remove;
        int delay_watch_checks;
        int delay_wait_checks;
+       int san_path_err_threshold;
+       int san_path_err_forget_rate;
+       int san_path_err_recovery_time;
        int skip_kpartx;
+       int max_sectors_kb;
        uid_t uid;
        gid_t gid;
        mode_t mode;
@@ -131,11 +146,15 @@ struct config {
        int reassign_maps;
        int retain_hwhandler;
        int detect_prio;
+       int detect_checker;
        int force_sync;
        int deferred_remove;
        int processed_main_config;
        int delay_watch_checks;
        int delay_wait_checks;
+       int san_path_err_threshold;
+       int san_path_err_forget_rate;
+       int san_path_err_recovery_time;
        int uxsock_timeout;
        int strict_timing;
        int retrigger_tries;
@@ -145,10 +164,13 @@ struct config {
        int uev_wait_timeout;
        int skip_kpartx;
        int disable_changed_wwids;
+       int remove_retries;
+       int max_sectors_kb;
        unsigned int version[3];
 
        char * multipath_dir;
        char * selector;
+       char * uid_attrs;
        char * uid_attribute;
        char * getuid;
        char * features;
index d428099..b29a660 100644 (file)
@@ -39,6 +39,7 @@
 #include "util.h"
 #include "uxsock.h"
 #include "wwids.h"
+#include "sysfs.h"
 
 /* group paths in pg by host adapter
  */
@@ -253,8 +254,7 @@ int rr_optimize_path_order(struct pathgroup *pgp)
        return 0;
 }
 
-extern int
-setup_map (struct multipath * mpp, char * params, int params_size)
+int setup_map(struct multipath *mpp, char *params, int params_size)
 {
        struct pathgroup * pgp;
        struct config *conf;
@@ -295,7 +295,11 @@ setup_map (struct multipath * mpp, char * params, int params_size)
        select_deferred_remove(conf, mpp);
        select_delay_watch_checks(conf, mpp);
        select_delay_wait_checks(conf, mpp);
+       select_san_path_err_threshold(conf, mpp);
+       select_san_path_err_forget_rate(conf, mpp);
+       select_san_path_err_recovery_time(conf, mpp);
        select_skip_kpartx(conf, mpp);
+       select_max_sectors_kb(conf, mpp);
 
        sysfs_set_scsi_tmo(mpp, conf->checkint);
        put_multipath_config(conf);
@@ -386,11 +390,105 @@ pgcmp (struct multipath * mpp, struct multipath * cmpp)
        return r;
 }
 
+static struct udev_device *
+get_udev_for_mpp(const struct multipath *mpp)
+{
+       dev_t devnum;
+       struct udev_device *udd;
+
+       if (!mpp || !mpp->dmi) {
+               condlog(1, "%s called with empty mpp", __func__);
+               return NULL;
+       }
+
+       devnum = makedev(mpp->dmi->major, mpp->dmi->minor);
+       udd = udev_device_new_from_devnum(udev, 'b', devnum);
+       if (!udd) {
+               condlog(1, "failed to get udev device for %s", mpp->alias);
+               return NULL;
+       }
+       return udd;
+}
+
+static void
+trigger_udev_change(const struct multipath *mpp)
+{
+       static const char change[] = "change";
+       struct udev_device *udd = get_udev_for_mpp(mpp);
+       if (!udd)
+               return;
+       condlog(3, "triggering %s uevent for %s", change, mpp->alias);
+       sysfs_attr_set_value(udd, "uevent", change, sizeof(change)-1);
+       udev_device_unref(udd);
+}
+
+static int
+is_mpp_known_to_udev(const struct multipath *mpp)
+{
+       struct udev_device *udd = get_udev_for_mpp(mpp);
+       int ret = (udd != NULL);
+       udev_device_unref(udd);
+       return ret;
+}
+
+static int
+sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload)
+{
+       struct pathgroup * pgp;
+       struct path *pp;
+       char buff[11];
+       int i, j, ret, err = 0;
+       struct udev_device *udd;
+       int max_sectors_kb;
+
+       if (mpp->max_sectors_kb == MAX_SECTORS_KB_UNDEF)
+               return 0;
+       max_sectors_kb = mpp->max_sectors_kb;
+       if (is_reload) {
+               if (!mpp->dmi && dm_get_info(mpp->alias, &mpp->dmi) != 0) {
+                       condlog(1, "failed to get dm info for %s", mpp->alias);
+                       return 1;
+               }
+               udd = get_udev_for_mpp(mpp);
+               if (!udd) {
+                       condlog(1, "failed to get udev device to set max_sectors_kb for %s", mpp->alias);
+                       return 1;
+               }
+               ret = sysfs_attr_get_value(udd, "queue/max_sectors_kb", buff,
+                                          sizeof(buff));
+               udev_device_unref(udd);
+               if (ret <= 0) {
+                       condlog(1, "failed to get current max_sectors_kb from %s", mpp->alias);
+                       return 1;
+               }
+               if (sscanf(buff, "%u\n", &max_sectors_kb) != 1) {
+                       condlog(1, "can't parse current max_sectors_kb from %s",
+                               mpp->alias);
+                       return 1;
+               }
+       }
+       snprintf(buff, 11, "%d", max_sectors_kb);
+
+       vector_foreach_slot (mpp->pg, pgp, i) {
+               vector_foreach_slot(pgp->paths, pp, j) {
+                       ret = sysfs_attr_set_value(pp->udev,
+                                                  "queue/max_sectors_kb",
+                                                  buff, strlen(buff));
+                       if (ret < 0) {
+                               condlog(1, "failed setting max_sectors_kb on %s : %s", pp->dev, strerror(-ret));
+                               err = 1;
+                       }
+               }
+       }
+       return err;
+}
+
 static void
 select_action (struct multipath * mpp, vector curmp, int force_reload)
 {
        struct multipath * cmpp;
        struct multipath * cmpp_by_name;
+       char * mpp_feat, * cmpp_feat;
 
        cmpp = find_mp_by_wwid(curmp, mpp->wwid);
        cmpp_by_name = find_mp_by_alias(curmp, mpp->alias);
@@ -429,13 +527,13 @@ select_action (struct multipath * mpp, vector curmp, int force_reload)
                /* reset alias to existing alias */
                FREE(mpp->alias);
                mpp->alias = STRDUP(cmpp->alias);
-               mpp->action = ACT_NOTHING;
+               mpp->action = ACT_IMPOSSIBLE;
                return;
        }
 
        if (pathcount(mpp, PATH_UP) == 0) {
-               mpp->action = ACT_NOTHING;
-               condlog(3, "%s: set ACT_NOTHING (no usable path)",
+               mpp->action = ACT_IMPOSSIBLE;
+               condlog(3, "%s: set ACT_IMPOSSIBLE (no usable path)",
                        mpp->alias);
                return;
        }
@@ -451,11 +549,11 @@ select_action (struct multipath * mpp, vector curmp, int force_reload)
                        mpp->alias);
                return;
        }
-       if (!mpp->no_path_retry &&
-           (strlen(cmpp->features) != strlen(mpp->features) ||
-            strcmp(cmpp->features, mpp->features))) {
+
+       if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF &&
+           mpp->no_path_retry != cmpp->no_path_retry) {
                mpp->action =  ACT_RELOAD;
-               condlog(3, "%s: set ACT_RELOAD (features change)",
+               condlog(3, "%s: set ACT_RELOAD (no_path_retry change)",
                        mpp->alias);
                return;
        }
@@ -468,6 +566,31 @@ select_action (struct multipath * mpp, vector curmp, int force_reload)
                        mpp->alias);
                return;
        }
+
+       if (mpp->retain_hwhandler != RETAIN_HWHANDLER_UNDEF &&
+           mpp->retain_hwhandler != cmpp->retain_hwhandler) {
+               mpp->action = ACT_RELOAD;
+               condlog(3, "%s: set ACT_RELOAD (retain_hwhandler change)",
+                       mpp->alias);
+               return;
+       }
+
+       cmpp_feat = STRDUP(cmpp->features);
+       mpp_feat = STRDUP(mpp->features);
+       if (cmpp_feat && mpp_feat) {
+               remove_feature(&mpp_feat, "queue_if_no_path");
+               remove_feature(&mpp_feat, "retain_attached_hw_handler");
+               remove_feature(&cmpp_feat, "queue_if_no_path");
+               remove_feature(&cmpp_feat, "retain_attached_hw_handler");
+               if (strncmp(mpp_feat, cmpp_feat, PARAMS_SIZE)) {
+                       mpp->action =  ACT_RELOAD;
+                       condlog(3, "%s: set ACT_RELOAD (features change)",
+                               mpp->alias);
+               }
+       }
+       FREE(cmpp_feat);
+       FREE(mpp_feat);
+
        if (!cmpp->selector || strncmp(cmpp->selector, mpp->selector,
                    strlen(mpp->selector))) {
                mpp->action = ACT_RELOAD;
@@ -499,14 +622,19 @@ select_action (struct multipath * mpp, vector curmp, int force_reload)
                        mpp->alias);
                return;
        }
+       if (!is_mpp_known_to_udev(cmpp)) {
+               mpp->action = ACT_RELOAD;
+               condlog(3, "%s: set ACT_RELOAD (udev device not initialized)",
+                       mpp->alias);
+               return;
+       }
        mpp->action = ACT_NOTHING;
        condlog(3, "%s: set ACT_NOTHING (map unchanged)",
                mpp->alias);
        return;
 }
 
-extern int
-reinstate_paths (struct multipath * mpp)
+int reinstate_paths(struct multipath *mpp)
 {
        int i, j;
        struct pathgroup * pgp;
@@ -582,8 +710,7 @@ fail:
 #define DOMAP_EXIST    2
 #define DOMAP_DRY      3
 
-extern int
-domap (struct multipath * mpp, char * params, int is_daemon)
+int domap(struct multipath *mpp, char *params, int is_daemon)
 {
        int r = DOMAP_FAIL;
        struct config *conf;
@@ -607,6 +734,7 @@ domap (struct multipath * mpp, char * params, int is_daemon)
        switch (mpp->action) {
        case ACT_REJECT:
        case ACT_NOTHING:
+       case ACT_IMPOSSIBLE:
                return DOMAP_EXIST;
 
        case ACT_SWITCHPG:
@@ -626,16 +754,19 @@ domap (struct multipath * mpp, char * params, int is_daemon)
                        return DOMAP_RETRY;
                }
 
+               sysfs_set_max_sectors_kb(mpp, 0);
                r = dm_addmap_create(mpp, params);
 
                lock_multipath(mpp, 0);
                break;
 
        case ACT_RELOAD:
+               sysfs_set_max_sectors_kb(mpp, 1);
                r = dm_addmap_reload(mpp, params, 0);
                break;
 
        case ACT_RESIZE:
+               sysfs_set_max_sectors_kb(mpp, 1);
                r = dm_addmap_reload(mpp, params, 1);
                break;
 
@@ -651,8 +782,10 @@ domap (struct multipath * mpp, char * params, int is_daemon)
                r = dm_rename(mpp->alias_old, mpp->alias,
                              conf->partition_delim, mpp->skip_kpartx);
                put_multipath_config(conf);
-               if (r)
+               if (r) {
+                       sysfs_set_max_sectors_kb(mpp, 1);
                        r = dm_addmap_reload(mpp, params, 0);
+               }
                break;
 
        default:
@@ -748,8 +881,15 @@ out:
        return ret;
 }
 
-extern int
-coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, int force_reload, enum mpath_cmds cmd)
+/*
+ * The force_reload parameter determines how coalesce_paths treats existing maps.
+ * FORCE_RELOAD_NONE: existing maps aren't touched at all
+ * FORCE_RELOAD_YES: all maps are rebuilt from scratch and (re)loaded in DM
+ * FORCE_RELOAD_WEAK: existing maps are compared to the current conf and only
+ * reloaded in DM if there's a difference. This is useful during startup.
+ */
+int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid,
+                   int force_reload, enum mpath_cmds cmd)
 {
        int r = 1;
        int k, i;
@@ -767,7 +907,7 @@ coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, int force_r
        if (refwwid && !strlen(refwwid))
                refwwid = NULL;
 
-       if (force_reload) {
+       if (force_reload != FORCE_RELOAD_NONE) {
                vector_foreach_slot (pathvec, pp1, k) {
                        pp1->mpp = NULL;
                }
@@ -856,7 +996,8 @@ coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, int force_r
                if (cmd == CMD_DRY_RUN)
                        mpp->action = ACT_DRY_RUN;
                if (mpp->action == ACT_UNDEF)
-                       select_action(mpp, curmp, force_reload);
+                       select_action(mpp, curmp,
+                                     force_reload == FORCE_RELOAD_YES ? 1 : 0);
 
                r = domap(mpp, params, is_daemon);
 
@@ -876,6 +1017,18 @@ coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, int force_r
                if (r == DOMAP_DRY)
                        continue;
 
+               if (r == DOMAP_EXIST && mpp->action == ACT_NOTHING &&
+                   force_reload == FORCE_RELOAD_WEAK)
+                       /*
+                        * First time we're called, and no changes applied.
+                        * domap() was a noop. But we can't be sure that
+                        * udev has already finished setting up this device
+                        * (udev in initrd may have been shut down while
+                        * processing this device or its children).
+                        * Trigger a change event, just in case.
+                        */
+                       trigger_udev_change(find_mp_by_wwid(curmp, mpp->wwid));
+
                conf = get_multipath_config();
                allow_queueing = conf->allow_queueing;
                put_multipath_config(conf);
@@ -953,9 +1106,8 @@ coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, int force_r
  * 1 - failure
  * 2 - blacklist
  */
-extern int
-get_refwwid (enum mpath_cmds cmd, char * dev, enum devtypes dev_type,
-            vector pathvec, char **wwid)
+int get_refwwid(enum mpath_cmds cmd, char *dev, enum devtypes dev_type,
+               vector pathvec, char **wwid)
 {
        int ret = 1;
        struct path * pp;
@@ -1126,7 +1278,8 @@ out:
        return 1;
 }
 
-extern int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, int is_daemon)
+int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh,
+              int is_daemon)
 {
        char params[PARAMS_SIZE] = {0};
        struct path *pp;
index 442c956..fb078a6 100644 (file)
@@ -20,6 +20,7 @@ enum actions {
        ACT_RESIZE,
        ACT_FORCERENAME,
        ACT_DRY_RUN,
+       ACT_IMPOSSIBLE,
 };
 
 #define FLUSH_ONE 1
index a72078f..db2b756 100644 (file)
 #define DEFAULT_DEV_LOSS_TMO   600
 #define DEFAULT_RETAIN_HWHANDLER RETAIN_HWHANDLER_ON
 #define DEFAULT_DETECT_PRIO    DETECT_PRIO_ON
+#define DEFAULT_DETECT_CHECKER DETECT_CHECKER_ON
 #define DEFAULT_DEFERRED_REMOVE        DEFERRED_REMOVE_OFF
-#define DEFAULT_DELAY_CHECKS   DELAY_CHECKS_OFF
+#define DEFAULT_DELAY_CHECKS   NU_NO
+#define DEFAULT_ERR_CHECKS     NU_NO
 #define DEFAULT_UEVENT_STACKSIZE 256
 #define DEFAULT_RETRIGGER_DELAY        10
 #define DEFAULT_RETRIGGER_TRIES        3
@@ -37,6 +39,7 @@
 #define DEFAULT_PARTITION_DELIM        NULL
 #define DEFAULT_SKIP_KPARTX SKIP_KPARTX_OFF
 #define DEFAULT_DISABLE_CHANGED_WWIDS 0
+#define DEFAULT_MAX_SECTORS_KB MAX_SECTORS_KB_UNDEF
 
 #define DEFAULT_CHECKINT       5
 #define MAX_CHECKINT(a)                (a << 2)
index 5aea5b6..026418f 100644 (file)
@@ -12,6 +12,7 @@
 #include <ctype.h>
 #include <unistd.h>
 #include <errno.h>
+#include <sys/sysmacros.h>
 
 #include "checkers.h"
 #include "vector.h"
@@ -94,8 +95,8 @@ dm_write_log (int level, const char *file, int line, const char *f, ...)
        return;
 }
 
-extern void
-dm_init(int v) {
+void dm_init(int v)
+{
        dm_log_init(&dm_write_log);
        dm_log_init_verbose(v + 3);
 }
@@ -200,8 +201,7 @@ dm_drv_prereq (void)
        return 1;
 }
 
-extern int
-dm_prereq (void)
+int dm_prereq(void)
 {
        if (dm_lib_prereq())
                return 1;
@@ -249,13 +249,13 @@ out:
        return r;
 }
 
-extern int
-dm_simplecmd_flush (int task, const char *name, uint16_t udev_flags) {
+int dm_simplecmd_flush (int task, const char *name, uint16_t udev_flags)
+{
        return dm_simplecmd(task, name, 0, 1, udev_flags, 0);
 }
 
-extern int
-dm_simplecmd_noflush (int task, const char *name, uint16_t udev_flags) {
+int dm_simplecmd_noflush (int task, const char *name, uint16_t udev_flags)
+{
        return dm_simplecmd(task, name, 1, 1, udev_flags, 0);
 }
 
@@ -338,8 +338,8 @@ addout:
        return r;
 }
 
-extern int
-dm_addmap_create (struct multipath *mpp, char * params) {
+int dm_addmap_create (struct multipath *mpp, char * params)
+{
        int ro;
 
        for (ro = 0; ro <= 1; ro++) {
@@ -369,13 +369,13 @@ dm_addmap_create (struct multipath *mpp, char * params) {
 #define ADDMAP_RW 0
 #define ADDMAP_RO 1
 
-extern int
-dm_addmap_reload (struct multipath *mpp, char *params, int flush)
+int dm_addmap_reload(struct multipath *mpp, char *params, int flush)
 {
-       int r;
+       int r = 0;
        uint16_t udev_flags = (flush ? 0 : MPATH_UDEV_RELOAD_FLAG) |
                              ((mpp->skip_kpartx == SKIP_KPARTX_ON)?
-                              MPATH_UDEV_NO_KPARTX_FLAG : 0);
+                              MPATH_UDEV_NO_KPARTX_FLAG : 0) |
+                             ((mpp->nr_active)? 0 : MPATH_UDEV_NO_PATHS_FLAG);
 
        /*
         * DM_DEVICE_RELOAD cannot wait on a cookie, as
@@ -383,22 +383,22 @@ dm_addmap_reload (struct multipath *mpp, char *params, int flush)
         * DM_DEVICE_RESUME. So call DM_DEVICE_RESUME
         * after each successful call to DM_DEVICE_RELOAD.
         */
-       r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, ADDMAP_RW,
-                     SKIP_KPARTX_OFF);
+       if (!mpp->force_readonly)
+               r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params,
+                             ADDMAP_RW, SKIP_KPARTX_OFF);
        if (!r) {
-               if (errno != EROFS)
+               if (!mpp->force_readonly && errno != EROFS)
                        return 0;
                r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp,
                              params, ADDMAP_RO, SKIP_KPARTX_OFF);
        }
        if (r)
-               r = dm_simplecmd(DM_DEVICE_RESUME, mpp->alias, flush,
+               r = dm_simplecmd(DM_DEVICE_RESUME, mpp->alias, !flush,
                                 1, udev_flags, 0);
        return r;
 }
 
-extern int
-dm_map_present (const char * str)
+int dm_map_present(const char * str)
 {
        int r = 0;
        struct dm_task *dmt;
@@ -425,8 +425,7 @@ out:
        return r;
 }
 
-extern int
-dm_get_map(const char * name, unsigned long long * size, char * outparams)
+int dm_get_map(const char *name, unsigned long long *size, char *outparams)
 {
        int r = 1;
        struct dm_task *dmt;
@@ -492,8 +491,7 @@ uuidout:
        return r;
 }
 
-extern int
-dm_get_uuid(char *name, char *uuid)
+int dm_get_uuid(char *name, char *uuid)
 {
        char uuidtmp[WWID_SIZE];
 
@@ -533,8 +531,7 @@ dm_compare_uuid(const char* mapname1, const char* mapname2)
        return 1;
 }
 
-extern int
-dm_get_status(char * name, char * outstatus)
+int dm_get_status(char *name, char *outstatus)
 {
        int r = 1;
        struct dm_task *dmt;
@@ -577,8 +574,7 @@ out:
  *    0 : no match
  *   -1 : empty map
  */
-extern int
-dm_type(const char * name, char * type)
+int dm_type(const char *name, char *type)
 {
        int r = 0;
        struct dm_task *dmt;
@@ -611,8 +607,7 @@ out:
        return r;
 }
 
-extern int
-dm_is_mpath(const char * name)
+int dm_is_mpath(const char *name)
 {
        int r = 0;
        struct dm_task *dmt;
@@ -766,12 +761,6 @@ out:
 }
 
 static int
-has_partmap(const char *name, void *data)
-{
-       return 1;
-}
-
-static int
 partmap_in_use(const char *name, void *data)
 {
        int part_count, *ret_count = (int *)data;
@@ -791,10 +780,13 @@ partmap_in_use(const char *name, void *data)
        return 0;
 }
 
-extern int
-_dm_flush_map (const char * mapname, int need_sync, int deferred_remove)
+int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
+                  int need_suspend, int retries)
 {
        int r;
+       int queue_if_no_path = 0;
+       unsigned long long mapsize;
+       char params[PARAMS_SIZE] = {0};
 
        if (!dm_is_mpath(mapname))
                return 0; /* nothing to do */
@@ -804,6 +796,16 @@ _dm_flush_map (const char * mapname, int need_sync, int deferred_remove)
        if (!do_deferred(deferred_remove) && partmap_in_use(mapname, NULL))
                        return 1;
 
+       if (need_suspend &&
+           !dm_get_map(mapname, &mapsize, params) &&
+           strstr(params, "queue_if_no_path")) {
+               if (!dm_queue_if_no_path((char *)mapname, 0))
+                       queue_if_no_path = 1;
+               else
+                       /* Leave queue_if_no_path alone if unset failed */
+                       queue_if_no_path = -1;
+       }
+
        if (dm_remove_partmaps(mapname, need_sync, deferred_remove))
                return 1;
 
@@ -812,17 +814,36 @@ _dm_flush_map (const char * mapname, int need_sync, int deferred_remove)
                return 1;
        }
 
-       r = dm_device_remove(mapname, need_sync, deferred_remove);
+       do {
+               if (need_suspend && queue_if_no_path != -1)
+                       dm_simplecmd_flush(DM_DEVICE_SUSPEND, mapname, 0);
 
-       if (r) {
-               if (do_deferred(deferred_remove) && dm_map_present(mapname)) {
-                       condlog(4, "multipath map %s remove deferred",
+               r = dm_device_remove(mapname, need_sync, deferred_remove);
+
+               if (r) {
+                       if (do_deferred(deferred_remove)
+                           && dm_map_present(mapname)) {
+                               condlog(4, "multipath map %s remove deferred",
+                                       mapname);
+                               return 2;
+                       }
+                       condlog(4, "multipath map %s removed", mapname);
+                       return 0;
+               } else {
+                       condlog(2, "failed to remove multipath map %s",
                                mapname);
-                       return 2;
+                       if (need_suspend && queue_if_no_path != -1) {
+                               dm_simplecmd_noflush(DM_DEVICE_RESUME,
+                                                    mapname, 0);
+                       }
                }
-               condlog(4, "multipath map %s removed", mapname);
-               return 0;
-       }
+               if (retries)
+                       sleep(1);
+       } while (retries-- > 0);
+
+       if (queue_if_no_path == 1)
+               dm_queue_if_no_path((char *)mapname, 1);
+
        return 1;
 }
 
@@ -831,7 +852,7 @@ _dm_flush_map (const char * mapname, int need_sync, int deferred_remove)
 int
 dm_flush_map_nopaths(const char * mapname, int deferred_remove)
 {
-       return _dm_flush_map(mapname, 1, deferred_remove);
+       return _dm_flush_map(mapname, 1, deferred_remove, 0, 0);
 }
 
 #else
@@ -839,53 +860,12 @@ dm_flush_map_nopaths(const char * mapname, int deferred_remove)
 int
 dm_flush_map_nopaths(const char * mapname, int deferred_remove)
 {
-       return _dm_flush_map(mapname, 1, 0);
+       return _dm_flush_map(mapname, 1, 0, 0, 0);
 }
 
 #endif
 
-extern int
-dm_suspend_and_flush_map (const char * mapname)
-{
-       int s = 0, queue_if_no_path = 0;
-       unsigned long long mapsize;
-       char params[PARAMS_SIZE] = {0};
-       int udev_flags = 0;
-
-       if (!dm_is_mpath(mapname))
-               return 0; /* nothing to do */
-
-       /* if the device currently has no partitions, do not
-          run kpartx on it if you fail to delete it */
-       if (do_foreach_partmaps(mapname, has_partmap, NULL) == 0)
-               udev_flags |= MPATH_UDEV_NO_KPARTX_FLAG;
-
-       if (!dm_get_map(mapname, &mapsize, params)) {
-               if (strstr(params, "queue_if_no_path"))
-                       queue_if_no_path = 1;
-       }
-
-       if (queue_if_no_path)
-               s = dm_queue_if_no_path((char *)mapname, 0);
-       /* Leave queue_if_no_path alone if unset failed */
-       if (s)
-               queue_if_no_path = 0;
-       else
-               s = dm_simplecmd_flush(DM_DEVICE_SUSPEND, mapname, 0);
-
-       if (!dm_flush_map(mapname)) {
-               condlog(4, "multipath map %s removed", mapname);
-               return 0;
-       }
-       condlog(2, "failed to remove multipath map %s", mapname);
-       dm_simplecmd_noflush(DM_DEVICE_RESUME, mapname, udev_flags);
-       if (queue_if_no_path)
-               s = dm_queue_if_no_path((char *)mapname, 1);
-       return 1;
-}
-
-extern int
-dm_flush_maps (void)
+int dm_flush_maps (int retries)
 {
        int r = 0;
        struct dm_task *dmt;
@@ -907,7 +887,7 @@ dm_flush_maps (void)
                goto out;
 
        do {
-               r |= dm_suspend_and_flush_map(names->name);
+               r |= dm_suspend_and_flush_map(names->name, retries);
                next = names->next;
                names = (void *) names + next;
        } while (next);
@@ -1582,10 +1562,9 @@ int dm_reassign(const char *mapname)
                sysfs_check_holders(dm_dep, dev_t);
        }
 
-       dm_task_destroy (dmt);
-
        r = 1;
 out:
+       dm_task_destroy (dmt);
        return r;
 }
 
index e6d1090..aca4454 100644 (file)
 #define MPATH_UDEV_NO_KPARTX_FLAG 0
 #endif
 
+#ifdef DM_SUBSYSTEM_UDEV_FLAG2
+#define MPATH_UDEV_NO_PATHS_FLAG DM_SUBSYSTEM_UDEV_FLAG2
+#else
+#define MPATH_UDEV_NO_PATHS_FLAG 0
+#endif
+
 void dm_init(int verbosity);
 int dm_prereq (void);
 int dm_drv_version (unsigned int * version, char * str);
@@ -30,13 +36,14 @@ int dm_get_map(const char *, unsigned long long *, char *);
 int dm_get_status(char *, char *);
 int dm_type(const char *, char *);
 int dm_is_mpath(const char *);
-int _dm_flush_map (const char *, int, int);
+int _dm_flush_map (const char *, int, int, int, int);
 int dm_flush_map_nopaths(const char * mapname, int deferred_remove);
-#define dm_flush_map(mapname) _dm_flush_map(mapname, 1, 0)
-#define dm_flush_map_nosync(mapname) _dm_flush_map(mapname, 0, 0)
+#define dm_flush_map(mapname) _dm_flush_map(mapname, 1, 0, 0, 0)
+#define dm_flush_map_nosync(mapname) _dm_flush_map(mapname, 0, 0, 0, 0)
+#define dm_suspend_and_flush_map(mapname, retries) \
+       _dm_flush_map(mapname, 1, 0, 1, retries)
 int dm_cancel_deferred_remove(struct multipath *mpp);
-int dm_suspend_and_flush_map(const char * mapname);
-int dm_flush_maps (void);
+int dm_flush_maps (int retries);
 int dm_fail_path(char * mapname, char * path);
 int dm_reinstate_path(char * mapname, char * path);
 int dm_queue_if_no_path(char *mapname, int enable);
index 61b6910..82066f6 100644 (file)
@@ -249,6 +249,8 @@ declare_ovr_snprint(selector, print_str)
 declare_mp_handler(selector, set_str)
 declare_mp_snprint(selector, print_str)
 
+declare_def_handler(uid_attrs, set_str)
+declare_def_snprint(uid_attrs, print_str)
 declare_def_handler(uid_attribute, set_str)
 declare_def_snprint_defstr(uid_attribute, print_str, DEFAULT_UID_ATTRIBUTE)
 declare_ovr_handler(uid_attribute, set_str)
@@ -379,6 +381,13 @@ declare_ovr_snprint(detect_prio, print_yes_no_undef)
 declare_hw_handler(detect_prio, set_yes_no_undef)
 declare_hw_snprint(detect_prio, print_yes_no_undef)
 
+declare_def_handler(detect_checker, set_yes_no_undef)
+declare_def_snprint_defint(detect_checker, print_yes_no_undef, YNU_NO)
+declare_ovr_handler(detect_checker, set_yes_no_undef)
+declare_ovr_snprint(detect_checker, print_yes_no_undef)
+declare_hw_handler(detect_checker, set_yes_no_undef)
+declare_hw_snprint(detect_checker, print_yes_no_undef)
+
 declare_def_handler(force_sync, set_yes_no)
 declare_def_snprint(force_sync, print_yes_no)
 
@@ -415,6 +424,18 @@ declare_mp_snprint(skip_kpartx, print_yes_no_undef)
 declare_def_handler(disable_changed_wwids, set_yes_no)
 declare_def_snprint(disable_changed_wwids, print_yes_no)
 
+declare_def_handler(remove_retries, set_int)
+declare_def_snprint(remove_retries, print_int)
+
+declare_def_handler(max_sectors_kb, set_int)
+declare_def_snprint(max_sectors_kb, print_nonzero)
+declare_ovr_handler(max_sectors_kb, set_int)
+declare_ovr_snprint(max_sectors_kb, print_nonzero)
+declare_hw_handler(max_sectors_kb, set_int)
+declare_hw_snprint(max_sectors_kb, print_nonzero)
+declare_mp_handler(max_sectors_kb, set_int)
+declare_mp_snprint(max_sectors_kb, print_nonzero)
+
 static int
 def_config_dir_handler(struct config *conf, vector strvec)
 {
@@ -1011,7 +1032,7 @@ declare_mp_handler(reservation_key, set_reservation_key)
 declare_mp_snprint(reservation_key, print_reservation_key)
 
 static int
-set_delay_checks(vector strvec, void *ptr)
+set_off_int_undef(vector strvec, void *ptr)
 {
        int *int_ptr = (int *)ptr;
        char * buff;
@@ -1021,47 +1042,69 @@ set_delay_checks(vector strvec, void *ptr)
                return 1;
 
        if (!strcmp(buff, "no") || !strcmp(buff, "0"))
-               *int_ptr = DELAY_CHECKS_OFF;
+               *int_ptr = NU_NO;
        else if ((*int_ptr = atoi(buff)) < 1)
-               *int_ptr = DELAY_CHECKS_UNDEF;
+               *int_ptr = NU_UNDEF;
 
        FREE(buff);
        return 0;
 }
 
 int
-print_delay_checks(char * buff, int len, void *ptr)
+print_off_int_undef(char * buff, int len, void *ptr)
 {
        int *int_ptr = (int *)ptr;
 
        switch(*int_ptr) {
-       case DELAY_CHECKS_UNDEF:
+       case NU_UNDEF:
                return 0;
-       case DELAY_CHECKS_OFF:
-               return snprintf(buff, len, "\"off\"");
+       case NU_NO:
+               return snprintf(buff, len, "\"no\"");
        default:
                return snprintf(buff, len, "%i", *int_ptr);
        }
 }
 
-declare_def_handler(delay_watch_checks, set_delay_checks)
-declare_def_snprint(delay_watch_checks, print_delay_checks)
-declare_ovr_handler(delay_watch_checks, set_delay_checks)
-declare_ovr_snprint(delay_watch_checks, print_delay_checks)
-declare_hw_handler(delay_watch_checks, set_delay_checks)
-declare_hw_snprint(delay_watch_checks, print_delay_checks)
-declare_mp_handler(delay_watch_checks, set_delay_checks)
-declare_mp_snprint(delay_watch_checks, print_delay_checks)
-
-declare_def_handler(delay_wait_checks, set_delay_checks)
-declare_def_snprint(delay_wait_checks, print_delay_checks)
-declare_ovr_handler(delay_wait_checks, set_delay_checks)
-declare_ovr_snprint(delay_wait_checks, print_delay_checks)
-declare_hw_handler(delay_wait_checks, set_delay_checks)
-declare_hw_snprint(delay_wait_checks, print_delay_checks)
-declare_mp_handler(delay_wait_checks, set_delay_checks)
-declare_mp_snprint(delay_wait_checks, print_delay_checks)
-
+declare_def_handler(delay_watch_checks, set_off_int_undef)
+declare_def_snprint(delay_watch_checks, print_off_int_undef)
+declare_ovr_handler(delay_watch_checks, set_off_int_undef)
+declare_ovr_snprint(delay_watch_checks, print_off_int_undef)
+declare_hw_handler(delay_watch_checks, set_off_int_undef)
+declare_hw_snprint(delay_watch_checks, print_off_int_undef)
+declare_mp_handler(delay_watch_checks, set_off_int_undef)
+declare_mp_snprint(delay_watch_checks, print_off_int_undef)
+declare_def_handler(delay_wait_checks, set_off_int_undef)
+declare_def_snprint(delay_wait_checks, print_off_int_undef)
+declare_ovr_handler(delay_wait_checks, set_off_int_undef)
+declare_ovr_snprint(delay_wait_checks, print_off_int_undef)
+declare_hw_handler(delay_wait_checks, set_off_int_undef)
+declare_hw_snprint(delay_wait_checks, print_off_int_undef)
+declare_mp_handler(delay_wait_checks, set_off_int_undef)
+declare_mp_snprint(delay_wait_checks, print_off_int_undef)
+declare_def_handler(san_path_err_threshold, set_off_int_undef)
+declare_def_snprint(san_path_err_threshold, print_off_int_undef)
+declare_ovr_handler(san_path_err_threshold, set_off_int_undef)
+declare_ovr_snprint(san_path_err_threshold, print_off_int_undef)
+declare_hw_handler(san_path_err_threshold, set_off_int_undef)
+declare_hw_snprint(san_path_err_threshold, print_off_int_undef)
+declare_mp_handler(san_path_err_threshold, set_off_int_undef)
+declare_mp_snprint(san_path_err_threshold, print_off_int_undef)
+declare_def_handler(san_path_err_forget_rate, set_off_int_undef)
+declare_def_snprint(san_path_err_forget_rate, print_off_int_undef)
+declare_ovr_handler(san_path_err_forget_rate, set_off_int_undef)
+declare_ovr_snprint(san_path_err_forget_rate, print_off_int_undef)
+declare_hw_handler(san_path_err_forget_rate, set_off_int_undef)
+declare_hw_snprint(san_path_err_forget_rate, print_off_int_undef)
+declare_mp_handler(san_path_err_forget_rate, set_off_int_undef)
+declare_mp_snprint(san_path_err_forget_rate, print_off_int_undef)
+declare_def_handler(san_path_err_recovery_time, set_off_int_undef)
+declare_def_snprint(san_path_err_recovery_time, print_off_int_undef)
+declare_ovr_handler(san_path_err_recovery_time, set_off_int_undef)
+declare_ovr_snprint(san_path_err_recovery_time, print_off_int_undef)
+declare_hw_handler(san_path_err_recovery_time, set_off_int_undef)
+declare_hw_snprint(san_path_err_recovery_time, print_off_int_undef)
+declare_mp_handler(san_path_err_recovery_time, set_off_int_undef)
+declare_mp_snprint(san_path_err_recovery_time, print_off_int_undef)
 static int
 def_uxsock_timeout_handler(struct config *conf, vector strvec)
 {
@@ -1355,6 +1398,7 @@ init_keywords(vector keywords)
        install_keyword("multipath_dir", &def_multipath_dir_handler, &snprint_def_multipath_dir);
        install_keyword("path_selector", &def_selector_handler, &snprint_def_selector);
        install_keyword("path_grouping_policy", &def_pgpolicy_handler, &snprint_def_pgpolicy);
+       install_keyword("uid_attrs", &def_uid_attrs_handler, &snprint_def_uid_attrs);
        install_keyword("uid_attribute", &def_uid_attribute_handler, &snprint_def_uid_attribute);
        install_keyword("getuid_callout", &def_getuid_handler, &snprint_def_getuid);
        install_keyword("prio", &def_prio_name_handler, &snprint_def_prio_name);
@@ -1385,6 +1429,7 @@ init_keywords(vector keywords)
        install_keyword("reservation_key", &def_reservation_key_handler, &snprint_def_reservation_key);
        install_keyword("retain_attached_hw_handler", &def_retain_hwhandler_handler, &snprint_def_retain_hwhandler);
        install_keyword("detect_prio", &def_detect_prio_handler, &snprint_def_detect_prio);
+       install_keyword("detect_checker", &def_detect_checker_handler, &snprint_def_detect_checker);
        install_keyword("force_sync", &def_force_sync_handler, &snprint_def_force_sync);
        install_keyword("strict_timing", &def_strict_timing_handler, &snprint_def_strict_timing);
        install_keyword("deferred_remove", &def_deferred_remove_handler, &snprint_def_deferred_remove);
@@ -1392,6 +1437,10 @@ init_keywords(vector keywords)
        install_keyword("config_dir", &def_config_dir_handler, &snprint_def_config_dir);
        install_keyword("delay_watch_checks", &def_delay_watch_checks_handler, &snprint_def_delay_watch_checks);
        install_keyword("delay_wait_checks", &def_delay_wait_checks_handler, &snprint_def_delay_wait_checks);
+       install_keyword("san_path_err_threshold", &def_san_path_err_threshold_handler, &snprint_def_san_path_err_threshold);
+       install_keyword("san_path_err_forget_rate", &def_san_path_err_forget_rate_handler, &snprint_def_san_path_err_forget_rate);
+       install_keyword("san_path_err_recovery_time", &def_san_path_err_recovery_time_handler, &snprint_def_san_path_err_recovery_time);
+
        install_keyword("find_multipaths", &def_find_multipaths_handler, &snprint_def_find_multipaths);
        install_keyword("uxsock_timeout", &def_uxsock_timeout_handler, &snprint_def_uxsock_timeout);
        install_keyword("retrigger_tries", &def_retrigger_tries_handler, &snprint_def_retrigger_tries);
@@ -1399,6 +1448,8 @@ init_keywords(vector keywords)
        install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout);
        install_keyword("skip_kpartx", &def_skip_kpartx_handler, &snprint_def_skip_kpartx);
        install_keyword("disable_changed_wwids", &def_disable_changed_wwids_handler, &snprint_def_disable_changed_wwids);
+       install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries);
+       install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb);
        __deprecated install_keyword("default_selector", &def_selector_handler, NULL);
        __deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL);
        __deprecated install_keyword("default_uid_attribute", &def_uid_attribute_handler, NULL);
@@ -1469,10 +1520,15 @@ init_keywords(vector keywords)
        install_keyword("user_friendly_names", &hw_user_friendly_names_handler, &snprint_hw_user_friendly_names);
        install_keyword("retain_attached_hw_handler", &hw_retain_hwhandler_handler, &snprint_hw_retain_hwhandler);
        install_keyword("detect_prio", &hw_detect_prio_handler, &snprint_hw_detect_prio);
+       install_keyword("detect_checker", &hw_detect_checker_handler, &snprint_hw_detect_checker);
        install_keyword("deferred_remove", &hw_deferred_remove_handler, &snprint_hw_deferred_remove);
        install_keyword("delay_watch_checks", &hw_delay_watch_checks_handler, &snprint_hw_delay_watch_checks);
        install_keyword("delay_wait_checks", &hw_delay_wait_checks_handler, &snprint_hw_delay_wait_checks);
+       install_keyword("san_path_err_threshold", &hw_san_path_err_threshold_handler, &snprint_hw_san_path_err_threshold);
+       install_keyword("san_path_err_forget_rate", &hw_san_path_err_forget_rate_handler, &snprint_hw_san_path_err_forget_rate);
+       install_keyword("san_path_err_recovery_time", &hw_san_path_err_recovery_time_handler, &snprint_hw_san_path_err_recovery_time);
        install_keyword("skip_kpartx", &hw_skip_kpartx_handler, &snprint_hw_skip_kpartx);
+       install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb);
        install_sublevel_end();
 
        install_keyword_root("overrides", &overrides_handler);
@@ -1497,10 +1553,16 @@ init_keywords(vector keywords)
        install_keyword("user_friendly_names", &ovr_user_friendly_names_handler, &snprint_ovr_user_friendly_names);
        install_keyword("retain_attached_hw_handler", &ovr_retain_hwhandler_handler, &snprint_ovr_retain_hwhandler);
        install_keyword("detect_prio", &ovr_detect_prio_handler, &snprint_ovr_detect_prio);
+       install_keyword("detect_checker", &ovr_detect_checker_handler, &snprint_ovr_detect_checker);
        install_keyword("deferred_remove", &ovr_deferred_remove_handler, &snprint_ovr_deferred_remove);
        install_keyword("delay_watch_checks", &ovr_delay_watch_checks_handler, &snprint_ovr_delay_watch_checks);
        install_keyword("delay_wait_checks", &ovr_delay_wait_checks_handler, &snprint_ovr_delay_wait_checks);
+       install_keyword("san_path_err_threshold", &ovr_san_path_err_threshold_handler, &snprint_ovr_san_path_err_threshold);
+       install_keyword("san_path_err_forget_rate", &ovr_san_path_err_forget_rate_handler, &snprint_ovr_san_path_err_forget_rate);
+       install_keyword("san_path_err_recovery_time", &ovr_san_path_err_recovery_time_handler, &snprint_ovr_san_path_err_recovery_time);
+
        install_keyword("skip_kpartx", &ovr_skip_kpartx_handler, &snprint_ovr_skip_kpartx);
+       install_keyword("max_sectors_kb", &ovr_max_sectors_kb_handler, &snprint_ovr_max_sectors_kb);
 
        install_keyword_root("multipaths", &multipaths_handler);
        install_keyword_multi("multipath", &multipath_handler, NULL);
@@ -1527,6 +1589,10 @@ init_keywords(vector keywords)
        install_keyword("deferred_remove", &mp_deferred_remove_handler, &snprint_mp_deferred_remove);
        install_keyword("delay_watch_checks", &mp_delay_watch_checks_handler, &snprint_mp_delay_watch_checks);
        install_keyword("delay_wait_checks", &mp_delay_wait_checks_handler, &snprint_mp_delay_wait_checks);
+       install_keyword("san_path_err_threshold", &mp_san_path_err_threshold_handler, &snprint_mp_san_path_err_threshold);
+       install_keyword("san_path_err_forget_rate", &mp_san_path_err_forget_rate_handler, &snprint_mp_san_path_err_forget_rate);
+       install_keyword("san_path_err_recovery_time", &mp_san_path_err_recovery_time_handler, &snprint_mp_san_path_err_recovery_time);
        install_keyword("skip_kpartx", &mp_skip_kpartx_handler, &snprint_mp_skip_kpartx);
+       install_keyword("max_sectors_kb", &mp_max_sectors_kb_handler, &snprint_mp_max_sectors_kb);
        install_sublevel_end();
 }
index 4cd03c5..2d6097d 100644 (file)
@@ -14,6 +14,5 @@ int print_no_path_retry(char * buff, int len, void *ptr);
 int print_fast_io_fail(char * buff, int len, void *ptr);
 int print_dev_loss(char * buff, int len, void *ptr);
 int print_reservation_key(char * buff, int len, void * ptr);
-int print_delay_checks(char * buff, int len, void *ptr);
-
+int print_off_int_undef(char * buff, int len, void *ptr);
 #endif /* _DICT_H */
index 756344f..8c51254 100644 (file)
 #include "discovery.h"
 #include "prio.h"
 #include "defaults.h"
+#include "prioritizers/alua_rtpg.h"
 
 int
 alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
-                         int flag, struct path **pp_ptr)
+                         char *wwid, int flag, struct path **pp_ptr)
 {
        int err = PATHINFO_FAILED;
        struct path * pp;
@@ -51,6 +52,9 @@ alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
        if (!pp)
                return PATHINFO_FAILED;
 
+       if(wwid)
+               strncpy(pp->wwid, wwid, sizeof(pp->wwid));
+
        if (safe_sprintf(pp->dev, "%s", devname)) {
                condlog(0, "pp->dev too small");
        } else {
@@ -58,7 +62,7 @@ alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
                err = pathinfo(pp, conf, flag | DI_BLACKLIST);
        }
 
-       if (err)
+       if (err || !pp_ptr)
                free_path(pp);
        else if (pp_ptr)
                *pp_ptr = pp;
@@ -117,17 +121,17 @@ path_discover (vector pathvec, struct config * conf,
        if (!devname)
                return PATHINFO_FAILED;
 
-       if (filter_property(conf, udevice) > 0)
-               return PATHINFO_SKIPPED;
-
-       if (filter_devnode(conf->blist_devnode, conf->elist_devnode,
-                          (char *)devname) > 0)
-               return PATHINFO_SKIPPED;
-
        pp = find_path_by_dev(pathvec, (char *)devname);
        if (!pp) {
-               return store_pathinfo(pathvec, conf,
-                                     udevice, flag, NULL);
+               char devt[BLK_DEV_SIZE];
+               dev_t devnum = udev_device_get_devnum(udevice);
+
+               snprintf(devt, BLK_DEV_SIZE, "%d:%d",
+                        major(devnum), minor(devnum));
+               pp = find_path_by_devt(pathvec, devt);
+               if (!pp)
+                       return store_pathinfo(pathvec, conf,
+                                             udevice, flag, NULL);
        }
        return pathinfo(pp, conf, flag);
 }
@@ -177,7 +181,7 @@ path_discovery (vector pathvec, int flag)
 }
 
 #define declare_sysfs_get_str(fname)                                   \
-extern ssize_t                                                         \
+ssize_t                                                                        \
 sysfs_get_##fname (struct udev_device * udev, char * buff, size_t len) \
 {                                                                      \
        int l;                                                  \
@@ -209,8 +213,6 @@ declare_sysfs_get_str(devtype);
 declare_sysfs_get_str(vendor);
 declare_sysfs_get_str(model);
 declare_sysfs_get_str(rev);
-declare_sysfs_get_str(access_state);
-declare_sysfs_get_str(preferred_path);
 
 ssize_t
 sysfs_get_vpd (struct udev_device * udev, int pg,
@@ -503,10 +505,10 @@ sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen)
        if (!parent)
                return -1;
 
-       if (sysfs_get_access_state(parent, buff, buflen) <= 0)
+       if (sysfs_attr_get_value(parent, "access_state", buff, buflen) <= 0)
                return -1;
 
-       if (sysfs_get_preferred_path(parent, value, 16) <= 0)
+       if (sysfs_attr_get_value(parent, "preferred_path", value, 16) <= 0)
                return 0;
 
        preferred = strtoul(value, &eptr, 0);
@@ -585,7 +587,8 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
                                goto out;
                        }
                }
-       } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO) {
+       } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO &&
+               mpp->no_path_retry != NO_PATH_RETRY_QUEUE) {
                condlog(3, "%s: limiting dev_loss_tmo to %d, since "
                        "fast_io_fail is not set",
                        rport_id, DEFAULT_DEV_LOSS_TMO);
@@ -812,6 +815,25 @@ get_serial (char * str, int maxlen, int fd)
        return 1;
 }
 
+static void
+detect_alua(struct path * pp, struct config *conf)
+{
+       int ret;
+       int tpgs;
+       unsigned int timeout = conf->checker_timeout;
+
+       if ((tpgs = get_target_port_group_support(pp->fd, timeout)) <= 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       ret = get_target_port_group(pp, timeout);
+       if (ret < 0 || get_asymmetric_access_state(pp->fd, ret, timeout) < 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       pp->tpgs = tpgs;
+}
+
 #define DEFAULT_SGIO_LEN 254
 
 static int
@@ -1170,6 +1192,36 @@ scsi_sysfs_pathinfo (struct path * pp, vector hwtable)
 }
 
 static int
+nvme_sysfs_pathinfo (struct path * pp, vector hwtable)
+{
+       struct udev_device *parent, *nvme = NULL;
+
+       parent = pp->udev;
+       while (parent) {
+               const char *subsys = udev_device_get_subsystem(parent);
+
+               if (subsys && !strncmp(subsys, "nvme", 4)) {
+                       nvme = parent;
+                       break;
+               }
+               parent = udev_device_get_parent(parent);
+       }
+       if (!nvme)
+               return 1;
+
+       snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME");
+       snprintf(pp->product_id, SCSI_PRODUCT_SIZE, "%s", udev_device_get_sysattr_value(nvme, "model"));
+       snprintf(pp->serial, SERIAL_SIZE, "%s", udev_device_get_sysattr_value(nvme, "serial"));
+       snprintf(pp->rev, SCSI_REV_SIZE, "%s", udev_device_get_sysattr_value(nvme, "firmware_rev"));
+
+       condlog(3, "%s: vendor:%s product:%s serial:%s rev:%s", pp->dev,
+               pp->vendor_id, pp->product_id, pp->serial, pp->rev);
+       pp->hwe = find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL);
+
+       return 0;
+}
+
+static int
 rbd_sysfs_pathinfo (struct path * pp, vector hwtable)
 {
        sprintf(pp->vendor_id, "Ceph");
@@ -1388,6 +1440,8 @@ sysfs_pathinfo(struct path * pp, vector hwtable)
                pp->bus = SYSFS_BUS_SCSI;
        if (!strncmp(pp->dev,"rbd", 3))
                pp->bus = SYSFS_BUS_RBD;
+       if (!strncmp(pp->dev,"nvme", 4))
+               pp->bus = SYSFS_BUS_NVME;
 
        if (pp->bus == SYSFS_BUS_UNDEF)
                return 0;
@@ -1403,16 +1457,22 @@ sysfs_pathinfo(struct path * pp, vector hwtable)
        } else if (pp->bus == SYSFS_BUS_RBD) {
                if (rbd_sysfs_pathinfo(pp, hwtable))
                        return 1;
+       } else if (pp->bus == SYSFS_BUS_NVME) {
+               if (nvme_sysfs_pathinfo(pp, hwtable))
+                       return 1;
        }
        return 0;
 }
 
 static int
-scsi_ioctl_pathinfo (struct path * pp, int mask)
+scsi_ioctl_pathinfo (struct path * pp, struct config *conf, int mask)
 {
        struct udev_device *parent;
        const char *attr_path = NULL;
 
+       if (pp->tpgs == TPGS_UNDEF)
+               detect_alua(pp, conf);
+
        if (!(mask & DI_SERIAL))
                return 0;
 
@@ -1472,6 +1532,7 @@ get_state (struct path * pp, struct config *conf, int daemon)
                                return PATH_UNCHECKED;
                        }
                }
+               select_detect_checker(conf, pp);
                select_checker(conf, pp);
                if (!checker_selected(c)) {
                        condlog(3, "%s: No checker selected", pp->dev);
@@ -1714,14 +1775,27 @@ get_uid (struct path * pp, int path_state, struct udev_device *udev)
        return 0;
 }
 
-extern int
-pathinfo (struct path *pp, struct config *conf, int mask)
+int pathinfo(struct path *pp, struct config *conf, int mask)
 {
        int path_state;
 
-       if (!pp)
+       if (!pp || !conf)
                return PATHINFO_FAILED;
 
+       /*
+        * For behavior backward-compatibility with multipathd,
+        * the blacklisting by filter_property|devnode() is not
+        * limited by DI_BLACKLIST and occurs before this debug
+        * message with the mask value.
+        */
+       if (pp->udev && filter_property(conf, pp->udev) > 0)
+               return PATHINFO_SKIPPED;
+
+       if (filter_devnode(conf->blist_devnode,
+                          conf->elist_devnode,
+                          pp->dev) > 0)
+               return PATHINFO_SKIPPED;
+
        condlog(3, "%s: mask = 0x%x", pp->dev, mask);
 
        /*
@@ -1767,7 +1841,7 @@ pathinfo (struct path *pp, struct config *conf, int mask)
                get_geometry(pp);
 
        if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI &&
-           scsi_ioctl_pathinfo(pp, mask))
+           scsi_ioctl_pathinfo(pp, conf, mask))
                goto blank;
 
        if (pp->bus == SYSFS_BUS_CCISS &&
@@ -1784,7 +1858,7 @@ pathinfo (struct path *pp, struct config *conf, int mask)
                                pp->state = PATH_DOWN;
                        if (pp->state == PATH_UP && !pp->size) {
                                condlog(3, "%s: device size is 0, "
-                                       "path unuseable", pp->dev);
+                                       "path unusable", pp->dev);
                                pp->state = PATH_GHOST;
                        }
                } else {
index 176eac1..0563bfd 100644 (file)
@@ -18,7 +18,7 @@
 #endif
 
 /*
- * exerpt from sg_err.h
+ * excerpt from sg_err.h
  */
 #define SCSI_CHECK_CONDITION    0x2
 #define SCSI_COMMAND_TERMINATED 0x22
@@ -37,7 +37,7 @@ int path_offline (struct path *);
 int get_state (struct path * pp, struct config * conf, int daemon);
 int pathinfo (struct path * pp, struct config * conf, int mask);
 int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
-                             int flag, struct path **pp_ptr);
+                             char *wwid, int flag, struct path **pp_ptr);
 int store_pathinfo (vector pathvec, struct config *conf,
                    struct udev_device *udevice, int flag,
                    struct path **pp_ptr);
index 87e8398..469e60d 100644 (file)
@@ -140,9 +140,8 @@ assemble_map (struct multipath * mp, char * params, int len)
        return 0;
 }
 
-extern int
-disassemble_map (vector pathvec, char * params, struct multipath * mpp,
-                int is_daemon)
+int disassemble_map(vector pathvec, char *params, struct multipath *mpp,
+                   int is_daemon)
 {
        char * word;
        char * p;
@@ -184,10 +183,7 @@ disassemble_map (vector pathvec, char * params, struct multipath * mpp,
                        FREE(word);
                        return 1;
                }
-               if ((mpp->no_path_retry == NO_PATH_RETRY_UNDEF) ||
-                       (mpp->no_path_retry == NO_PATH_RETRY_FAIL) ||
-                       (mpp->no_path_retry == NO_PATH_RETRY_QUEUE))
-                       setup_feature(mpp, word);
+               setup_feature(mpp, word);
 
                FREE(word);
        }
@@ -331,12 +327,15 @@ disassemble_map (vector pathvec, char * params, struct multipath * mpp,
                        if (devt2devname(devname, FILE_NAME_SIZE, word)) {
                                condlog(2, "%s: cannot find block device",
                                        word);
-                               FREE(word);
-                               continue;
+                               devname[0] = '\0';
                        }
 
-                       if (pathvec)
-                               pp = find_path_by_dev(pathvec, devname);
+                       if (pathvec) {
+                               if (strlen(devname))
+                                       pp = find_path_by_dev(pathvec, devname);
+                               else
+                                       pp = find_path_by_devt(pathvec, word);
+                       }
 
                        if (!pp) {
                                pp = alloc_path();
@@ -393,19 +392,17 @@ disassemble_map (vector pathvec, char * params, struct multipath * mpp,
 
                        for (k = 0; k < num_paths_args; k++)
                                if (k == 0) {
+                                       p += get_word(p, &word);
+                                       def_minio = atoi(word);
+                                       FREE(word);
+
                                        if (!strncmp(mpp->selector,
                                                     "round-robin", 11)) {
-                                               p += get_word(p, &word);
-                                               def_minio = atoi(word);
 
                                                if (mpp->rr_weight == RR_WEIGHT_PRIO
                                                    && pp->priority > 0)
                                                        def_minio /= pp->priority;
 
-                                               FREE(word);
-                                       } else {
-                                               p += get_word(p, NULL);
-                                               def_minio = 0;
                                        }
 
                                        if (def_minio != mpp->minio)
@@ -425,8 +422,7 @@ out:
        return 1;
 }
 
-extern int
-disassemble_status (char * params, struct multipath * mpp)
+int disassemble_status(char *params, struct multipath *mpp)
 {
        char * word;
        char * p;
index 340035e..c944015 100644 (file)
@@ -544,7 +544,6 @@ static struct hwentry default_hw[] = {
                /* FlashSystem 710/720/810/820/840/900 */
                .vendor        = "IBM",
                .product       = "FlashSystem",
-               .no_path_retry = NO_PATH_RETRY_FAIL,
                .pgpolicy      = MULTIBUS,
        },
        {
@@ -885,6 +884,7 @@ static struct hwentry default_hw[] = {
                .prio_name     = PRIO_ALUA,
        },
        {
+               /* SANsymphony */
                .vendor        = "DataCore",
                .product       = "Virtual Disk",
                .pgpolicy      = GROUP_BY_PRIO,
@@ -899,8 +899,6 @@ static struct hwentry default_hw[] = {
                .vendor        = "PURE",
                .product       = "FlashArray",
                .pgpolicy      = MULTIBUS,
-               .fast_io_fail  = 10,
-               .dev_loss      = 60,
        },
        /*
         * Huawei
@@ -920,7 +918,6 @@ static struct hwentry default_hw[] = {
        {
                .vendor        = "Ceph",
                .product       = "RBD",
-               .no_path_retry = NO_PATH_RETRY_FAIL,
                .checker_name  = RBD,
                .deferred_remove = DEFERRED_REMOVE_ON,
        },
@@ -995,6 +992,16 @@ static struct hwentry default_hw[] = {
                .prio_name     = PRIO_ALUA,
                .no_path_retry = 15,
        },
+       {
+               /* NST / UNITY */
+               .vendor        = "Nexsan",
+               .product       = "(NestOS|NST5000)",
+               .hwhandler     = "1 alua",
+               .pgpolicy      = GROUP_BY_PRIO,
+               .pgfailback    = -FAILBACK_IMMEDIATE,
+               .prio_name     = PRIO_ALUA,
+               .no_path_retry = 30,
+       },
        /*
         * Xiotech
         */
@@ -1038,7 +1045,20 @@ static struct hwentry default_hw[] = {
                .vendor        = "Promise",
                .product       = "VTrak",
                .bl_product    = "VTrak V-LUN",
-               .pgpolicy      = MULTIBUS,
+               .hwhandler     = "1 alua",
+               .pgpolicy      = GROUP_BY_PRIO,
+               .pgfailback    = -FAILBACK_IMMEDIATE,
+               .prio_name     = PRIO_ALUA,
+               .no_path_retry = 30,
+       },
+       {
+               .vendor        = "Promise",
+               .product       = "Vess",
+               .bl_product    = "Vess V-LUN",
+               .hwhandler     = "1 alua",
+               .pgpolicy      = GROUP_BY_PRIO,
+               .pgfailback    = -FAILBACK_IMMEDIATE,
+               .prio_name     = PRIO_ALUA,
                .no_path_retry = 30,
        },
        /*
@@ -1053,6 +1073,16 @@ static struct hwentry default_hw[] = {
                .prio_name     = PRIO_ALUA,
                .no_path_retry = 30,
        },
+       /*
+        * Generic NVMe devices
+        */
+       {
+               .vendor        = "NVME",
+               .product       = ".*",
+               .uid_attribute = "ID_WWN",
+               .checker_name  = DIRECTIO,
+               .retain_hwhandler = RETAIN_HWHANDLER_OFF,
+       },
 #if 0
        /*
         * Copy this TEMPLATE to add new hardware.
@@ -1092,9 +1122,12 @@ static struct hwentry default_hw[] = {
                .dev_loss      = 600,
                .retain_hwhandler = RETAIN_HWHANDLER_ON,
                .detect_prio   = DETECT_PRIO_ON,
+               .detect_checker = DETECT_CHECKER_ON,
                .deferred_remove = DEFERRED_REMOVE_OFF,
                .delay_watch_checks = DELAY_CHECKS_OFF,
                .delay_wait_checks = DELAY_CHECKS_OFF,
+               .skip_kpartx = SKIP_KPARTX_OFF,
+               .max_sectors_kb = MAX_SECTORS_KB_UNDEF,
        },
 #endif
        /*
@@ -1106,8 +1139,7 @@ static struct hwentry default_hw[] = {
        },
 };
 
-extern int
-setup_default_hwtable (vector hw)
+int setup_default_hwtable(vector hw)
 {
        int r = 0;
        struct hwentry * hwe = default_hw;
index ceaa381..2b1dcf3 100644 (file)
@@ -317,4 +317,45 @@ static inline void list_splice_tail_init(struct list_head *list,
             &pos->member != (head);                                    \
             pos = n, n = list_entry(n->member.next, typeof(*n), member))
 
+/**
+ * list_for_each_entry_reverse_safe - iterate backwards over list of given type safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse_safe(pos, n, head, member)          \
+       for (pos = list_entry((head)->prev, typeof(*pos), member),      \
+                n = list_entry(pos->member.prev, typeof(*pos), member);\
+            &pos->member != (head);                                    \
+            pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
+/**
+ * list_for_some_entry_safe - iterate list from the given begin node to the given end node safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @from:      the begin node of the iteration.
+ * @to:                the end node of the iteration.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_some_entry_safe(pos, n, from, to, member)              \
+       for (pos = list_entry((from)->next, typeof(*pos), member),      \
+            n = list_entry(pos->member.next, typeof(*pos), member);    \
+            &pos->member != (to);                                      \
+            pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_some_entry_reverse_safe - iterate backwards list from the given begin node to the given end node safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @from:      the begin node of the iteration.
+ * @to:                the end node of the iteration.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_some_entry_reverse_safe(pos, n, from, to, member)      \
+       for (pos = list_entry((from)->prev, typeof(*pos), member),      \
+            n = list_entry(pos->member.prev, typeof(*pos), member);    \
+            &pos->member != (to);                                      \
+            pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
 #endif /* _LIST_H */
index 293a688..ff5a030 100644 (file)
@@ -148,7 +148,7 @@ dbg_strdup(char *str, char *file, char *function, int line)
 
 
 
-/* Display a buffer into a HEXA formated output */
+/* Display a buffer into a HEXA formatted output */
 static void
 dump_buffer(char *buff, int count)
 {
index 3064aab..4ae4afb 100644 (file)
@@ -13,8 +13,7 @@
 #include "pgpolicies.h"
 #include "switchgroup.h"
 
-extern int
-get_pgpolicy_id (char * str)
+int get_pgpolicy_id(char * str)
 {
        if (0 == strncmp(str, "failover", 8))
                return FAILOVER;
@@ -30,8 +29,7 @@ get_pgpolicy_id (char * str)
        return IOPOLICY_UNDEF;
 }
 
-extern int
-get_pgpolicy_name (char * buff, int len, int id)
+int get_pgpolicy_name(char * buff, int len, int id)
 {
        char * s;
 
@@ -89,8 +87,8 @@ sort_pathgroups (struct multipath *mp) {
 /*
  * One path group per unique tgt_node_name present in the path vector
  */
-extern int
-group_by_node_name (struct multipath * mp) {
+int group_by_node_name(struct multipath * mp)
+{
        int i, j;
        int * bitmap;
        struct path * pp;
@@ -165,8 +163,8 @@ out:
 /*
  * One path group per unique serial number present in the path vector
  */
-extern int
-group_by_serial (struct multipath * mp) {
+int group_by_serial(struct multipath * mp)
+{
        int i, j;
        int * bitmap;
        struct path * pp;
@@ -237,8 +235,7 @@ out:
        return 1;
 }
 
-extern int
-one_path_per_group (struct multipath * mp)
+int one_path_per_group(struct multipath *mp)
 {
        int i;
        struct path * pp;
@@ -275,8 +272,7 @@ out:
        return 1;
 }
 
-extern int
-one_group (struct multipath * mp)      /* aka multibus */
+int one_group(struct multipath *mp)    /* aka multibus */
 {
        struct pathgroup * pgp;
 
@@ -313,8 +309,7 @@ out:
        return 1;
 }
 
-extern int
-group_by_prio (struct multipath * mp)
+int group_by_prio(struct multipath *mp)
 {
        int i;
        unsigned int prio;
index 865562b..7c2a158 100644 (file)
@@ -392,7 +392,7 @@ snprint_dev_t (char * buff, size_t len, struct path * pp)
 static int
 snprint_offline (char * buff, size_t len, struct path * pp)
 {
-       if (!pp)
+       if (!pp || !pp->mpp)
                return snprintf(buff, len, "unknown");
        else if (pp->offline)
                return snprintf(buff, len, "offline");
@@ -403,7 +403,7 @@ snprint_offline (char * buff, size_t len, struct path * pp)
 static int
 snprint_chk_state (char * buff, size_t len, struct path * pp)
 {
-       if (!pp)
+       if (!pp || !pp->mpp)
                return snprintf(buff, len, "undef");
 
        switch (pp->state) {
@@ -942,8 +942,7 @@ snprint_pathgroup (char * line, int len, char * format,
        return (c - line);
 }
 
-extern void
-print_multipath_topology (struct multipath * mpp, int verbosity)
+void print_multipath_topology(struct multipath *mpp, int verbosity)
 {
        int resize;
        char *buff = NULL;
@@ -973,9 +972,8 @@ print_multipath_topology (struct multipath * mpp, int verbosity)
        FREE(buff);
 }
 
-extern int
-snprint_multipath_topology (char * buff, int len, struct multipath * mpp,
-                           int verbosity)
+int snprint_multipath_topology(char *buff, int len, struct multipath *mpp,
+                              int verbosity)
 {
        int j, i, fwd = 0;
        struct path * pp = NULL;
@@ -998,7 +996,7 @@ snprint_multipath_topology (char * buff, int len, struct multipath * mpp,
 
        if (verbosity > 1 &&
            mpp->action != ACT_NOTHING &&
-           mpp->action != ACT_UNDEF)
+           mpp->action != ACT_UNDEF && mpp->action != ACT_IMPOSSIBLE)
                        c += sprintf(c, "%%A: ");
 
        c += sprintf(c, "%%n");
@@ -1247,8 +1245,7 @@ snprint_hwentry (struct config *conf, char * buff, int len, struct hwentry * hwe
        return fwd;
 }
 
-extern int
-snprint_hwtable (struct config *conf, char * buff, int len, vector hwtable)
+int snprint_hwtable(struct config *conf, char *buff, int len, vector hwtable)
 {
        int fwd = 0;
        int i;
@@ -1300,8 +1297,7 @@ snprint_mpentry (struct config *conf, char * buff, int len, struct mpentry * mpe
        return fwd;
 }
 
-extern int
-snprint_mptable (struct config *conf, char * buff, int len, vector mptable)
+int snprint_mptable(struct config *conf, char *buff, int len, vector mptable)
 {
        int fwd = 0;
        int i;
@@ -1326,8 +1322,8 @@ snprint_mptable (struct config *conf, char * buff, int len, vector mptable)
        return fwd;
 }
 
-extern int
-snprint_overrides (struct config *conf, char * buff, int len, struct hwentry *overrides)
+int snprint_overrides(struct config *conf, char * buff, int len,
+                     struct hwentry *overrides)
 {
        int fwd = 0;
        int i;
@@ -1356,8 +1352,7 @@ out:
        return fwd;
 }
 
-extern int
-snprint_defaults (struct config *conf, char * buff, int len)
+int snprint_defaults(struct config *conf, char *buff, int len)
 {
        int fwd = 0;
        int i;
@@ -1438,8 +1433,7 @@ snprint_blacklist_devgroup (char *buff, int len, int *fwd, vector *vec)
        return pos;
 }
 
-extern int
-snprint_blacklist_report (struct config *conf, char * buff, int len)
+int snprint_blacklist_report(struct config *conf, char *buff, int len)
 {
        int threshold = MAX_LINE_LEN;
        int fwd = 0;
@@ -1501,8 +1495,7 @@ snprint_blacklist_report (struct config *conf, char * buff, int len)
        return fwd;
 }
 
-extern int
-snprint_blacklist (struct config *conf, char * buff, int len)
+int snprint_blacklist(struct config *conf, char *buff, int len)
 {
        int i;
        struct blentry * ble;
@@ -1578,8 +1571,7 @@ snprint_blacklist (struct config *conf, char * buff, int len)
        return fwd;
 }
 
-extern int
-snprint_blacklist_except (struct config *conf, char * buff, int len)
+int snprint_blacklist_except(struct config *conf, char *buff, int len)
 {
        int i;
        struct blentry * ele;
@@ -1655,8 +1647,7 @@ snprint_blacklist_except (struct config *conf, char * buff, int len)
        return fwd;
 }
 
-extern int
-snprint_status (char * buff, int len, struct vectors *vecs)
+int snprint_status(char *buff, int len, struct vectors *vecs)
 {
        int fwd = 0;
        int i;
@@ -1687,8 +1678,8 @@ snprint_status (char * buff, int len, struct vectors *vecs)
        return fwd;
 }
 
-extern int
-snprint_devices (struct config *conf, char * buff, int len, struct vectors *vecs)
+int snprint_devices(struct config *conf, char * buff, int len,
+                   struct vectors *vecs)
 {
        DIR *blkdir;
        struct dirent *blkdev;
@@ -1756,8 +1747,7 @@ snprint_devices (struct config *conf, char * buff, int len, struct vectors *vecs
 /*
  * stdout printing helpers
  */
-extern void
-print_path (struct path * pp, char * style)
+void print_path(struct path *pp, char *style)
 {
        char line[MAX_LINE_LEN];
 
@@ -1766,8 +1756,7 @@ print_path (struct path * pp, char * style)
        printf("%s", line);
 }
 
-extern void
-print_multipath (struct multipath * mpp, char * style)
+void print_multipath(struct multipath *mpp, char *style)
 {
        char line[MAX_LINE_LEN];
 
@@ -1776,8 +1765,7 @@ print_multipath (struct multipath * mpp, char * style)
        printf("%s", line);
 }
 
-extern void
-print_pathgroup (struct pathgroup * pgp, char * style)
+void print_pathgroup(struct pathgroup *pgp, char *style)
 {
        char line[MAX_LINE_LEN];
 
@@ -1786,8 +1774,7 @@ print_pathgroup (struct pathgroup * pgp, char * style)
        printf("%s", line);
 }
 
-extern void
-print_map (struct multipath * mpp, char * params)
+void print_map(struct multipath *mpp, char *params)
 {
        if (mpp->size && params)
                printf("0 %llu %s %s\n",
@@ -1795,14 +1782,12 @@ print_map (struct multipath * mpp, char * params)
        return;
 }
 
-extern void
-print_all_paths (vector pathvec, int banner)
+void print_all_paths(vector pathvec, int banner)
 {
        print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
 }
 
-extern void
-print_all_paths_custo (vector pathvec, int banner, char *fmt)
+void print_all_paths_custo(vector pathvec, int banner, char *fmt)
 {
        int i;
        struct path * pp;
index 6839fc7..b8c3436 100644 (file)
@@ -66,7 +66,7 @@
 
 #define MAX_LINE_LEN  80
 #define MAX_LINES     64
-#define MAX_FIELD_LEN 64
+#define MAX_FIELD_LEN 128
 #define PROGRESS_LEN  10
 
 struct path_data {
index 4d4969b..13a0924 100644 (file)
@@ -109,6 +109,7 @@ inquiry_command_set_evpd(struct inquiry_command *ic)
 #define VERSION_SPC3                                   0x05
 
 /* Defined TPGS field values. */
+#define TPGS_UNDEF                                      -1
 #define TPGS_NONE                                      0x0
 #define TPGS_IMPLICIT                                  0x1
 #define TPGS_EXPLICIT                                  0x2
index d161e68..70fb5d1 100644 (file)
@@ -7,12 +7,12 @@
  * Prioritizer for Device Mapper Multipath and HDS Storage
  *
  * Hitachis Modular Storage contains two controllers for redundancy. The
- * Storage internal LUN (LDEV) will normally allocated via two pathes to the
+ * Storage internal LUN (LDEV) will normally allocated via two paths to the
  * server (one path per controller). For performance reasons should the server
  * access to a LDEV only via one controller. The other path to the other
  * controller is stand-by. It is also possible to allocate more as one path
  * for a LDEV per controller. Here is active/active access allowed. The other
- * pathes via the other controller are stand-by.
+ * paths via the other controller are stand-by.
  *
  * This prioritizer checks with inquiry command the represented LDEV and
  * Controller number and gives back a priority followed by this scheme:
index 38495cd..ca06d6c 100644 (file)
@@ -117,7 +117,7 @@ out:
 }
 
 /*
- * Retuns:
+ * Returns:
  * -1: Unable to obtain proxy info
  *  0: Device _not_ proxy path
  *  1: Device _is_ proxy path
index ec1fd92..dd10ceb 100644 (file)
@@ -18,6 +18,7 @@
 #include "prio.h"
 #include "discovery.h"
 #include "dict.h"
+#include "util.h"
 #include "prioritizers/alua_rtpg.h"
 #include <inttypes.h>
 
@@ -41,28 +42,28 @@ do {                                                                        \
 #define do_default(dest, value)                                                \
 do {                                                                   \
        dest = value;                                                   \
-       origin = "(internal default)";                                  \
+       origin = "(setting: multipath internal)";                       \
 } while(0)
 
 #define mp_set_mpe(var)                                                        \
-do_set(var, mp->mpe, mp->var, "(LUN setting)")
+do_set(var, mp->mpe, mp->var, "(setting: multipath.conf multipaths section)")
 #define mp_set_hwe(var)                                                        \
-do_set(var, mp->hwe, mp->var, "(controller setting)")
+do_set(var, mp->hwe, mp->var, "(setting: array configuration)")
 #define mp_set_ovr(var)                                                        \
-do_set(var, conf->overrides, mp->var, "(overrides setting)")
+do_set(var, conf->overrides, mp->var, "(setting: multipath.conf overrides section)")
 #define mp_set_conf(var)                                               \
-do_set(var, conf, mp->var, "(config file default)")
+do_set(var, conf, mp->var, "(setting: multipath.conf defaults/devices section)")
 #define mp_set_default(var, value)                                     \
 do_default(mp->var, value)
 
 #define pp_set_mpe(var)                                                        \
-do_set(var, mpe, pp->var, "(LUN setting)")
+do_set(var, mpe, pp->var, "(setting: multipath.conf multipaths section)")
 #define pp_set_hwe(var)                                                        \
-do_set(var, pp->hwe, pp->var, "(controller setting)")
+do_set(var, pp->hwe, pp->var, "(setting: array configuration)")
 #define pp_set_conf(var)                                               \
-do_set(var, conf, pp->var, "(config file default)")
+do_set(var, conf, pp->var, "(setting: multipath.conf defaults/devices section)")
 #define pp_set_ovr(var)                                                        \
-do_set(var, conf->overrides, pp->var, "(overrides setting)")
+do_set(var, conf->overrides, pp->var, "(setting: multipath.conf overrides section)")
 #define pp_set_default(var, value)                                     \
 do_default(pp->var, value)
 
@@ -77,12 +78,11 @@ do {                                                                        \
 } while(0)
 
 #define set_attr_mpe(var, shift)                                       \
-do_attr_set(var, mp->mpe, shift, "(LUN setting)")
+do_attr_set(var, mp->mpe, shift, "(setting: multipath.conf multipaths section)")
 #define set_attr_conf(var, shift)                                      \
-do_attr_set(var, conf, shift, "(config file default)")
+do_attr_set(var, conf, shift, "(setting: multipath.conf defaults/devices section)")
 
-extern int
-select_mode (struct config *conf, struct multipath *mp)
+int select_mode(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -95,8 +95,7 @@ out:
        return 0;
 }
 
-extern int
-select_uid (struct config *conf, struct multipath *mp)
+int select_uid(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -109,8 +108,7 @@ out:
        return 0;
 }
 
-extern int
-select_gid (struct config *conf, struct multipath *mp)
+int select_gid(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -128,8 +126,7 @@ out:
  * traverse the configuration layers from most specific to most generic
  * stop at first explicit setting found
  */
-extern int
-select_rr_weight (struct config *conf, struct multipath * mp)
+int select_rr_weight(struct config *conf, struct multipath * mp)
 {
        char *origin, buff[13];
 
@@ -144,8 +141,7 @@ out:
        return 0;
 }
 
-extern int
-select_pgfailback (struct config *conf, struct multipath * mp)
+int select_pgfailback(struct config *conf, struct multipath * mp)
 {
        char *origin, buff[13];
 
@@ -160,8 +156,7 @@ out:
        return 0;
 }
 
-extern int
-select_pgpolicy (struct config *conf, struct multipath * mp)
+int select_pgpolicy(struct config *conf, struct multipath * mp)
 {
        char *origin, buff[POLICY_NAME_SIZE];
 
@@ -182,8 +177,7 @@ out:
        return 0;
 }
 
-extern int
-select_selector (struct config *conf, struct multipath * mp)
+int select_selector(struct config *conf, struct multipath * mp)
 {
        char *origin;
 
@@ -221,13 +215,13 @@ want_user_friendly_names(struct config *conf, struct multipath * mp)
        int user_friendly_names;
 
        do_set(user_friendly_names, mp->mpe, user_friendly_names,
-              "(LUN setting)");
+              "(setting: multipath.conf multipaths section)");
        do_set(user_friendly_names, conf->overrides, user_friendly_names,
-              "(overrides setting)");
+              "(setting: multipath.conf overrides section)");
        do_set(user_friendly_names, mp->hwe, user_friendly_names,
-              "(controller setting)");
+              "(setting: array configuration)");
        do_set(user_friendly_names, conf, user_friendly_names,
-              "(config file setting)");
+              "(setting: multipath.conf defaults/devices section)");
        do_default(user_friendly_names, DEFAULT_USER_FRIENDLY_NAMES);
 out:
        condlog(3, "%s: user_friendly_names = %s %s", mp->wwid,
@@ -236,14 +230,13 @@ out:
        return (user_friendly_names == USER_FRIENDLY_NAMES_ON);
 }
 
-extern int
-select_alias (struct config *conf, struct multipath * mp)
+int select_alias(struct config *conf, struct multipath * mp)
 {
        char *origin = NULL;
 
        if (mp->mpe && mp->mpe->alias) {
                mp->alias = STRDUP(mp->mpe->alias);
-               origin = "(LUN setting)";
+               origin = "(setting: multipath.conf multipaths section)";
                goto out;
        }
 
@@ -276,8 +269,7 @@ out:
        return mp->alias ? 0 : 1;
 }
 
-extern int
-select_features (struct config *conf, struct multipath * mp)
+int select_features(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -297,13 +289,14 @@ out:
                        condlog(1, "%s: config error, overriding 'no_path_retry' value",
                                mp->alias);
                        mp->no_path_retry = NO_PATH_RETRY_QUEUE;
-               }
+               } else if (mp->no_path_retry != NO_PATH_RETRY_QUEUE)
+                       condlog(1, "%s: config error, ignoring 'queue_if_no_path' because no_path_retry=%d",
+                               mp->alias, mp->no_path_retry);
        }
        return 0;
 }
 
-extern int
-select_hwhandler (struct config *conf, struct multipath * mp)
+int select_hwhandler(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -317,40 +310,49 @@ out:
        return 0;
 }
 
-extern int
-select_checker(struct config *conf, struct path *pp)
+int select_checker(struct config *conf, struct path *pp)
 {
        char *origin, *checker_name;
        struct checker * c = &pp->checker;
 
-       do_set(checker_name, conf->overrides, checker_name, "(overrides setting)");
-       do_set(checker_name, pp->hwe, checker_name, "(controller setting)");
-       do_set(checker_name, conf, checker_name, "(config file setting)");
+       if (pp->detect_checker == DETECT_CHECKER_ON && pp->tpgs > 0) {
+               checker_name = TUR;
+               origin = "(setting: array autodetected)";
+               goto out;
+       }
+       do_set(checker_name, conf->overrides, checker_name, "(setting: multipath.conf overrides section)");
+       do_set(checker_name, pp->hwe, checker_name, "(setting: array configuration)");
+       do_set(checker_name, conf, checker_name, "(setting: multipath.conf defaults/devices section)");
        do_default(checker_name, DEFAULT_CHECKER);
 out:
        checker_get(conf->multipath_dir, c, checker_name);
        condlog(3, "%s: path_checker = %s %s", pp->dev, c->name, origin);
        if (conf->checker_timeout) {
                c->timeout = conf->checker_timeout;
-               condlog(3, "%s: checker timeout = %u s (config file default)",
+               condlog(3, "%s: checker timeout = %u s (setting: multipath.conf defaults/devices section)",
                                pp->dev, c->timeout);
        }
        else if (sysfs_get_timeout(pp, &c->timeout) > 0)
-               condlog(3, "%s: checker timeout = %u ms (sysfs setting)",
+               condlog(3, "%s: checker timeout = %u ms (setting: kernel sysfs)",
                                pp->dev, c->timeout);
        else {
                c->timeout = DEF_TIMEOUT;
-               condlog(3, "%s: checker timeout = %u ms (internal default)",
+               condlog(3, "%s: checker timeout = %u ms (setting: multipath internal)",
                                pp->dev, c->timeout);
        }
        return 0;
 }
 
-extern int
-select_getuid (struct config *conf, struct path * pp)
+int select_getuid(struct config *conf, struct path *pp)
 {
        char *origin;
 
+       pp->uid_attribute = parse_uid_attribute_by_attrs(conf->uid_attrs, pp->dev);
+       if (pp->uid_attribute) {
+               origin = "(setting: multipath.conf defaults section)";
+               goto out;
+       }
+
        pp_set_ovr(getuid);
        pp_set_ovr(uid_attribute);
        pp_set_hwe(getuid);
@@ -371,20 +373,11 @@ out:
 void
 detect_prio(struct config *conf, struct path * pp)
 {
-       int ret;
        struct prio *p = &pp->prio;
-       int tpgs = 0;
-       unsigned int timeout = conf->checker_timeout;
        char buff[512];
        char *default_prio = PRIO_ALUA;
 
-       if ((tpgs = get_target_port_group_support(pp->fd, timeout)) <= 0)
-               return;
-       pp->tpgs = tpgs;
-       ret = get_target_port_group(pp, timeout);
-       if (ret < 0)
-               return;
-       if (get_asymmetric_access_state(pp->fd, ret, timeout) < 0)
+       if (pp->tpgs <= 0)
                return;
        if (sysfs_get_asymmetric_access_state(pp, buff, 512) >= 0)
                default_prio = PRIO_SYSFS;
@@ -400,8 +393,7 @@ do {                                                                        \
        }                                                               \
 } while(0)
 
-extern int
-select_prio (struct config *conf, struct path * pp)
+int select_prio(struct config *conf, struct path *pp)
 {
        char *origin;
        struct mpentry * mpe;
@@ -410,17 +402,17 @@ select_prio (struct config *conf, struct path * pp)
        if (pp->detect_prio == DETECT_PRIO_ON) {
                detect_prio(conf, pp);
                if (prio_selected(p)) {
-                       origin = "(detected setting)";
+                       origin = "(setting: array autodetected)";
                        goto out;
                }
        }
        mpe = find_mpe(conf->mptable, pp->wwid);
-       set_prio(conf->multipath_dir, mpe, "(LUN setting)");
-       set_prio(conf->multipath_dir, conf->overrides, "(overrides setting)");
-       set_prio(conf->multipath_dir, pp->hwe, "controller setting)");
-       set_prio(conf->multipath_dir, conf, "(config file default)");
+       set_prio(conf->multipath_dir, mpe, "(setting: multipath.conf multipaths section)");
+       set_prio(conf->multipath_dir, conf->overrides, "(setting: multipath.conf overrides section)");
+       set_prio(conf->multipath_dir, pp->hwe, "(setting: array configuration)");
+       set_prio(conf->multipath_dir, conf, "(setting: multipath.conf defaults/devices section)");
        prio_get(conf->multipath_dir, p, DEFAULT_PRIO, DEFAULT_PRIO_ARGS);
-       origin = "(internal default)";
+       origin = "(setting: multipath internal)";
 out:
        /*
         * fetch tpgs mode for alua, if its not already obtained
@@ -438,8 +430,7 @@ out:
        return 0;
 }
 
-extern int
-select_no_path_retry(struct config *conf, struct multipath *mp)
+int select_no_path_retry(struct config *conf, struct multipath *mp)
 {
        char *origin = NULL;
        char buff[12];
@@ -462,7 +453,7 @@ out:
                condlog(3, "%s: no_path_retry = %s (inherited setting)",
                        mp->alias, buff);
        else
-               condlog(3, "%s: no_path_retry = undef (internal default)",
+               condlog(3, "%s: no_path_retry = undef (setting: multipath internal)",
                        mp->alias);
        return 0;
 }
@@ -472,10 +463,10 @@ select_minio_rq (struct config *conf, struct multipath * mp)
 {
        char *origin;
 
-       do_set(minio_rq, mp->mpe, mp->minio, "(LUN setting)");
-       do_set(minio_rq, conf->overrides, mp->minio, "(overrides setting)");
-       do_set(minio_rq, mp->hwe, mp->minio, "(controller setting)");
-       do_set(minio_rq, conf, mp->minio, "(config file setting)");
+       do_set(minio_rq, mp->mpe, mp->minio, "(setting: multipath.conf multipaths section)");
+       do_set(minio_rq, conf->overrides, mp->minio, "(setting: multipath.conf overrides section)");
+       do_set(minio_rq, mp->hwe, mp->minio, "(setting: array configuration)");
+       do_set(minio_rq, conf, mp->minio, "(setting: multipath.conf defaults/devices section)");
        do_default(mp->minio, DEFAULT_MINIO_RQ);
 out:
        condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin);
@@ -497,8 +488,7 @@ out:
        return 0;
 }
 
-extern int
-select_minio (struct config *conf, struct multipath * mp)
+int select_minio(struct config *conf, struct multipath *mp)
 {
        unsigned int minv_dmrq[3] = {1, 1, 0};
 
@@ -508,8 +498,7 @@ select_minio (struct config *conf, struct multipath * mp)
                return select_minio_bio(conf, mp);
 }
 
-extern int
-select_fast_io_fail(struct config *conf, struct multipath *mp)
+int select_fast_io_fail(struct config *conf, struct multipath *mp)
 {
        char *origin, buff[12];
 
@@ -523,8 +512,7 @@ out:
        return 0;
 }
 
-extern int
-select_dev_loss(struct config *conf, struct multipath *mp)
+int select_dev_loss(struct config *conf, struct multipath *mp)
 {
        char *origin, buff[12];
 
@@ -539,8 +527,7 @@ out:
        return 0;
 }
 
-extern int
-select_flush_on_last_del(struct config *conf, struct multipath *mp)
+int select_flush_on_last_del(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -557,8 +544,7 @@ out:
        return 0;
 }
 
-extern int
-select_reservation_key (struct config *conf, struct multipath * mp)
+int select_reservation_key(struct config *conf, struct multipath *mp)
 {
        char *origin, buff[12];
 
@@ -572,8 +558,7 @@ out:
        return 0;
 }
 
-extern int
-select_retain_hwhandler (struct config *conf, struct multipath * mp)
+int select_retain_hwhandler(struct config *conf, struct multipath *mp)
 {
        char *origin;
        unsigned int minv_dm_retain[3] = {1, 5, 0};
@@ -594,8 +579,7 @@ out:
        return 0;
 }
 
-extern int
-select_detect_prio (struct config *conf, struct path * pp)
+int select_detect_prio(struct config *conf, struct path *pp)
 {
        char *origin;
 
@@ -609,8 +593,22 @@ out:
        return 0;
 }
 
-extern int
-select_deferred_remove (struct config *conf, struct multipath *mp)
+int select_detect_checker(struct config *conf, struct path *pp)
+{
+       char *origin;
+
+       pp_set_ovr(detect_checker);
+       pp_set_hwe(detect_checker);
+       pp_set_conf(detect_checker);
+       pp_set_default(detect_checker, DEFAULT_DETECT_CHECKER);
+out:
+       condlog(3, "%s: detect_checker = %s %s", pp->dev,
+               (pp->detect_checker == DETECT_CHECKER_ON)? "yes" : "no",
+               origin);
+       return 0;
+}
+
+int select_deferred_remove(struct config *conf, struct multipath *mp)
 {
        char *origin;
 
@@ -635,8 +633,7 @@ out:
        return 0;
 }
 
-extern int
-select_delay_watch_checks(struct config *conf, struct multipath *mp)
+int select_delay_watch_checks(struct config *conf, struct multipath *mp)
 {
        char *origin, buff[12];
 
@@ -646,13 +643,12 @@ select_delay_watch_checks(struct config *conf, struct multipath *mp)
        mp_set_conf(delay_watch_checks);
        mp_set_default(delay_watch_checks, DEFAULT_DELAY_CHECKS);
 out:
-       print_delay_checks(buff, 12, &mp->delay_watch_checks);
+       print_off_int_undef(buff, 12, &mp->delay_watch_checks);
        condlog(3, "%s: delay_watch_checks = %s %s", mp->alias, buff, origin);
        return 0;
 }
 
-extern int
-select_delay_wait_checks(struct config *conf, struct multipath *mp)
+int select_delay_wait_checks(struct config *conf, struct multipath *mp)
 {
        char *origin, buff[12];
 
@@ -662,14 +658,57 @@ select_delay_wait_checks(struct config *conf, struct multipath *mp)
        mp_set_conf(delay_wait_checks);
        mp_set_default(delay_wait_checks, DEFAULT_DELAY_CHECKS);
 out:
-       print_delay_checks(buff, 12, &mp->delay_wait_checks);
+       print_off_int_undef(buff, 12, &mp->delay_wait_checks);
        condlog(3, "%s: delay_wait_checks = %s %s", mp->alias, buff, origin);
        return 0;
 
 }
+int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
+{
+       char *origin, buff[12];
 
-extern int
-select_skip_kpartx (struct config *conf, struct multipath * mp)
+       mp_set_mpe(san_path_err_threshold);
+       mp_set_ovr(san_path_err_threshold);
+       mp_set_hwe(san_path_err_threshold);
+       mp_set_conf(san_path_err_threshold);
+       mp_set_default(san_path_err_threshold, DEFAULT_ERR_CHECKS);
+out:
+       print_off_int_undef(buff, 12, &mp->san_path_err_threshold);
+       condlog(3, "%s: san_path_err_threshold = %s %s", mp->alias, buff, origin);
+       return 0;
+}
+
+int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
+{
+       char *origin, buff[12];
+
+       mp_set_mpe(san_path_err_forget_rate);
+       mp_set_ovr(san_path_err_forget_rate);
+       mp_set_hwe(san_path_err_forget_rate);
+       mp_set_conf(san_path_err_forget_rate);
+       mp_set_default(san_path_err_forget_rate, DEFAULT_ERR_CHECKS);
+out:
+       print_off_int_undef(buff, 12, &mp->san_path_err_forget_rate);
+       condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias, buff, origin);
+       return 0;
+
+}
+int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
+{
+       char *origin, buff[12];
+
+       mp_set_mpe(san_path_err_recovery_time);
+       mp_set_ovr(san_path_err_recovery_time);
+       mp_set_hwe(san_path_err_recovery_time);
+       mp_set_conf(san_path_err_recovery_time);
+       mp_set_default(san_path_err_recovery_time, DEFAULT_ERR_CHECKS);
+out:
+       print_off_int_undef(buff, 12, &mp->san_path_err_recovery_time);
+       condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias, buff, origin);
+       return 0;
+
+}
+int select_skip_kpartx (struct config *conf, struct multipath * mp)
 {
        char *origin;
 
@@ -684,3 +723,19 @@ out:
                origin);
        return 0;
 }
+
+extern int
+select_max_sectors_kb (struct config *conf, struct multipath * mp)
+{
+       char *origin;
+
+       mp_set_mpe(max_sectors_kb);
+       mp_set_ovr(max_sectors_kb);
+       mp_set_hwe(max_sectors_kb);
+       mp_set_conf(max_sectors_kb);
+       return 0;
+out:
+       condlog(3, "%s: max_sectors_kb = %i %s", mp->alias, mp->max_sectors_kb,
+               origin);
+       return 0;
+}
index 3e6d607..58a32f3 100644 (file)
@@ -19,7 +19,12 @@ int select_dev_loss(struct config *conf, struct multipath *mp);
 int select_reservation_key(struct config *conf, struct multipath *mp);
 int select_retain_hwhandler (struct config *conf, struct multipath * mp);
 int select_detect_prio(struct config *conf, struct path * pp);
+int select_detect_checker(struct config *conf, struct path * pp);
 int select_deferred_remove(struct config *conf, struct multipath *mp);
 int select_delay_watch_checks (struct config *conf, struct multipath * mp);
 int select_delay_wait_checks (struct config *conf, struct multipath * mp);
 int select_skip_kpartx (struct config *conf, struct multipath * mp);
+int select_max_sectors_kb (struct config *conf, struct multipath * mp);
+int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp);
+int select_san_path_err_threshold(struct config *conf, struct multipath *mp);
+int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp);
index e4bf4c6..e225f8b 100644 (file)
@@ -17,6 +17,7 @@
 #include "structs_vec.h"
 #include "blacklist.h"
 #include "prio.h"
+#include "prioritizers/alua_spc3.h"
 
 struct adapter_group *
 alloc_adaptergroup(void)
@@ -96,6 +97,7 @@ alloc_path (void)
                pp->sg_id.lun = -1;
                pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC;
                pp->fd = -1;
+               pp->tpgs = TPGS_UNDEF;
                pp->priority = PRIO_UNDEF;
        }
        return pp;
@@ -201,8 +203,7 @@ alloc_multipath (void)
        return mpp;
 }
 
-extern void
-free_multipath_attributes (struct multipath * mpp)
+void free_multipath_attributes(struct multipath *mpp)
 {
        if (!mpp)
                return;
@@ -439,8 +440,7 @@ find_path_by_devt (vector pathvec, char * dev_t)
        return NULL;
 }
 
-extern int
-pathcountgr (struct pathgroup * pgp, int state)
+int pathcountgr(struct pathgroup *pgp, int state)
 {
        struct path *pp;
        int count = 0;
@@ -453,8 +453,7 @@ pathcountgr (struct pathgroup * pgp, int state)
        return count;
 }
 
-extern int
-pathcount (struct multipath * mpp, int state)
+int pathcount(struct multipath *mpp, int state)
 {
        struct pathgroup *pgp;
        int count = 0;
@@ -467,8 +466,7 @@ pathcount (struct multipath * mpp, int state)
        return count;
 }
 
-extern int
-pathcmp (struct pathgroup *pgp, struct pathgroup *cpgp)
+int pathcmp(struct pathgroup *pgp, struct pathgroup *cpgp)
 {
        int i, j;
        struct path *pp, *cpp;
@@ -498,19 +496,26 @@ first_path (struct multipath * mpp)
        return pgp?VECTOR_SLOT(pgp->paths, 0):NULL;
 }
 
-extern void
-setup_feature(struct multipath * mpp, char *feature)
+void setup_feature(struct multipath *mpp, char *feature)
 {
        if (!strncmp(feature, "queue_if_no_path", 16)) {
                if (mpp->no_path_retry <= NO_PATH_RETRY_UNDEF)
                        mpp->no_path_retry = NO_PATH_RETRY_QUEUE;
+               else
+                       condlog(1, "%s: ignoring feature queue_if_no_path because no_path_retry = %d",
+                               mpp->alias, mpp->no_path_retry);
+       } else if (!strcmp(feature, "retain_attached_hw_handler")) {
+               if (mpp->retain_hwhandler != RETAIN_HWHANDLER_OFF)
+                       mpp->retain_hwhandler = RETAIN_HWHANDLER_ON;
+               else
+                       condlog(1, "%s: ignoring feature 'retain_attached_hw_handler'",
+                               mpp->alias);
        }
 }
 
-extern int
-add_feature (char **f, char *n)
+int add_feature(char **f, char *n)
 {
-       int c = 0, d, l;
+       int c = 0, d, l = 0;
        char *e, *p, *t;
 
        if (!f)
@@ -532,18 +537,19 @@ add_feature (char **f, char *n)
        }
 
        /* Check if feature is already present */
-       if (strstr(*f, n))
-               return 0;
-
-       /* Get feature count */
-       c = strtoul(*f, &e, 10);
-       if (*f == e)
-               /* parse error */
-               return 1;
-
-       /* Check if we need to increase feature count space */
-       l = strlen(*f) + strlen(n) + 1;
+       if (*f) {
+               if (strstr(*f, n))
+                       return 0;
+
+               /* Get feature count */
+               c = strtoul(*f, &e, 10);
+               if (*f == e)
+                       /* parse error */
+                       return 1;
 
+               /* Check if we need to increase feature count space */
+               l = strlen(*f) + strlen(n) + 1;
+       }
        /* Count new features */
        if ((c % 10) == 9)
                l++;
@@ -575,7 +581,10 @@ add_feature (char **f, char *n)
        snprintf(p, l + 2, "%0d ", c);
 
        /* Copy the feature string */
-       p = strchr(*f, ' ');
+       p = NULL;
+       if (*f)
+               p = strchr(*f, ' ');
+
        if (p) {
                while (*p == ' ')
                        p++;
@@ -592,8 +601,7 @@ add_feature (char **f, char *n)
        return 0;
 }
 
-extern int
-remove_feature(char **f, char *o)
+int remove_feature(char **f, char *o)
 {
        int c = 0, d, l;
        char *e, *p, *n;
index 58508f6..98e13e4 100644 (file)
@@ -53,6 +53,7 @@ enum sysfs_buses {
        SYSFS_BUS_CCW,
        SYSFS_BUS_CCISS,
        SYSFS_BUS_RBD,
+       SYSFS_BUS_NVME,
 };
 
 enum pathstates {
@@ -121,6 +122,12 @@ enum detect_prio_states {
        DETECT_PRIO_ON = YNU_YES,
 };
 
+enum detect_checker_states {
+       DETECT_CHECKER_UNDEF = YNU_UNDEF,
+       DETECT_CHECKER_OFF = YNU_NO,
+       DETECT_CHECKER_ON = YNU_YES,
+};
+
 enum deferred_remove_states {
        DEFERRED_REMOVE_UNDEF = YNU_UNDEF,
        DEFERRED_REMOVE_OFF = YNU_NO,
@@ -134,6 +141,11 @@ enum skip_kpartx_states {
        SKIP_KPARTX_ON = YNU_YES,
 };
 
+enum max_sectors_kb_states {
+       MAX_SECTORS_KB_UNDEF = 0,
+       MAX_SECTORS_KB_MIN = 4,  /* can't be smaller than page size */
+};
+
 enum scsi_protocol {
        SCSI_PROTOCOL_FCP = 0,  /* Fibre Channel */
        SCSI_PROTOCOL_SPI = 1,  /* parallel SCSI */
@@ -147,9 +159,9 @@ enum scsi_protocol {
        SCSI_PROTOCOL_UNSPEC = 0xf, /* No specific protocol */
 };
 
-enum delay_checks_states {
-       DELAY_CHECKS_OFF = -1,
-       DELAY_CHECKS_UNDEF = 0,
+enum no_undef_states {
+       NU_NO = -1,
+       NU_UNDEF = 0,
 };
 
 enum initialized_states {
@@ -205,6 +217,7 @@ struct path {
        int priority;
        int pgindex;
        int detect_prio;
+       int detect_checker;
        int watch_checks;
        int wait_checks;
        int tpgs;
@@ -218,7 +231,10 @@ struct path {
        int initialized;
        int retriggers;
        int wwid_changed;
-
+       unsigned int path_failures;
+       time_t dis_reinstate_time;
+       int disable_reinstate;
+       int san_path_err_forget_rate;
        /* configlet pointers */
        struct hwentry * hwe;
 };
@@ -250,7 +266,12 @@ struct multipath {
        int deferred_remove;
        int delay_watch_checks;
        int delay_wait_checks;
+       int san_path_err_threshold;
+       int san_path_err_forget_rate;
+       int san_path_err_recovery_time;
        int skip_kpartx;
+       int max_sectors_kb;
+       int force_readonly;
        unsigned int dev_loss;
        uid_t uid;
        gid_t gid;
index e898528..22be8e0 100644 (file)
@@ -20,8 +20,7 @@
 /*
  * creates or updates mpp->paths reading mpp->pg
  */
-extern int
-update_mpp_paths(struct multipath * mpp, vector pathvec)
+int update_mpp_paths(struct multipath *mpp, vector pathvec)
 {
        struct pathgroup * pgp;
        struct path * pp;
@@ -45,8 +44,7 @@ update_mpp_paths(struct multipath * mpp, vector pathvec)
        return 0;
 }
 
-extern int
-adopt_paths (vector pathvec, struct multipath * mpp)
+int adopt_paths(vector pathvec, struct multipath *mpp)
 {
        int i, ret;
        struct path * pp;
@@ -81,8 +79,7 @@ adopt_paths (vector pathvec, struct multipath * mpp)
        return 0;
 }
 
-extern void
-orphan_path (struct path * pp, const char *reason)
+void orphan_path(struct path *pp, const char *reason)
 {
        condlog(3, "%s: orphan path, %s", pp->dev, reason);
        pp->mpp = NULL;
@@ -96,8 +93,7 @@ orphan_path (struct path * pp, const char *reason)
        pp->fd = -1;
 }
 
-extern void
-orphan_paths (vector pathvec, struct multipath * mpp)
+void orphan_paths(vector pathvec, struct multipath *mpp)
 {
        int i;
        struct path * pp;
@@ -151,15 +147,13 @@ _remove_map (struct multipath * mpp, struct vectors * vecs,
        free_multipath(mpp, KEEP_PATHS);
 }
 
-extern void
-remove_map (struct multipath * mpp, struct vectors * vecs, int purge_vec)
+void remove_map(struct multipath *mpp, struct vectors *vecs, int purge_vec)
 {
        _remove_map(mpp, vecs, KEEP_WAITER, purge_vec);
 }
 
-extern void
-remove_map_and_stop_waiter (struct multipath * mpp, struct vectors * vecs,
-                           int purge_vec)
+void remove_map_and_stop_waiter(struct multipath *mpp, struct vectors *vecs,
+                               int purge_vec)
 {
        _remove_map(mpp, vecs, STOP_WAITER, purge_vec);
 }
@@ -182,14 +176,12 @@ _remove_maps (struct vectors * vecs, int stop_waiter)
        vecs->mpvec = NULL;
 }
 
-extern void
-remove_maps (struct vectors * vecs)
+void remove_maps(struct vectors *vecs)
 {
        _remove_maps(vecs, KEEP_WAITER);
 }
 
-extern void
-remove_maps_and_stop_waiters (struct vectors * vecs)
+void remove_maps_and_stop_waiters(struct vectors *vecs)
 {
        _remove_maps(vecs, STOP_WAITER);
 }
@@ -320,8 +312,8 @@ void sync_paths(struct multipath *mpp, vector pathvec)
                pp->mpp = mpp;
 }
 
-extern int
-update_multipath_strings (struct multipath *mpp, vector pathvec, int is_daemon)
+int
+update_multipath_strings(struct multipath *mpp, vector pathvec, int is_daemon)
 {
        if (!mpp)
                return 1;
@@ -343,8 +335,7 @@ update_multipath_strings (struct multipath *mpp, vector pathvec, int is_daemon)
        return 0;
 }
 
-extern void
-set_no_path_retry(struct config *conf, struct multipath *mpp)
+void set_no_path_retry(struct config *conf, struct multipath *mpp)
 {
        mpp->retry_tick = 0;
        mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST);
@@ -373,9 +364,8 @@ set_no_path_retry(struct config *conf, struct multipath *mpp)
        }
 }
 
-extern int
-__setup_multipath (struct vectors * vecs, struct multipath * mpp,
-                  int reset, int is_daemon)
+int __setup_multipath(struct vectors *vecs, struct multipath *mpp,
+                     int reset, int is_daemon)
 {
        struct config *conf;
 
@@ -425,8 +415,7 @@ out:
        return 1;
 }
 
-extern struct multipath *
-add_map_without_path (struct vectors * vecs, char * alias)
+struct multipath *add_map_without_path (struct vectors *vecs, char *alias)
 {
        struct multipath * mpp = alloc_multipath();
 
@@ -473,9 +462,8 @@ find_existing_alias (struct multipath * mpp,
                }
 }
 
-extern struct multipath *
-add_map_with_path (struct vectors * vecs,
-                  struct path * pp, int add_vec)
+struct multipath *add_map_with_path(struct vectors *vecs, struct path *pp,
+                                   int add_vec)
 {
        struct multipath * mpp;
        struct config *conf = NULL;
@@ -514,8 +502,7 @@ out:
        return NULL;
 }
 
-extern int
-verify_paths(struct multipath * mpp, struct vectors * vecs)
+int verify_paths(struct multipath *mpp, struct vectors *vecs)
 {
        struct path * pp;
        int count = 0;
index 031c3d7..9632ce2 100644 (file)
@@ -7,8 +7,7 @@
 #include "structs.h"
 #include "switchgroup.h"
 
-extern void
-path_group_prio_update (struct pathgroup * pgp)
+void path_group_prio_update(struct pathgroup *pgp)
 {
        int i;
        int priority = 0;
@@ -32,8 +31,7 @@ path_group_prio_update (struct pathgroup * pgp)
                pgp->priority = 0;
 }
 
-extern int
-select_path_group (struct multipath * mpp)
+int select_path_group(struct multipath *mpp)
 {
        int i;
        int max_priority = 0;
index 9cf0311..97e0997 100644 (file)
@@ -154,7 +154,7 @@ ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name,
 }
 
 ssize_t sysfs_attr_set_value(struct udev_device *dev, const char *attr_name,
-                            char * value, size_t value_len)
+                            const char * value, size_t value_len)
 {
        char devpath[PATH_SIZE];
        struct stat statbuf;
index 2588c24..75c0f9c 100644 (file)
@@ -6,7 +6,7 @@
 #define _LIBMULTIPATH_SYSFS_H
 
 ssize_t sysfs_attr_set_value(struct udev_device *dev, const char *attr_name,
-                            char * value, size_t value_len);
+                            const char * value, size_t value_len);
 ssize_t sysfs_attr_get_value(struct udev_device *dev, const char *attr_name,
                             char * value, size_t value_len);
 ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name,
index 19b910f..4fbd1df 100644 (file)
@@ -24,6 +24,7 @@
 
 #include <unistd.h>
 #include <stdio.h>
+#include <stdbool.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <stddef.h>
@@ -38,6 +39,7 @@
 #include <linux/netlink.h>
 #include <pthread.h>
 #include <sys/mman.h>
+#include <sys/time.h>
 #include <libudev.h>
 #include <errno.h>
 
 #include "list.h"
 #include "uevent.h"
 #include "vector.h"
+#include "structs.h"
+#include "util.h"
+#include "config.h"
+#include "blacklist.h"
+
+#define MAX_ACCUMULATION_COUNT 2048
+#define MAX_ACCUMULATION_TIME 30*1000
+#define MIN_BURST_SPEED 10
 
 typedef int (uev_trigger)(struct uevent *, void * trigger_data);
 
@@ -72,48 +82,310 @@ struct uevent * alloc_uevent (void)
 {
        struct uevent *uev = MALLOC(sizeof(struct uevent));
 
-       if (uev)
+       if (uev) {
                INIT_LIST_HEAD(&uev->node);
+               INIT_LIST_HEAD(&uev->merge_node);
+       }
 
        return uev;
 }
 
 void
-service_uevq(struct list_head *tmpq)
+uevq_cleanup(struct list_head *tmpq)
 {
        struct uevent *uev, *tmp;
 
        list_for_each_entry_safe(uev, tmp, tmpq, node) {
                list_del_init(&uev->node);
 
-               if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data))
-                       condlog(0, "uevent trigger error");
-
                if (uev->udev)
                        udev_device_unref(uev->udev);
                FREE(uev);
        }
 }
 
-static void uevent_cleanup(void *arg)
+void
+uevent_get_wwid(struct uevent *uev)
 {
-       struct udev *udev = arg;
+       int i;
+       char *uid_attribute;
+       struct config * conf;
 
-       condlog(3, "Releasing uevent_listen() resources");
-       udev_unref(udev);
+       conf = get_multipath_config();
+       uid_attribute = parse_uid_attribute_by_attrs(conf->uid_attrs, uev->kernel);
+       put_multipath_config(conf);
+
+       if (!uid_attribute)
+               return;
+
+       for (i = 0; uev->envp[i] != NULL; i++) {
+               if (!strncmp(uev->envp[i], uid_attribute, strlen(uid_attribute)) &&
+                   strlen(uev->envp[i]) > strlen(uid_attribute) &&
+                   uev->envp[i][strlen(uid_attribute)] == '=') {
+                       uev->wwid = uev->envp[i] + strlen(uid_attribute) + 1;
+                       break;
+               }
+       }
+       free(uid_attribute);
+}
+
+bool
+uevent_need_merge(void)
+{
+       struct config * conf;
+       bool need_merge = false;
+
+       conf = get_multipath_config();
+       if (conf->uid_attrs)
+               need_merge = true;
+       put_multipath_config(conf);
+
+       return need_merge;
+}
+
+static bool
+uevent_can_discard_by_devpath(const char *devpath)
+{
+       static const char BLOCK[] = "/block/";
+       const char *tmp = strstr(devpath, BLOCK);
+
+       if (tmp == NULL) {
+               condlog(4, "no /block/ in '%s'", devpath);
+               return true;
+       }
+       tmp += sizeof(BLOCK) - 1;
+       if (*tmp == '\0')
+               /* just ".../block/" - discard */
+               return true;
+       /*
+        * If there are more path elements after ".../block/xyz",
+        * it's a partition - discard it; but don't discard ".../block/sda/".
+        */
+       tmp = strchr(tmp, '/');
+       return tmp != NULL && *(tmp + 1) != '\0';
+}
+
+bool
+uevent_can_discard(struct uevent *uev)
+{
+       struct config * conf;
+
+       if (uevent_can_discard_by_devpath(uev->devpath))
+               return true;
+
+       /*
+        * do not filter dm devices by devnode
+        */
+       if (!strncmp(uev->kernel, "dm-", 3))
+               return false;
+       /*
+        * filter paths devices by devnode
+        */
+       conf = get_multipath_config();
+       if (filter_devnode(conf->blist_devnode, conf->elist_devnode,
+                          uev->kernel) > 0) {
+               put_multipath_config(conf);
+               return true;
+       }
+       put_multipath_config(conf);
+
+       return false;
+}
+
+bool
+uevent_can_filter(struct uevent *earlier, struct uevent *later)
+{
+
+       /*
+        * filter earlier uvents if path has removed later. Eg:
+        * "add path1 |chang path1 |add path2 |remove path1"
+        * can filter as:
+        * "add path2 |remove path1"
+        * uevents "add path1" and "chang path1" are filtered out
+        */
+       if (!strcmp(earlier->kernel, later->kernel) &&
+               !strcmp(later->action, "remove") &&
+               strncmp(later->kernel, "dm-", 3)) {
+               return true;
+       }
+
+       /*
+        * filter change uvents if add uevents exist. Eg:
+        * "change path1| add path1 |add path2"
+        * can filter as:
+        * "add path1 |add path2"
+        * uevent "chang path1" is filtered out
+        */
+       if (!strcmp(earlier->kernel, later->kernel) &&
+               !strcmp(earlier->action, "change") &&
+               !strcmp(later->action, "add") &&
+               strncmp(later->kernel, "dm-", 3)) {
+               return true;
+       }
+
+       return false;
+}
+
+bool
+merge_need_stop(struct uevent *earlier, struct uevent *later)
+{
+       /*
+        * dm uevent do not try to merge with left uevents
+        */
+       if (!strncmp(later->kernel, "dm-", 3))
+               return true;
+
+       /*
+        * we can not make a jugement without wwid,
+        * so it is sensible to stop merging
+        */
+       if (!earlier->wwid || !later->wwid)
+               return true;
+       /*
+        * uevents merging stoped
+        * when we meet an opposite action uevent from the same LUN to AVOID
+        * "add path1 |remove path1 |add path2 |remove path2 |add path3"
+        * to merge as "remove path1, path2" and "add path1, path2, path3"
+        * OR
+        * "remove path1 |add path1 |remove path2 |add path2 |remove path3"
+        * to merge as "add path1, path2" and "remove path1, path2, path3"
+        * SO
+        * when we meet a non-change uevent from the same LUN
+        * with the same wwid and different action
+        * it would be better to stop merging.
+        */
+       if (!strcmp(earlier->wwid, later->wwid) &&
+           strcmp(earlier->action, later->action) &&
+           strcmp(earlier->action, "change") &&
+           strcmp(later->action, "change"))
+               return true;
+
+       return false;
+}
+
+bool
+uevent_can_merge(struct uevent *earlier, struct uevent *later)
+{
+       /* merge paths uevents
+        * whose wwids exsit and are same
+        * and actions are same,
+        * and actions are addition or deletion
+        */
+       if (earlier->wwid && later->wwid &&
+           !strcmp(earlier->wwid, later->wwid) &&
+           !strcmp(earlier->action, later->action) &&
+           strncmp(earlier->action, "change", 6) &&
+           strncmp(earlier->kernel, "dm-", 3)) {
+               return true;
+       }
+
+       return false;
 }
 
 void
-uevq_cleanup(struct list_head *tmpq)
+uevent_prepare(struct list_head *tmpq)
+{
+       struct uevent *uev, *tmp;
+
+       list_for_each_entry_reverse_safe(uev, tmp, tmpq, node) {
+               if (uevent_can_discard(uev)) {
+                       list_del_init(&uev->node);
+                       if (uev->udev)
+                               udev_device_unref(uev->udev);
+                       FREE(uev);
+                       continue;
+               }
+
+               if (strncmp(uev->kernel, "dm-", 3) &&
+                   uevent_need_merge())
+                       uevent_get_wwid(uev);
+       }
+}
+
+void
+uevent_filter(struct uevent *later, struct list_head *tmpq)
+{
+       struct uevent *earlier, *tmp;
+
+       list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) {
+               /*
+                * filter unnessary earlier uevents
+                * by the later uevent
+                */
+               if (uevent_can_filter(earlier, later)) {
+                       condlog(2, "uevent: %s-%s has filtered by uevent: %s-%s",
+                               earlier->kernel, earlier->action,
+                               later->kernel, later->action);
+
+                       list_del_init(&earlier->node);
+                       if (earlier->udev)
+                               udev_device_unref(earlier->udev);
+                       FREE(earlier);
+               }
+       }
+}
+
+void
+uevent_merge(struct uevent *later, struct list_head *tmpq)
+{
+       struct uevent *earlier, *tmp;
+
+       list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) {
+               if (merge_need_stop(earlier, later))
+                       break;
+               /*
+                * merge earlier uevents to the later uevent
+                */
+               if (uevent_can_merge(earlier, later)) {
+                       condlog(2, "merged uevent: %s-%s-%s with uevent: %s-%s-%s",
+                               earlier->action, earlier->kernel, earlier->wwid,
+                               later->action, later->kernel, later->wwid);
+
+                       list_move(&earlier->node, &later->merge_node);
+               }
+       }
+}
+
+void
+merge_uevq(struct list_head *tmpq)
+{
+       struct uevent *later;
+
+       uevent_prepare(tmpq);
+       list_for_each_entry_reverse(later, tmpq, node) {
+               uevent_filter(later, tmpq);
+               if(uevent_need_merge())
+                       uevent_merge(later, tmpq);
+       }
+}
+
+void
+service_uevq(struct list_head *tmpq)
 {
        struct uevent *uev, *tmp;
 
        list_for_each_entry_safe(uev, tmp, tmpq, node) {
                list_del_init(&uev->node);
+
+               if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data))
+                       condlog(0, "uevent trigger error");
+
+               uevq_cleanup(&uev->merge_node);
+
+               if (uev->udev)
+                       udev_device_unref(uev->udev);
                FREE(uev);
        }
 }
 
+static void uevent_cleanup(void *arg)
+{
+       struct udev *udev = arg;
+
+       condlog(3, "Releasing uevent_listen() resources");
+       udev_unref(udev);
+}
+
 /*
  * Service the uevent queue.
  */
@@ -142,6 +414,7 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data),
                pthread_mutex_unlock(uevq_lockp);
                if (!my_uev_trigger)
                        break;
+               merge_uevq(&uevq_tmp);
                service_uevq(&uevq_tmp);
        }
        condlog(3, "Terminating uev service queue");
@@ -442,11 +715,43 @@ struct uevent *uevent_from_udev_device(struct udev_device *dev)
        return uev;
 }
 
+bool uevent_burst(struct timeval *start_time, int events)
+{
+       struct timeval diff_time, end_time;
+       unsigned long speed;
+       unsigned long eclipse_ms;
+
+       if(events > MAX_ACCUMULATION_COUNT) {
+               condlog(2, "burst got %u uevents, too much uevents, stopped", events);
+               return false;
+       }
+
+       gettimeofday(&end_time, NULL);
+       timersub(&end_time, start_time, &diff_time);
+
+       eclipse_ms = diff_time.tv_sec * 1000 + diff_time.tv_usec / 1000;
+
+       if (eclipse_ms == 0)
+               return true;
+
+       if (eclipse_ms > MAX_ACCUMULATION_TIME) {
+               condlog(2, "burst continued %lu ms, too long time, stopped", eclipse_ms);
+               return false;
+       }
+
+       speed = (events * 1000) / eclipse_ms;
+       if (speed > MIN_BURST_SPEED)
+               return true;
+
+       return false;
+}
+
 int uevent_listen(struct udev *udev)
 {
        int err = 2;
        struct udev_monitor *monitor = NULL;
        int fd, socket_flags, events;
+       struct timeval start_time;
        int need_failback = 1;
        int timeout = 30;
        LIST_HEAD(uevlisten_tmp);
@@ -500,6 +805,7 @@ int uevent_listen(struct udev *udev)
        }
 
        events = 0;
+       gettimeofday(&start_time, NULL);
        while (1) {
                struct uevent *uev;
                struct udev_device *dev;
@@ -514,7 +820,7 @@ int uevent_listen(struct udev *udev)
                errno = 0;
                fdcount = poll(&ev_poll, 1, poll_timeout);
                if (fdcount && ev_poll.revents & POLLIN) {
-                       timeout = 0;
+                       timeout = uevent_burst(&start_time, events + 1) ? 1 : 0;
                        dev = udev_monitor_receive_device(monitor);
                        if (!dev) {
                                condlog(0, "failed getting udev device");
@@ -547,6 +853,7 @@ int uevent_listen(struct udev *udev)
                        pthread_mutex_unlock(uevq_lockp);
                        events = 0;
                }
+               gettimeofday(&start_time, NULL);
                timeout = 30;
        }
        need_failback = 0;
@@ -559,8 +866,7 @@ out:
        return err;
 }
 
-extern int
-uevent_get_major(struct uevent *uev)
+int uevent_get_major(struct uevent *uev)
 {
        char *p, *q;
        int i, major = -1;
@@ -579,8 +885,7 @@ uevent_get_major(struct uevent *uev)
        return major;
 }
 
-extern int
-uevent_get_minor(struct uevent *uev)
+int uevent_get_minor(struct uevent *uev)
 {
        char *p, *q;
        int i, minor = -1;
@@ -599,8 +904,7 @@ uevent_get_minor(struct uevent *uev)
        return minor;
 }
 
-extern int
-uevent_get_disk_ro(struct uevent *uev)
+int uevent_get_disk_ro(struct uevent *uev)
 {
        char *p, *q;
        int i, ro = -1;
@@ -619,8 +923,7 @@ uevent_get_disk_ro(struct uevent *uev)
        return ro;
 }
 
-extern char *
-uevent_get_dm_name(struct uevent *uev)
+char *uevent_get_dm_name(struct uevent *uev)
 {
        char *p = NULL;
        int i;
index 9d22dcd..61a4207 100644 (file)
@@ -17,11 +17,13 @@ struct udev;
 
 struct uevent {
        struct list_head node;
+       struct list_head merge_node;
        struct udev_device *udev;
        char buffer[HOTPLUG_BUFFER_SIZE + OBJECT_SIZE];
        char *devpath;
        char *action;
        char *kernel;
+       char *wwid;
        unsigned long seqnum;
        char *envp[HOTPLUG_NUM_ENVP];
 };
index 0a136b4..b90cd8b 100644 (file)
@@ -4,14 +4,18 @@
 #include <pthread.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
+#include <dirent.h>
 #include <unistd.h>
+#include <errno.h>
 
 #include "debug.h"
 #include "memory.h"
 #include "checkers.h"
 #include "vector.h"
 #include "structs.h"
+#include "log.h"
 
 size_t
 strchop(char *str)
@@ -145,8 +149,7 @@ size_t strlcat(char *dst, const char *src, size_t size)
        return bytes;
 }
 
-extern int
-devt2devname (char *devname, int devname_len, char *devt)
+int devt2devname(char *devname, int devname_len, char *devt)
 {
        FILE *fd;
        unsigned int tmpmaj, tmpmin, major, minor;
@@ -262,6 +265,48 @@ dev_t parse_devt(const char *dev_t)
        return makedev(maj, min);
 }
 
+char *parse_uid_attribute_by_attrs(char *uid_attrs, char *path_dev)
+{
+       char *uid_attribute;
+       char *uid_attr_record;
+       char *dev;
+       char *attr;
+       char *tmp;
+       int  count;
+
+       if(!uid_attrs || !path_dev)
+               return NULL;
+
+       count = get_word(uid_attrs, &uid_attr_record);
+       while (uid_attr_record) {
+               tmp = strrchr(uid_attr_record, ':');
+               if (!tmp) {
+                       free(uid_attr_record);
+                       if (!count)
+                               break;
+                       uid_attrs += count;
+                       count = get_word(uid_attrs, &uid_attr_record);
+                       continue;
+               }
+               dev = uid_attr_record;
+               attr = tmp + 1;
+               *tmp = '\0';
+
+               if(!strncmp(path_dev, dev, strlen(dev))) {
+                       uid_attribute = STRDUP(attr);
+                       free(uid_attr_record);
+                       return uid_attribute;
+               }
+
+               free(uid_attr_record);
+               if (!count)
+                       break;
+               uid_attrs += count;
+               count = get_word(uid_attrs, &uid_attr_record);
+       }
+       return NULL;
+}
+
 void
 setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached)
 {
@@ -279,3 +324,59 @@ setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached)
                assert(ret == 0);
        }
 }
+
+int systemd_service_enabled_in(const char *dev, const char *prefix)
+{
+       char path[PATH_SIZE], file[PATH_SIZE], service[PATH_SIZE];
+       DIR *dirfd;
+       struct dirent *d;
+       int found = 0;
+
+       snprintf(service, PATH_SIZE, "multipathd.service");
+       snprintf(path, PATH_SIZE, "%s/systemd/system", prefix);
+       condlog(3, "%s: checking for %s in %s", dev, service, path);
+
+       dirfd = opendir(path);
+       if (dirfd == NULL)
+               return 0;
+
+       while ((d = readdir(dirfd)) != NULL) {
+               char *p;
+               struct stat stbuf;
+
+               if ((strcmp(d->d_name,".") == 0) ||
+                   (strcmp(d->d_name,"..") == 0))
+                       continue;
+
+               if (strlen(d->d_name) < 6)
+                       continue;
+
+               p = d->d_name + strlen(d->d_name) - 6;
+               if (strcmp(p, ".wants"))
+                       continue;
+               snprintf(file, PATH_SIZE, "%s/%s/%s",
+                        path, d->d_name, service);
+               if (stat(file, &stbuf) == 0) {
+                       condlog(3, "%s: found %s", dev, file);
+                       found++;
+                       break;
+               }
+       }
+       closedir(dirfd);
+
+       return found;
+}
+
+int systemd_service_enabled(const char *dev)
+{
+       int found = 0;
+
+       found = systemd_service_enabled_in(dev, "/etc");
+       if (!found)
+               found = systemd_service_enabled_in(dev, "/usr/lib");
+       if (!found)
+               found = systemd_service_enabled_in(dev, "/lib");
+       if (!found)
+               found = systemd_service_enabled_in(dev, "/run");
+       return found;
+}
index f3b37ee..b087e32 100644 (file)
@@ -12,7 +12,9 @@ size_t strlcat(char *dst, const char *src, size_t size);
 int devt2devname (char *, int, char *);
 dev_t parse_devt(const char *dev_t);
 char *convert_dev(char *dev, int is_path_device);
+char *parse_uid_attribute_by_attrs(char *uid_attrs, char *path_dev);
 void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached);
+int systemd_service_enabled(const char *dev);
 
 #define safe_sprintf(var, format, args...)     \
        snprintf(var, sizeof(var), format, ##args) >= sizeof(var)
index b158a56..7e5a144 100644 (file)
 #include "memory.h"
 #include "uxsock.h"
 #include "debug.h"
+
+/*
+ * Code is similar with mpath_recv_reply() with data size limitation
+ * and debug-able malloc.
+ * When limit == 0, it means no limit on data size, used for socket client
+ * to receiving data from multipathd.
+ */
+static int _recv_packet(int fd, char **buf, unsigned int timeout,
+                       ssize_t limit);
+
 /*
  * create a unix domain socket and start listening on it
  * return a file descriptor open on the socket
@@ -74,93 +84,49 @@ int ux_socket_listen(const char *name)
 }
 
 /*
- * keep writing until it's all sent
- */
-size_t write_all(int fd, const void *buf, size_t len)
-{
-       size_t total = 0;
-
-       while (len) {
-               ssize_t n = send(fd, buf, len, MSG_NOSIGNAL);
-               if (n < 0) {
-                       if ((errno == EINTR) || (errno == EAGAIN))
-                               continue;
-                       return total;
-               }
-               if (!n)
-                       return total;
-               buf = n + (char *)buf;
-               len -= n;
-               total += n;
-       }
-       return total;
-}
-
-/*
- * keep reading until its all read
- */
-ssize_t read_all(int fd, void *buf, size_t len, unsigned int timeout)
-{
-       size_t total = 0;
-       ssize_t n;
-       int ret;
-       struct pollfd pfd;
-
-       while (len) {
-               pfd.fd = fd;
-               pfd.events = POLLIN;
-               ret = poll(&pfd, 1, timeout);
-               if (!ret) {
-                       return -ETIMEDOUT;
-               } else if (ret < 0) {
-                       if (errno == EINTR)
-                               continue;
-                       return -errno;
-               } else if (!(pfd.revents & POLLIN))
-                       continue;
-               n = read(fd, buf, len);
-               if (n < 0) {
-                       if ((errno == EINTR) || (errno == EAGAIN))
-                               continue;
-                       return -errno;
-               }
-               if (!n)
-                       return total;
-               buf = n + (char *)buf;
-               len -= n;
-               total += n;
-       }
-       return total;
-}
-
-/*
  * send a packet in length prefix format
  */
 int send_packet(int fd, const char *buf)
 {
-       return mpath_send_cmd(fd, buf);
+       if (mpath_send_cmd(fd, buf) < 0)
+               return -errno;
+       return 0;
 }
 
-/*
- * receive a packet in length prefix format
- */
-int recv_packet(int fd, char **buf, unsigned int timeout)
+static int _recv_packet(int fd, char **buf, unsigned int timeout, ssize_t limit)
 {
-       int err;
-       ssize_t len;
+       int err = 0;
+       ssize_t len = 0;
 
        *buf = NULL;
        len = mpath_recv_reply_len(fd, timeout);
-       if (len <= 0)
+       if (len == 0)
                return len;
+       if (len < 0)
+               return -errno;
+       if ((limit > 0) && (len > limit))
+               return -EINVAL;
        (*buf) = MALLOC(len);
        if (!*buf)
                return -ENOMEM;
        err = mpath_recv_reply_data(fd, *buf, len, timeout);
-       if (err) {
+       if (err != 0) {
                FREE(*buf);
                (*buf) = NULL;
-               return err;
+               return -errno;
        }
-       return 0;
+       return err;
+}
+
+/*
+ * receive a packet in length prefix format
+ */
+int recv_packet(int fd, char **buf, unsigned int timeout)
+{
+       return _recv_packet(fd, buf, timeout, 0 /* no limit */);
+}
+
+int recv_packet_from_client(int fd, char **buf, unsigned int timeout)
+{
+       return _recv_packet(fd, buf, timeout, _MAX_CMD_LEN);
 }
index c1cf81f..8e7401d 100644 (file)
@@ -2,5 +2,12 @@
 int ux_socket_listen(const char *name);
 int send_packet(int fd, const char *buf);
 int recv_packet(int fd, char **buf, unsigned int timeout);
-size_t write_all(int fd, const void *buf, size_t len);
-ssize_t read_all(int fd, void *buf, size_t len, unsigned int timeout);
+
+#define _MAX_CMD_LEN           512
+
+/*
+ * Used for receiving socket command from untrusted socket client where data
+ * size is restricted to 512(_MAX_CMD_LEN) at most.
+ * Return -EINVAL if data length requested by client exceeded the _MAX_CMD_LEN.
+ */
+int recv_packet_from_client(int fd, char **buf, unsigned int timeout);
index f00476d..4b7c32a 100644 (file)
@@ -20,8 +20,8 @@
 #ifndef _VERSION_H
 #define _VERSION_H
 
-#define VERSION_CODE 0x000604
-#define DATE_CODE    0x030b10
+#define VERSION_CODE 0x000700
+#define DATE_CODE    0x040b11
 
 #define PROG    "multipath-tools"
 
diff --git a/multipath/01_udev b/multipath/01_udev
deleted file mode 100755 (executable)
index c4e4c53..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/sh
-#
-cp /sbin/udev $INITRDDIR/sbin/hotplug
-cp /sbin/udevstart $INITRDDIR/sbin/
-cp /bin/mountpoint $INITRDDIR/bin/
-cp /bin/readlink $INITRDDIR/bin/
-
-PROGS="/sbin/udev /sbin/udevstart /bin/mountpoint /bin/readlink"
-LIBS=`ldd $PROGS | grep -v linux-gate.so | sort -u | \
-awk '{print $3}'`
-for i in $LIBS
-do
-       mkdir -p `dirname $INITRDDIR/$i`
-       cp $i $INITRDDIR/$i
-done
-
-#
-# config files
-#
-if [ -d /etc/dev.d ]
-then
-       cp -a /etc/dev.d $INITRDDIR/etc/
-fi
-
-if [ -d /etc/udev ]
-then
-       cp -a /etc/udev $INITRDDIR/etc/
-fi
-
-#
-# run udev from initrd
-#
-cat <<EOF >| $INITRDDIR/scripts/10_udev.sh
-
-cd /
-mount -nt proc proc proc
-mount -nt sysfs sysfs sys
-mount -nt tmpfs tmpfs dev || mount -nt ramfs ramfs dev
-mount -nt tmpfs tmpfs tmp || mount -nt ramfs ramfs tmp
-
-#modprobe dm-mod
-#modprobe dm-multipath
-/sbin/udevstart
-
-umount -n tmp
-umount -n sys
-umount -n proc
-
-sleep 2
-EOF
diff --git a/multipath/02_multipath b/multipath/02_multipath
deleted file mode 100755 (executable)
index 523f0ef..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-#
-# store the multipath tool in the initrd
-# hotplug & udev will take care of calling it when appropriate
-# this tool is statically linked against klibc : no additional libs
-#
-cp /sbin/multipath $INITRDDIR/sbin
-cp /sbin/kpartx $INITRDDIR/sbin
-
-#
-# feed the dependencies too
-# scsi_id is dynamicaly linked, so store the libs too
-#
-cp /lib/udev/scsi_id $INITRDDIR/lib/udev/
-cp /bin/mountpoint $INITRDDIR/bin
-
-PROGS="/lib/udev/scsi_id /bin/mountpoint"
-LIBS=`ldd $PROGS | grep -v linux-gate.so | sort -u | \
-awk '{print $3}'`
-for i in $LIBS
-do
-       mkdir -p `dirname $INITRDDIR/$i`
-       cp $i $INITRDDIR/$i
-done
-
-#
-# config file ?
-#
-if [ -f /etc/multipath.conf ]
-then
-       cp /etc/multipath.conf $INITRDDIR/etc/
-fi
index 5559af3..b131a10 100644 (file)
@@ -2,39 +2,66 @@ ACTION!="add|change", GOTO="mpath_end"
 ENV{DM_UDEV_RULES_VSN}!="?*", GOTO="mpath_end"
 ENV{DM_UUID}!="mpath-?*", GOTO="mpath_end"
 
+IMPORT{db}="DM_DISABLE_OTHER_RULES_FLAG_OLD"
+IMPORT{db}="MPATH_DEVICE_READY"
+
+# If this uevent didn't come from dm, don't try to update the
+# device state
+ENV{DM_COOKIE}!="?*", ENV{DM_ACTION}!="PATH_*", IMPORT{db}="DM_UDEV_DISABLE_OTHER_RULES_FLAG", IMPORT{db}="DM_NOSCAN", GOTO="scan_import"
+
+ENV{.MPATH_DEVICE_READY_OLD}="$env{MPATH_DEVICE_READY}"
+
+# multipath sets DM_SUBSYSTEM_UDEV_FLAG2 when it reloads a
+# table with no active devices. If this happens, mark the
+# device not ready
+ENV{DM_SUBSYSTEM_UDEV_FLAG2}=="1", ENV{MPATH_DEVICE_READY}="0",\
+       GOTO="mpath_action"
+
+# If the last path has failed mark the device not ready
+ENV{DM_ACTION}=="PATH_FAILED", ENV{DM_NR_VALID_PATHS}=="0",\
+       ENV{MPATH_DEVICE_READY}="0", GOTO="mpath_action"
+
+# Don't mark a device ready on a PATH_FAILED event. even if
+# DM_NR_VALID_PATHS is greater than 0. Just keep the existing
+# value
+ENV{DM_ACTION}=="PATH_FAILED", GOTO="mpath_action"
+
+# This event is either a PATH_REINSTATED or a table reload where
+# there are active paths. Mark the device ready
+ENV{MPATH_DEVICE_READY}=""
+
+LABEL="mpath_action"
+# DM_SUBSYSTEM_UDEV_FLAG0 is the "RELOAD" flag for multipath subsystem.
+# Drop the DM_ACTIVATION flag here as mpath reloads tables if any of its
+# paths are lost/recovered. For any stack above the mpath device, this is not
+# something that should be reacted upon since it would be useless extra work.
+# It's exactly mpath's job to provide *seamless* device access to any of the
+# paths that are available underneath.
+ENV{DM_SUBSYSTEM_UDEV_FLAG0}=="1", ENV{DM_ACTIVATION}="0"
+
 # Do not initiate scanning if no path is available,
 # otherwise there would be a hang or IO error on access.
 # We'd like to avoid this, especially within udev processing.
-ENV{DM_NR_VALID_PATHS}!="?*", IMPORT{db}="DM_NR_VALID_PATHS"
-ENV{DM_NR_VALID_PATHS}!="0", GOTO="mpath_blkid_end"
-ENV{ID_FS_TYPE}!="?*", IMPORT{db}="ID_FS_TYPE"
-ENV{ID_FS_USAGE}!="?*", IMPORT{db}="ID_FS_USAGE"
-ENV{ID_FS_UUID}!="?*", IMPORT{db}="ID_FS_UUID"
-ENV{ID_FS_ENC}!="?*", IMPORT{db}="ID_FS_UUID_ENC"
-ENV{ID_FS_VERSION}!="?*", IMPORT{db}="ID_FS_VERSION"
-LABEL="mpath_blkid_end"
+ENV{MPATH_DEVICE_READY}=="0", ENV{DM_NOSCAN}="1"
 
 # Also skip all foreign rules if no path is available.
 # Remember the original value of DM_DISABLE_OTHER_RULES_FLAG
 # and restore it back once we have at least one path available.
-IMPORT{db}="DM_DISABLE_OTHER_RULES_FLAG_OLD"
-ENV{DM_ACTION}=="PATH_FAILED",\
-       ENV{DM_NR_VALID_PATHS}=="0",\
+ENV{MPATH_DEVICE_READY}=="0", ENV{.MPATH_DEVICE_READY_OLD}!="0",\
        ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}=="",\
        ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="$env{DM_UDEV_DISABLE_OTHER_RULES_FLAG}",\
        ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1"
-ENV{DM_ACTION}=="PATH_REINSTATED",\
-       ENV{DM_NR_VALID_PATHS}=="1",\
+ENV{MPATH_DEVICE_READY}!="0", ENV{.MPATH_DEVICE_READY_OLD}=="0",\
        ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{DM_DISABLE_OTHER_RULES_FLAG_OLD}",\
        ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="",\
        ENV{DM_ACTIVATION}="1"
 
-# DM_SUBSYSTEM_UDEV_FLAG0 is the "RELOAD" flag for multipath subsystem.
-# Drop the DM_ACTIVATION flag here as mpath reloads tables if any of its
-# paths are lost/recovered. For any stack above the mpath device, this is not
-# something that should be reacted upon since it would be useless extra work.
-# It's exactly mpath's job to provide *seamless* device access to any of the
-# paths that are available underneath.
-ENV{DM_SUBSYSTEM_UDEV_FLAG0}=="1", ENV{DM_ACTIVATION}="0"
+LABEL="scan_import"
+ENV{DM_NOSCAN}!="1", GOTO="mpath_end"
+ENV{ID_FS_TYPE}!="?*", IMPORT{db}="ID_FS_TYPE"
+ENV{ID_FS_USAGE}!="?*", IMPORT{db}="ID_FS_USAGE"
+ENV{ID_FS_UUID}!="?*", IMPORT{db}="ID_FS_UUID"
+ENV{ID_FS_ENC}!="?*", IMPORT{db}="ID_FS_UUID_ENC"
+ENV{ID_FS_VERSION}!="?*", IMPORT{db}="ID_FS_VERSION"
 
 LABEL="mpath_end"
index 06add30..4174d43 100644 (file)
@@ -103,8 +103,8 @@ usage (char * progname)
        fprintf (stderr, VERSION_STRING);
        fprintf (stderr, "Usage:\n");
        fprintf (stderr, "  %s [-a|-c|-w|-W] [-d] [-r] [-i] [-v lvl] [-p pol] [-b fil] [-q] [dev]\n", progname);
-       fprintf (stderr, "  %s -l|-ll|-f [-v lvl] [-b fil] [dev]\n", progname);
-       fprintf (stderr, "  %s -F [-v lvl]\n", progname);
+       fprintf (stderr, "  %s -l|-ll|-f [-v lvl] [-b fil] [-R num] [dev]\n", progname);
+       fprintf (stderr, "  %s -F [-v lvl] [-R num]\n", progname);
        fprintf (stderr, "  %s -t\n", progname);
        fprintf (stderr, "  %s -h\n", progname);
        fprintf (stderr,
@@ -137,6 +137,7 @@ usage (char * progname)
                "          . 1 print created devmap names only\n"
                "          . 2 default verbosity\n"
                "          . 3 print debug information\n"
+               "  -R num  number of times to retry removes of in-use devices\n"
                "  dev     action limited to:\n"
                "          . multipath named 'dev' (ex: mpath0) or\n"
                "          . multipath whose wwid is 'dev' (ex: 60051..)\n"
@@ -265,7 +266,8 @@ get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid)
  *   1: Failure
  */
 static int
-configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
+configure (struct config *conf, enum mpath_cmds cmd,
+          enum devtypes dev_type, char *devpath)
 {
        vector curmp = NULL;
        vector pathvec = NULL;
@@ -274,7 +276,6 @@ configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
        int di_flag = 0;
        char * refwwid = NULL;
        char * dev = NULL;
-       struct config *conf;
 
        /*
         * allocate core vectors to store paths and multipaths
@@ -294,7 +295,6 @@ configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
        /*
         * if we have a blacklisted device parameter, exit early
         */
-       conf = get_multipath_config();
        if (dev && (dev_type == DEV_DEVNODE ||
                    dev_type == DEV_UEVENT) &&
            cmd != CMD_REMOVE_WWID &&
@@ -303,10 +303,9 @@ configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
                if (cmd == CMD_VALID_PATH)
                        printf("%s is not a valid multipath device path\n",
                               devpath);
-               put_multipath_config(conf);
                goto out;
        }
-       put_multipath_config(conf);
+
        /*
         * scope limiting must be translated into a wwid
         * failing the translation is fatal (by policy)
@@ -368,7 +367,7 @@ configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
 
        if (cmd == CMD_LIST_LONG)
                /* extended path info '-ll' */
-               di_flag |= DI_SYSFS | DI_CHECKER;
+               di_flag |= DI_SYSFS | DI_CHECKER | DI_SERIAL;
        else if (cmd == CMD_LIST_SHORT)
                /* minimum path info '-l' */
                di_flag |= DI_SYSFS;
@@ -514,6 +513,7 @@ main (int argc, char *argv[])
        enum devtypes dev_type = DEV_NONE;
        char *dev = NULL;
        struct config *conf;
+       int retries = -1;
 
        udev = udev_new();
        logsink = 0;
@@ -522,7 +522,7 @@ main (int argc, char *argv[])
                exit(1);
        multipath_conf = conf;
        conf->retrigger_tries = 0;
-       while ((arg = getopt(argc, argv, ":adchl::FfM:v:p:b:BritquwW")) != EOF ) {
+       while ((arg = getopt(argc, argv, ":adchl::FfM:v:p:b:BrR:itquwW")) != EOF ) {
                switch(arg) {
                case 1: printf("optarg : %s\n",optarg);
                        break;
@@ -578,7 +578,7 @@ main (int argc, char *argv[])
                        }
                        break;
                case 'r':
-                       conf->force_reload = 1;
+                       conf->force_reload = FORCE_RELOAD_YES;
                        break;
                case 'i':
                        conf->ignore_wwids = 1;
@@ -602,6 +602,9 @@ main (int argc, char *argv[])
                case 'a':
                        cmd = CMD_ADD_WWID;
                        break;
+               case 'R':
+                       retries = atoi(optarg);
+                       break;
                case ':':
                        fprintf(stderr, "Missing option argument\n");
                        usage(argv[0]);
@@ -616,6 +619,16 @@ main (int argc, char *argv[])
                }
        }
 
+       /*
+        * FIXME: new device detection with find_multipaths currently
+        * doesn't work reliably.
+        */
+       if (cmd ==  CMD_VALID_PATH &&
+           conf->find_multipaths && conf->ignore_wwids) {
+               condlog(2, "ignoring -i flag because find_multipath is set in multipath.conf");
+               conf->ignore_wwids = 0;
+       }
+
        if (getuid() != 0) {
                fprintf(stderr, "need to be root\n");
                exit(1);
@@ -677,11 +690,14 @@ main (int argc, char *argv[])
 
                fd = mpath_connect();
                if (fd == -1) {
-                       printf("%s is not a valid multipath device path\n",
-                               dev);
-                       goto out;
-               }
-               mpath_disconnect(fd);
+                       condlog(3, "%s: daemon is not running", dev);
+                       if (!systemd_service_enabled(dev)) {
+                               printf("%s is not a valid "
+                                      "multipath device path\n", dev);
+                               goto out;
+                       }
+               } else
+                       mpath_disconnect(fd);
        }
        if (cmd == CMD_REMOVE_WWID && !dev) {
                condlog(0, "the -w option requires a device");
@@ -708,19 +724,21 @@ main (int argc, char *argv[])
                vector_free(curmp);
                goto out;
        }
+       if (retries < 0)
+               retries = conf->remove_retries;
        if (conf->remove == FLUSH_ONE) {
                if (dev_type == DEV_DEVMAP) {
-                       r = dm_suspend_and_flush_map(dev);
+                       r = dm_suspend_and_flush_map(dev, retries);
                } else
                        condlog(0, "must provide a map name to remove");
 
                goto out;
        }
        else if (conf->remove == FLUSH_ALL) {
-               r = dm_flush_maps();
+               r = dm_flush_maps(retries);
                goto out;
        }
-       while ((r = configure(cmd, dev_type, dev)) < 0)
+       while ((r = configure(conf, cmd, dev_type, dev)) < 0)
                condlog(3, "restart multipath configuration process");
 
 out:
index f0b1ff0..b9436e5 100644 (file)
@@ -28,6 +28,8 @@ multipath \- Device mapper target autoconfig.
 .RB [\| \-h | \-l | \-ll | \-f | \-t | \-F | \-B | \-c | \-q | \|-r | \|-i | \-a | \|-u | \-w | \-W \|]
 .RB [\| \-p\ \c
 .IR failover | multibus | group_by_serial | group_by_prio | group_by_node_name \|]
+.RB [\| \-R\ \c
+.IR retries \|]
 .RB [\| device \|]
 .
 .
@@ -155,6 +157,11 @@ in \fI/sys/class/fc_transport/target*/node_name\fR.
 Existing maps are not modified.
 .
 .TP
+.BI \-R " retries"
+Number of times to retry flushing multipath devices that are in-use. The default
+is \fI0\fR.
+.
+.TP
 .BI device
 Update only the devmap specified by
 .IR device ,
index b7d7e59..5939688 100644 (file)
@@ -7,7 +7,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH MULTIPATH.CONF 5 2016-10-18 "Linux"
+.TH MULTIPATH.CONF 5 2016-11-27 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
@@ -108,7 +108,7 @@ Default verbosity. Higher values increase the verbosity level. Valid
 levels are between 0 and 6.
 .RS
 .TP
-Default value is: \fB2\fR
+The default is: \fB2\fR
 .RE
 .
 .
@@ -120,7 +120,7 @@ This value will be overridden by the \fIWatchdogSec\fR
 setting in the multipathd.service definition if systemd is used.
 .RS
 .TP
-Default value is: \fB5\fR
+The default is: \fB5\fR
 .RE
 .
 .
@@ -129,7 +129,7 @@ Default value is: \fB5\fR
 Maximal interval between two path checks in seconds.
 .RS
 .TP
-Default value is: \fB4 * polling_interval\fR
+The default is: \fB4 * polling_interval\fR
 .RE
 .
 .
@@ -141,17 +141,17 @@ device, not the underlying block devices. Possible values are
 \fIyes\fR and \fIno\fR.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
 .TP
 .B multipath_dir
-Directory where the dynamic shared objects are stored.
+Directory where the dynamic shared objects are stored. Defined at compile time,
+commonly \fI/lib64/multipath/\fR or \fI/lib/multipath/\fR.
 .RS
 .TP
-Default value is: \fB<system dependent>\fR. Defined at compile time, commonly
-\fI/lib64/multipath/\fR.
+The default is: \fB<system dependent>\fR
 .RE
 .
 .
@@ -166,17 +166,15 @@ Loop through every path in the path group, sending the same amount of I/O to
 each. Some aspects of behavior can be controlled with the attributes:
 \fIrr_min_io\fR, \fIrr_min_io_rq\fR and \fIrr_weight\fR.
 .TP
-.\" XXX
 .I "queue-length 0"
-(Since ??? kernel) Choose the path for the next bunch of I/O based on the amount
+(Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on the amount
 of outstanding I/O to the path.
 .TP
-.\" XXX
 .I "service-time 0"
-(Since ??? kernel) Choose the path for the next bunch of I/O based on the amount
+(Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on the amount
 of outstanding I/O to the path and its relative throughput.
 .TP
-Default value is: \fBservice-time 0\fR
+The default is: \fBservice-time 0\fR
 .RE
 .
 .
@@ -204,7 +202,25 @@ per-multipath option in the configuration file.
 One priority group per target node name. Target node names are fetched
 in \fI/sys/class/fc_transport/target*/node_name\fR.
 .TP
-Default value is: \fBfailover\fR
+The default is: \fBfailover\fR
+.RE
+.
+.
+.TP
+.B uid_attrs
+The udev attribute providing a unique path identifier for corresponding
+type of path devices. If this field is configured and matched with type
+of device, it would override any other methods providing for device
+unique identifier in config file, and it would activate merging uevents
+according to the identifier to promote effiecncy in processing uevents.
+It has no default value, if you want to identify path by udev attribute
+and to activate merging uevents for SCSI and DASD devices, you can set
+its value as:
+.RS
+.TP
+\fBuid_attrs "sd:ID_SERIAL dasd:ID_UID"\fR
+.TP
+The default is: \fB<unset>\fR
 .RE
 .
 .
@@ -213,9 +229,9 @@ Default value is: \fBfailover\fR
 The udev attribute providing a unique path identifier.
 .RS
 .TP
-Default value is: for SCSI devices \fBID_SERIAL\fR
+The default is: for SCSI devices \fBID_SERIAL\fR
 .TP
-Default value is: for DASD devices \fBID_UID\fR
+The default is: for DASD devices \fBID_UID\fR
 .RE
 .
 .
@@ -285,7 +301,7 @@ priority provided as argument. Requires prio_args keyword.
 .\" XXX
 ???. Requires prio_args keyword.
 .TP
-Default value is: \fBconst\fR
+The default is: \fBconst\fR
 .RE
 .
 .
@@ -321,6 +337,10 @@ these values can be looked up through sysfs or by running \fImultipathd show pat
 If \fIexclusive_pref_bit\fR is set, paths with the \fIpreferred path\fR bit
 set will always be in their own path group.
 .TP
+.I sysfs
+If \fIexclusive_pref_bit\fR is set, paths with the \fIpreferred path\fR bit
+set will always be in their own path group.
+.TP
 .I datacore
 .\" XXX
 \fIpreferredsds\fR ???.
@@ -329,7 +349,7 @@ set will always be in their own path group.
 .\" XXX
 \fIpreferredip\fR ???.
 .TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
 .RE
 .
 .
@@ -362,7 +382,7 @@ Disable automatic partitions generation via kpartx.
 Where <mode> can be \fIbio\fR, \fIrq\fR or \fImq\fR. Which corresponds to
 bio-based, request_fn rq-based, and blk-mq rq-based respectively.
 .TP
-Default value is: \fB0\fR
+The default is: \fB0\fR
 .RE
 .
 .
@@ -406,7 +426,7 @@ Check the path state for HP/COMPAQ Smart Array(CCISS) controllers.
 .I rbd
 Check if the path is in the Ceph blacklist and remap the path if it is.
 .TP
-Default value is: \fBtur\fR
+The default is: \fBtur\fR
 .RE
 .
 .
@@ -415,7 +435,7 @@ Default value is: \fBtur\fR
 The \fIuser_friendly_names\fR prefix.
 .RS
 .TP
-Default value is: \fBmpath\fR
+The default is: \fBmpath\fR
 .RE
 .
 .
@@ -442,18 +462,18 @@ another node requested the failover.
 .I values > 0
 Deferred failback (time to defer in seconds).
 .TP
-Default value is: \fBmanual\fR
+The default is: \fBmanual\fR
 .RE
 .
 .
 .TP
 .B  rr_min_io
 Number of I/O requests to route to a path before switching to the next in the
-same path group. This is only for \fIBlock I/O\fR(BIO) based multipath and 
+same path group. This is only for \fIBlock I/O\fR(BIO) based multipath and
 only apply to \fIround-robin\fR path_selector.
 .RS
 .TP
-Default value is: \fB1000\fR
+The default is: \fB1000\fR
 .RE
 .
 .
@@ -464,7 +484,7 @@ same path group. This is only for \fIRequest\fR based multipath and
 only apply to \fIround-robin\fR path_selector.
 .RS
 .TP
-Default value is: \fB1\fR
+The default is: \fB1\fR
 .RE
 .
 .
@@ -478,7 +498,7 @@ maximum number of open fds is taken from the calling process. It is usually
 if that number is greated than 1024.
 .RS
 .TP
-Default value is: \fBmax\fR
+The default is: \fBmax\fR
 .RE
 .
 .
@@ -492,7 +512,7 @@ or
 Only apply to \fIround-robin\fR path_selector.
 .RS
 .TP
-Default value is: \fBuniform\fR
+The default is: \fBuniform\fR
 .RE
 .
 .
@@ -512,7 +532,7 @@ for never stop I/O queueing. Similar to \fIqueue_if_no_path\fR.
 .TP
 See KNOWN ISSUES.
 .TP
-Default value is: \fBfail\fR
+The default is: \fBfail\fR
 .RE
 .
 .
@@ -530,7 +550,7 @@ cannot be told to stop queueing I/O. Setting queue_without_daemon to
 , avoids this problem.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -540,7 +560,7 @@ Specify the timeout to use for path checkers and prioritizers that issue SCSI
 commands with an explicit timeout, in seconds.
 .RS
 .TP
-Default value is: in \fB/sys/block/sd<x>/device/timeout\fR
+The default is: in \fB/sys/block/sd<x>/device/timeout\fR
 .RE
 .
 .
@@ -552,7 +572,7 @@ If set to
 deleted.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -567,7 +587,7 @@ use the WWID as the alias. In either case this be will
 be overridden by any specific aliases in the \fImultipaths\fR section.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -580,7 +600,7 @@ This should be smaller than dev_loss_tmo. Setting this to
 will disable the timeout.
 .RS
 .TP
-Default value is: in \fB5\fR
+The default is: in \fB5\fR
 .RE
 .
 .
@@ -597,7 +617,7 @@ The Linux kernel will cap this value to \fI300\fR if \fIfast_io_fail_tmo\fR
 is not set. See KNOWN ISSUES.
 .RS
 .TP
-Default value is: \fB600\fR
+The default is: \fB600\fR
 .RE
 .
 .
@@ -607,7 +627,7 @@ The full pathname of the binding file to be used when the user_friendly_names
 option is set.
 .RS
 .TP
-Default value is: \fB/etc/multipath/bindings\fR
+The default is: \fB/etc/multipath/bindings\fR
 .RE
 .
 .
@@ -617,7 +637,7 @@ The full pathname of the WWIDs file, which is used by multipath to keep track
 of the WWIDs for LUNs it has created multipath devices on in the past.
 .RS
 .TP
-Default value is: \fB/etc/multipath/wwids\fR
+The default is: \fB/etc/multipath/wwids\fR
 .RE
 .
 .
@@ -631,7 +651,7 @@ errors are logged at level 3 until the device is restored. If set to
 , multipathd always logs the path checker error at logging level 2.
 .RS
 .TP
-Default value is: \fBalways\fR
+The default is: \fBalways\fR
 .RE
 .
 .
@@ -644,7 +664,7 @@ list which contains an 8-byte value provided by the application client to the
 device server to identify the I_T nexus.
 .RS
 .TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
 .RE
 .
 .
@@ -658,7 +678,7 @@ mutipath.conf. If the SCSI layer has not attached a hardware handler,
 multipath will continue to use its configured hardware handler.
 .RS
 .TP
-Default value is: \fByes\fR
+The default is: \fByes\fR
 .RE
 .
 .
@@ -674,7 +694,21 @@ attributes \fIaccess_state\fR and \fIpreferred_path\fR are supported, or the
 , the prioritizer will be selected as usual.
 .RS
 .TP
-Default value is: \fByes\fR
+The default is: \fByes\fR
+.RE
+.
+.
+.TP
+.B detect_checker
+if set to
+.I yes
+, multipath will try to detect if the device supports SCSI-3 ALUA. If so, the
+device will automatically use the \fItur\fR checker. If set to
+.I no
+, the checker will be selected as usual.
+.RS
+.TP
+The default is: \fByes\fR
 .RE
 .
 .
@@ -687,12 +721,12 @@ only one checker will run at a time.  This is useful in the case where many
 multipathd checkers running in parallel causes significant CPU pressure.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
 .TP
-.B strict_timinig
+.B strict_timing
 If set to
 .I yes
 , multipathd will start a new path checker loop after exactly one second,
@@ -703,7 +737,7 @@ A warning will be printed if path checks take longer than \fIpolling_interval\fR
 seconds.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -718,7 +752,7 @@ to the multipath device before the last user closes it, the deferred remove
 will be canceled.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -730,7 +764,7 @@ number. If this parameter is set, multipath will act like kpartx does with
 the \fI-p\fR option is used, and always add delimiter.
 .RS
 .TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
 .RE
 .
 .
@@ -742,7 +776,46 @@ information from them, just as if it was in \fI/etc/multipath.conf\fR.
 config_dir must either be "" or a fully qualified directory name.
 .RS
 .TP
-Default value is: \fB/etc/multipath/conf.d/\fR
+The default is: \fB/etc/multipath/conf.d/\fR
+.RE
+.
+.
+.TP
+.B san_path_err_threshold
+If set to a value greater than 0, multipathd will watch paths and check how many
+times a path has been failed due to errors.If the number of failures on a particular
+path is greater then the san_path_err_threshold then the path will not  reinstante
+till san_path_err_recovery_time.These path failures should occur within a
+san_path_err_forget_rate checks, if not we will consider the path is good enough
+to reinstantate.
+.RS
+.TP
+The default is: \fBno\fR
+.RE
+.
+.
+.TP
+.B san_path_err_forget_rate
+If set to a value greater than 0, multipathd will check whether the path failures
+has exceeded  the san_path_err_threshold within this many checks i.e
+san_path_err_forget_rate . If so we will not reinstante the path till
+san_path_err_recovery_time.
+.RS
+.TP
+The default is: \fBno\fR
+.RE
+.
+.
+.TP
+.B san_path_err_recovery_time
+If set to a value greater than 0, multipathd will make sure that when path failures
+has exceeded the san_path_err_threshold within san_path_err_forget_rate then the path
+will be placed in failed state for san_path_err_recovery_time duration.Once san_path_err_recovery_time
+has timeout  we will reinstante the failed path .
+san_path_err_recovery_time value should be in secs.
+.RS
+.TP
+The default is: \fBno\fR
 .RE
 .
 .
@@ -754,7 +827,7 @@ being watched, when they next become valid, they will not be used until they
 have stayed up for \fIdelay_wait_checks\fR checks.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -766,7 +839,7 @@ comes back online, it will marked and delayed, and not used until it has passed
 \fIdelay_wait_checks\fR checks.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -787,13 +860,13 @@ a path has the same WWID as a multipath device that was previously created
 while find_multipaths was set (even if that multipath device doesn't currently
 exist).
 Whenever a multipath device is created with find_multipaths set, multipath will
-remeber the WWID of the device, so that it will automatically create the
+remember the WWID of the device, so that it will automatically create the
 device again, as soon as it sees a path with that WWID. This should allow most
 users to have multipath automatically choose the correct paths to make into
 multipath devices, without having to edit the blacklist.
 .RS
 .TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
 .RE
 .
 .
@@ -807,7 +880,7 @@ In these cases it is recommended to increase the CLI timeout to avoid
 those issues.
 .RS
 .TP
-Default value is: \fB1000\fR
+The default is: \fB1000\fR
 .RE
 .
 .
@@ -817,7 +890,7 @@ Sets the number of times multipathd will try to retrigger a uevent to get the
 WWID.
 .RS
 .TP
-Default value is: \fB3\fR
+The default is: \fB3\fR
 .RE
 .
 .
@@ -826,7 +899,7 @@ Default value is: \fB3\fR
 Sets the amount of time, in seconds, to wait between retriggers.
 .RS
 .TP
-Default value is: \fB10\fR
+The default is: \fB10\fR
 .RE
 .
 .
@@ -838,7 +911,7 @@ automatically enabling device reloads. Usually multipathd will delay reloads
 on a device until it receives a change uevent from the initial table load.
 .RS
 .TP
-Default value is: \fB30\fR
+The default is: \fB30\fR
 .RE
 .
 .
@@ -849,7 +922,38 @@ If set to
 , kpartx will not automatically create partitions on the device.
 .RS
 .TP
-The default is \fBno\fR
+The default is: \fBno\fR
+.RE
+.
+.
+.TP
+.B disable_changed_wwids
+If set to \fIyes\fR, multipathd will check the path wwid on change events, and
+if it has changed from the wwid of the multipath device, multipathd will
+disable access to the path until the wwid changes back.
+.RS
+.TP
+The default is: \fBno\fR
+.RE
+.
+.
+.TP
+.B remove_retries
+This sets how may times multipath will retry removing a device that is in-use.
+Between each attempt, multipath will sleep 1 second.
+.RS
+.TP
+The default is: \fB0\fR
+.RE
+.
+.
+.TP
+.B max_sectors_kb
+Sets the max_sectors_kb device parameter on all path devices and the multipath
+device to the specified value.
+.RS
+.TP
+The default is: \fB<device dependent>\fR
 .RE
 .
 .
@@ -869,7 +973,7 @@ The following keywords are recognized:
 Regular expression of the device nodes to be excluded.
 .RS
 .TP
-Default value is: \fB^(ram|raw|loop|fd|md|dm-|sr|scd|st|dcssblk)[0-9]\fR, \fB^(td|hd|vd)[a-z]\fR and \fB^nvme\fR
+The default is: \fB^(ram|raw|loop|fd|md|dm-|sr|scd|st|dcssblk)[0-9]\fR and \fB^(td|hd|vd)[a-z]\fR
 .RE
 .TP
 .B wwid
@@ -910,7 +1014,7 @@ The \fIWorld Wide Identification\fR of a device.
 Regular expression of the udev property to be whitelisted.
 .RS
 .TP
-Default value is: \fB(SCSI_IDENT_|ID_WWN)\fR
+The default is: \fB(SCSI_IDENT_|ID_WWN)\fR
 .RE
 .TP
 .B device
@@ -980,11 +1084,19 @@ are taken from the \fIdefaults\fR or \fIdevices\fR section:
 .TP
 .B deferred_remove
 .TP
+.B san_path_err_threshold
+.TP
+.B san_path_err_forget_rate
+.TP
+.B san_path_err_recovery_time
+.TP
 .B delay_watch_checks
 .TP
 .B delay_wait_checks
 .TP
 .B skip_kpartx
+.TP
+.B max_sectors_kb
 .RE
 .PD
 .LP
@@ -1043,7 +1155,7 @@ Active/Standby mode exclusively.
 (Hardware-dependent)
 Hardware handler for SCSI-3 ALUA compatible arrays.
 .TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
 .RE
 .
 .
@@ -1089,13 +1201,23 @@ section:
 .TP
 .B detect_prio
 .TP
+.B detect_checker
+.TP
 .B deferred_remove
 .TP
+.B san_path_err_threshold
+.TP
+.B san_path_err_forget_rate
+.TP
+.B san_path_err_recovery_time
+.TP
 .B delay_watch_checks
 .TP
 .B delay_wait_checks
 .TP
 .B skip_kpartx
+.TP
+.B max_sectors_kb
 .RE
 .PD
 .LP
@@ -1151,8 +1273,16 @@ the values are taken from the \fIdevices\fR or \fIdefaults\fR sections:
 .TP
 .B detect_prio
 .TP
+.B detect_checker
+.TP
 .B deferred_remove
 .TP
+.B san_path_err_threshold
+.TP
+.B san_path_err_forget_rate
+.TP
+.B san_path_err_recovery_time
+.TP
 .B delay_watch_checks
 .TP
 .B delay_wait_checks
index c8fb7e6..86defc0 100644 (file)
@@ -3,6 +3,11 @@ SUBSYSTEM!="block", GOTO="end_mpath"
 ACTION!="add|change", GOTO="end_mpath"
 KERNEL!="sd*|dasd*", GOTO="end_mpath"
 
+IMPORT{cmdline}="nompath"
+ENV{nompath}=="?*", GOTO="end_mpath"
+IMPORT{cmdline}="multipath"
+ENV{multipath}=="off", GOTO="end_mpath"
+
 ENV{DEVTYPE}!="partition", GOTO="test_dev"
 IMPORT{parent}="DM_MULTIPATH_DEVICE_PATH"
 ENV{DM_MULTIPATH_DEVICE_PATH}=="1", ENV{ID_FS_TYPE}="none", \
index ae06f9e..d57f6d5 100644 (file)
@@ -1,7 +1,7 @@
 include ../Makefile.inc
 
 #
-# debuging stuff
+# debugging stuff
 #
 #CFLAGS += -DLCKDBG
 #CFLAGS += -D_DEBUG_
index 50161be..32d4976 100644 (file)
@@ -547,6 +547,8 @@ cli_init (void) {
        add_handler(LIST+BLACKLIST, NULL);
        add_handler(LIST+DEVICES, NULL);
        add_handler(LIST+WILDCARDS, NULL);
+       add_handler(RESET+MAPS+STATS, NULL);
+       add_handler(RESET+MAP+STATS, NULL);
        add_handler(ADD+PATH, NULL);
        add_handler(DEL+PATH, NULL);
        add_handler(ADD+MAP, NULL);
index b0eeca6..efc12dd 100644 (file)
@@ -277,6 +277,17 @@ show_config (char ** r, int * len)
        return 0;
 }
 
+void
+reset_stats(struct multipath * mpp)
+{
+       mpp->stat_switchgroup = 0;
+       mpp->stat_path_failures = 0;
+       mpp->stat_map_loads = 0;
+       mpp->stat_total_queueing_time = 0;
+       mpp->stat_queueing_timeouts = 0;
+       mpp->stat_map_failures = 0;
+}
+
 int
 cli_list_config (void * v, char ** reply, int * len, void * data)
 {
@@ -627,6 +638,39 @@ cli_list_daemon (void * v, char ** reply, int * len, void * data)
 }
 
 int
+cli_reset_maps_stats (void * v, char ** reply, int * len, void * data)
+{
+       struct vectors * vecs = (struct vectors *)data;
+       int i;
+       struct multipath * mpp;
+
+       condlog(3, "reset multipaths stats (operator)");
+
+       vector_foreach_slot(vecs->mpvec, mpp, i) {
+               reset_stats(mpp);
+       }
+       return 0;
+}
+
+int
+cli_reset_map_stats (void * v, char ** reply, int * len, void * data)
+{
+       struct vectors * vecs = (struct vectors *)data;
+       struct multipath * mpp;
+       char * param = get_keyparam(v, MAP);
+
+       param = convert_dev(param, 0);
+       mpp = find_mp_by_str(vecs->mpvec, param);
+
+       if (!mpp)
+               return 1;
+
+       condlog(3, "reset multipath %s stats (operator)", param);
+       reset_stats(mpp);
+       return 0;
+}
+
+int
 cli_add_path (void * v, char ** reply, int * len, void * data)
 {
        struct vectors * vecs = (struct vectors *)data;
@@ -670,7 +714,7 @@ cli_add_path (void * v, char ** reply, int * len, void * data)
                pp->checkint = conf->checkint;
        }
        put_multipath_config(conf);
-       return ev_add_path(pp, vecs);
+       return ev_add_path(pp, vecs, 1);
 blacklisted:
        *reply = strdup("blacklisted\n");
        *len = strlen(*reply) + 1;
@@ -692,7 +736,7 @@ cli_del_path (void * v, char ** reply, int * len, void * data)
                condlog(0, "%s: path already removed", param);
                return 1;
        }
-       return ev_remove_path(pp, vecs);
+       return ev_remove_path(pp, vecs, 1);
 }
 
 int
@@ -734,7 +778,8 @@ cli_add_map (void * v, char ** reply, int * len, void * data)
                        rc = get_refwwid(CMD_NONE, param, DEV_DEVMAP,
                                         vecs->pathvec, &refwwid);
                        if (refwwid) {
-                               if (coalesce_paths(vecs, NULL, refwwid, 0, 1))
+                               if (coalesce_paths(vecs, NULL, refwwid,
+                                                  FORCE_RELOAD_NONE, 1))
                                        condlog(2, "%s: coalesce_paths failed",
                                                                        param);
                                dm_lib_release();
index 19e003d..f4d02cc 100644 (file)
@@ -19,6 +19,8 @@ int cli_list_config (void * v, char ** reply, int * len, void * data);
 int cli_list_blacklist (void * v, char ** reply, int * len, void * data);
 int cli_list_devices (void * v, char ** reply, int * len, void * data);
 int cli_list_wildcards (void * v, char ** reply, int * len, void * data);
+int cli_reset_maps_stats (void * v, char ** reply, int * len, void * data);
+int cli_reset_map_stats (void * v, char ** reply, int * len, void * data);
 int cli_add_path (void * v, char ** reply, int * len, void * data);
 int cli_del_path (void * v, char ** reply, int * len, void * data);
 int cli_add_map (void * v, char ** reply, int * len, void * data);
index aec89e7..0c61caa 100644 (file)
@@ -23,6 +23,7 @@
 #endif
 #include <semaphore.h>
 #include <time.h>
+#include <stdbool.h>
 
 /*
  * libmultipath
@@ -485,6 +486,8 @@ ev_add_map (char * dev, char * alias, struct vectors * vecs)
 
        if (mpp) {
                if (mpp->wait_for_udev > 1) {
+                       condlog(2, "%s: performing delayed actions",
+                               mpp->alias);
                        if (update_map(mpp, vecs))
                                /* setup multipathd removed the map */
                                return 1;
@@ -532,7 +535,8 @@ ev_add_map (char * dev, char * alias, struct vectors * vecs)
        r = get_refwwid(CMD_NONE, dev, DEV_DEVMAP, vecs->pathvec, &refwwid);
 
        if (refwwid) {
-               r = coalesce_paths(vecs, NULL, refwwid, 0, CMD_NONE);
+               r = coalesce_paths(vecs, NULL, refwwid, FORCE_RELOAD_NONE,
+                                  CMD_NONE);
                dm_lib_release();
        }
 
@@ -608,7 +612,7 @@ ev_remove_map (char * devname, char * alias, int minor, struct vectors * vecs)
 }
 
 static int
-uev_add_path (struct uevent *uev, struct vectors * vecs)
+uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map)
 {
        struct path *pp;
        int ret = 0, i;
@@ -641,7 +645,7 @@ uev_add_path (struct uevent *uev, struct vectors * vecs)
                                     DI_ALL | DI_BLACKLIST);
                        put_multipath_config(conf);
                        if (r == PATHINFO_OK)
-                               ret = ev_add_path(pp, vecs);
+                               ret = ev_add_path(pp, vecs, need_do_map);
                        else if (r == PATHINFO_SKIPPED) {
                                condlog(3, "%s: remove blacklisted path",
                                        uev->kernel);
@@ -665,7 +669,7 @@ uev_add_path (struct uevent *uev, struct vectors * vecs)
         */
        conf = get_multipath_config();
        ret = alloc_path_with_pathinfo(conf, uev->udev,
-                                      DI_ALL, &pp);
+                                      uev->wwid, DI_ALL, &pp);
        put_multipath_config(conf);
        if (!pp) {
                if (ret == PATHINFO_SKIPPED)
@@ -681,7 +685,7 @@ uev_add_path (struct uevent *uev, struct vectors * vecs)
                conf = get_multipath_config();
                pp->checkint = conf->checkint;
                put_multipath_config(conf);
-               ret = ev_add_path(pp, vecs);
+               ret = ev_add_path(pp, vecs, need_do_map);
        } else {
                condlog(0, "%s: failed to store path info, "
                        "dropping event",
@@ -699,7 +703,7 @@ uev_add_path (struct uevent *uev, struct vectors * vecs)
  * 1: error
  */
 int
-ev_add_path (struct path * pp, struct vectors * vecs)
+ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map)
 {
        struct multipath * mpp;
        char params[PARAMS_SIZE] = {0};
@@ -719,6 +723,7 @@ ev_add_path (struct path * pp, struct vectors * vecs)
            (pathcount(mpp, PATH_UP) > 0 ||
             (pathcount(mpp, PATH_GHOST) > 0 && pp->tpgs != TPGS_IMPLICIT))) {
                /* if wait_for_udev is set and valid paths exist */
+               condlog(2, "%s: delaying path addition until %s is fully initialized", pp->dev, mpp->alias);
                mpp->wait_for_udev = 2;
                orphan_path(pp, "waiting for create to complete");
                return 0;
@@ -767,6 +772,13 @@ rescan:
        /* persistent reservation check*/
        mpath_pr_event_handle(pp);
 
+       if (!need_do_map)
+               return 0;
+
+       if (!dm_map_present(mpp->alias)) {
+               mpp->action = ACT_CREATE;
+               start_waiter = 1;
+       }
        /*
         * push the map to the device-mapper
         */
@@ -833,7 +845,7 @@ fail:
 }
 
 static int
-uev_remove_path (struct uevent *uev, struct vectors * vecs)
+uev_remove_path (struct uevent *uev, struct vectors * vecs, int need_do_map)
 {
        struct path *pp;
        int ret;
@@ -844,7 +856,7 @@ uev_remove_path (struct uevent *uev, struct vectors * vecs)
        pthread_testcancel();
        pp = find_path_by_dev(vecs->pathvec, uev->kernel);
        if (pp)
-               ret = ev_remove_path(pp, vecs);
+               ret = ev_remove_path(pp, vecs, need_do_map);
        lock_cleanup_pop(vecs->lock);
        if (!pp) {
                /* Not an error; path might have been purged earlier */
@@ -855,7 +867,7 @@ uev_remove_path (struct uevent *uev, struct vectors * vecs)
 }
 
 int
-ev_remove_path (struct path *pp, struct vectors * vecs)
+ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map)
 {
        struct multipath * mpp;
        int i, retval = 0;
@@ -918,6 +930,8 @@ ev_remove_path (struct path *pp, struct vectors * vecs)
                        goto out;
                }
 
+               if (!need_do_map)
+                       goto out;
                /*
                 * reload the map
                 */
@@ -960,6 +974,7 @@ uev_update_path (struct uevent *uev, struct vectors * vecs)
        struct path * pp;
        struct config *conf;
        int disable_changed_wwids;
+       int needs_reinit = 0;
 
        conf = get_multipath_config();
        disable_changed_wwids = conf->disable_changed_wwids;
@@ -987,7 +1002,8 @@ uev_update_path (struct uevent *uev, struct vectors * vecs)
                                if (!pp->wwid_changed) {
                                        pp->wwid_changed = 1;
                                        pp->tick = 1;
-                                       dm_fail_path(pp->mpp->alias, pp->dev_t);
+                                       if (pp->mpp)
+                                               dm_fail_path(pp->mpp->alias, pp->dev_t);
                                }
                                goto out;
                        } else
@@ -995,14 +1011,17 @@ uev_update_path (struct uevent *uev, struct vectors * vecs)
                }
 
                if (pp->initialized == INIT_REQUESTED_UDEV)
-                       retval = uev_add_path(uev, vecs);
+                       needs_reinit = 1;
                else if (mpp && ro >= 0) {
                        condlog(2, "%s: update path write_protect to '%d' (uevent)", uev->kernel, ro);
 
                        if (mpp->wait_for_udev)
                                mpp->wait_for_udev = 2;
                        else {
+                               if (ro == 1)
+                                       pp->mpp->force_readonly = 1;
                                retval = reload_map(vecs, mpp, 0, 1);
+                               pp->mpp->force_readonly = 0;
                                condlog(2, "%s: map %s reloaded (retval %d)",
                                        uev->kernel, mpp->alias, retval);
                        }
@@ -1010,9 +1029,25 @@ uev_update_path (struct uevent *uev, struct vectors * vecs)
        }
 out:
        lock_cleanup_pop(vecs->lock);
-       if (!pp)
-               condlog(0, "%s: spurious uevent, path not found", uev->kernel);
+       if (!pp) {
+               /* If the path is blacklisted, print a debug/non-default verbosity message. */
+               if (uev->udev) {
+                       int flag = DI_SYSFS | DI_WWID;
+
+                       conf = get_multipath_config();
+                       retval = alloc_path_with_pathinfo(conf, uev->udev, uev->wwid, flag, NULL);
+                       put_multipath_config(conf);
 
+                       if (retval == PATHINFO_SKIPPED) {
+                               condlog(3, "%s: spurious uevent, path is blacklisted", uev->kernel);
+                               return 0;
+                       }
+               }
+
+               condlog(0, "%s: spurious uevent, path not found", uev->kernel);
+       }
+       if (needs_reinit)
+               retval = uev_add_path(uev, vecs, 1);
        return retval;
 }
 
@@ -1033,7 +1068,8 @@ map_discovery (struct vectors * vecs)
 }
 
 int
-uxsock_trigger (char * str, char ** reply, int * len, void * trigger_data)
+uxsock_trigger (char * str, char ** reply, int * len, bool is_root,
+               void * trigger_data)
 {
        struct vectors * vecs;
        int r;
@@ -1042,19 +1078,30 @@ uxsock_trigger (char * str, char ** reply, int * len, void * trigger_data)
        *len = 0;
        vecs = (struct vectors *)trigger_data;
 
-       r = parse_cmd(str, reply, len, vecs, uxsock_timeout / 1000);
+       if ((str != NULL) && (is_root == false) &&
+           (strncmp(str, "list", strlen("list")) != 0) &&
+           (strncmp(str, "show", strlen("show")) != 0)) {
+               *reply = STRDUP("permission deny: need to be root");
+               if (*reply)
+                       *len = strlen(*reply) + 1;
+               return 1;
+       }
+
+       r = parse_cmd(str, reply, len, vecs, uxsock_timeout);
 
        if (r > 0) {
                if (r == ETIMEDOUT)
                        *reply = STRDUP("timeout\n");
                else
                        *reply = STRDUP("fail\n");
-               *len = strlen(*reply) + 1;
+               if (*reply)
+                       *len = strlen(*reply) + 1;
                r = 1;
        }
        else if (!r && *len == 0) {
                *reply = STRDUP("ok\n");
-               *len = strlen(*reply) + 1;
+               if (*reply)
+                       *len = strlen(*reply) + 1;
                r = 0;
        }
        /* else if (r < 0) leave *reply alone */
@@ -1062,40 +1109,15 @@ uxsock_trigger (char * str, char ** reply, int * len, void * trigger_data)
        return r;
 }
 
-static int
-uev_discard(char * devpath)
-{
-       char *tmp;
-       char a[11], b[11];
-
-       /*
-        * keep only block devices, discard partitions
-        */
-       tmp = strstr(devpath, "/block/");
-       if (tmp == NULL){
-               condlog(4, "no /block/ in '%s'", devpath);
-               return 1;
-       }
-       if (sscanf(tmp, "/block/%10s", a) != 1 ||
-           sscanf(tmp, "/block/%10[^/]/%10s", a, b) == 2) {
-               condlog(4, "discard event on %s", devpath);
-               return 1;
-       }
-       return 0;
-}
-
 int
 uev_trigger (struct uevent * uev, void * trigger_data)
 {
        int r = 0;
        struct vectors * vecs;
-       struct config *conf;
+       struct uevent *merge_uev, *tmp;
 
        vecs = (struct vectors *)trigger_data;
 
-       if (uev_discard(uev->devpath))
-               return 0;
-
        pthread_cleanup_push(config_cleanup, NULL);
        pthread_mutex_lock(&config_lock);
        if (running_state != DAEMON_IDLE &&
@@ -1124,28 +1146,21 @@ uev_trigger (struct uevent * uev, void * trigger_data)
        }
 
        /*
-        * path add/remove event
+        * path add/remove/change event, add/remove maybe merged
         */
-       conf = get_multipath_config();
-       if (filter_devnode(conf->blist_devnode, conf->elist_devnode,
-                          uev->kernel) > 0) {
-               put_multipath_config(conf);
-               goto out;
+       list_for_each_entry_safe(merge_uev, tmp, &uev->merge_node, node) {
+               if (!strncmp(merge_uev->action, "add", 3))
+                       r += uev_add_path(merge_uev, vecs, 0);
+               if (!strncmp(merge_uev->action, "remove", 6))
+                       r += uev_remove_path(merge_uev, vecs, 0);
        }
-       put_multipath_config(conf);
 
-       if (!strncmp(uev->action, "add", 3)) {
-               r = uev_add_path(uev, vecs);
-               goto out;
-       }
-       if (!strncmp(uev->action, "remove", 6)) {
-               r = uev_remove_path(uev, vecs);
-               goto out;
-       }
-       if (!strncmp(uev->action, "change", 6)) {
-               r = uev_update_path(uev, vecs);
-               goto out;
-       }
+       if (!strncmp(uev->action, "add", 3))
+               r += uev_add_path(uev, vecs, 1);
+       if (!strncmp(uev->action, "remove", 6))
+               r += uev_remove_path(uev, vecs, 1);
+       if (!strncmp(uev->action, "change", 6))
+               r += uev_update_path(uev, vecs);
 
 out:
        return r;
@@ -1210,6 +1225,8 @@ uxlsnrloop (void * ap)
        set_handler_callback(LIST+BLACKLIST, cli_list_blacklist);
        set_handler_callback(LIST+DEVICES, cli_list_devices);
        set_handler_callback(LIST+WILDCARDS, cli_list_wildcards);
+       set_handler_callback(RESET+MAPS+STATS, cli_reset_maps_stats);
+       set_handler_callback(RESET+MAP+STATS, cli_reset_map_stats);
        set_handler_callback(ADD+PATH, cli_add_path);
        set_handler_callback(DEL+PATH, cli_del_path);
        set_handler_callback(ADD+MAP, cli_add_map);
@@ -1442,7 +1459,8 @@ int update_prio(struct path *pp, int refresh_all)
        }
        oldpriority = pp->priority;
        conf = get_multipath_config();
-       pathinfo(pp, conf, DI_PRIO);
+       if (pp->state != PATH_DOWN)
+               pathinfo(pp, conf, DI_PRIO);
        put_multipath_config(conf);
 
        if (pp->priority == oldpriority)
@@ -1472,6 +1490,83 @@ void repair_path(struct path * pp)
        LOG_MSG(1, checker_message(&pp->checker));
 }
 
+static int check_path_reinstate_state(struct path * pp) {
+       struct timespec curr_time;
+       if (!((pp->mpp->san_path_err_threshold > 0) &&
+                               (pp->mpp->san_path_err_forget_rate > 0) &&
+                               (pp->mpp->san_path_err_recovery_time >0))) {
+               return 0;
+       }
+
+       if (pp->disable_reinstate) {
+               /* If we don't know how much time has passed, automatically
+                * reinstate the path, just to be safe. Also, if there are
+                * no other usable paths, reinstate the path
+                */
+               if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0 ||
+                               pp->mpp->nr_active == 0) {
+                       condlog(2, "%s : reinstating path early", pp->dev);
+                       goto reinstate_path;
+               }
+               if ((curr_time.tv_sec - pp->dis_reinstate_time ) > pp->mpp->san_path_err_recovery_time) {
+                       condlog(2,"%s : reinstate the path after err recovery time", pp->dev);
+                       goto reinstate_path;
+               }
+               return 1;
+       }
+       /* forget errors on a working path */
+       if ((pp->state == PATH_UP || pp->state == PATH_GHOST) &&
+                       pp->path_failures > 0) {
+               if (pp->san_path_err_forget_rate > 0){
+                       pp->san_path_err_forget_rate--;
+               } else {
+                       /* for every san_path_err_forget_rate number of
+                        * successful path checks decrement path_failures by 1
+                        */
+                       pp->path_failures--;
+                       pp->san_path_err_forget_rate = pp->mpp->san_path_err_forget_rate;
+               }
+               return 0;
+       }
+
+       /* If the path isn't recovering from a failed state, do nothing */
+       if (pp->state != PATH_DOWN && pp->state != PATH_SHAKY &&
+                       pp->state != PATH_TIMEOUT)
+               return 0;
+
+       if (pp->path_failures == 0)
+               pp->san_path_err_forget_rate = pp->mpp->san_path_err_forget_rate;
+
+       pp->path_failures++;
+
+       /* if we don't know the currently time, we don't know how long to
+        * delay the path, so there's no point in checking if we should
+        */
+
+       if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0)
+               return 0;
+       /* when path failures has exceeded the san_path_err_threshold
+        * place the path in delayed state till san_path_err_recovery_time
+        * so that the cutomer can rectify the issue within this time. After
+        * the completion of san_path_err_recovery_time it should
+        * automatically reinstate the path
+        */
+       if (pp->path_failures > pp->mpp->san_path_err_threshold) {
+               condlog(2, "%s : hit error threshold. Delaying path reinstatement", pp->dev);
+               pp->dis_reinstate_time = curr_time.tv_sec;
+               pp->disable_reinstate = 1;
+               return 1;
+       } else {
+               return 0;
+       }
+
+reinstate_path:
+       pp->path_failures = 0;
+       pp->disable_reinstate = 0;
+       pp->san_path_err_forget_rate = 0;
+       return 0;
+}
+
 /*
  * Returns '1' if the path has been checked, '-1' if it was blacklisted
  * and '0' otherwise
@@ -1555,7 +1650,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks)
                        conf = get_multipath_config();
                        ret = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST);
                        if (ret == PATHINFO_OK) {
-                               ev_add_path(pp, vecs);
+                               ev_add_path(pp, vecs, 1);
                                pp->tick = 1;
                        } else if (ret == PATHINFO_SKIPPED) {
                                put_multipath_config(conf);
@@ -1586,6 +1681,12 @@ check_path (struct vectors * vecs, struct path * pp, int ticks)
                return 0;
 
        if ((newstate == PATH_UP || newstate == PATH_GHOST) &&
+                       check_path_reinstate_state(pp)) {
+               pp->state = PATH_DELAYED;
+               return 1;
+       }
+
+       if ((newstate == PATH_UP || newstate == PATH_GHOST) &&
             pp->wait_checks > 0) {
                if (pp->mpp->nr_active > 0) {
                        pp->state = PATH_DELAYED;
@@ -1620,7 +1721,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks)
                pp->checkint = conf->checkint;
                put_multipath_config(conf);
 
-               if (newstate == PATH_DOWN || newstate == PATH_SHAKY || newstate == PATH_TIMEOUT) {
+               if (newstate != PATH_UP && newstate != PATH_GHOST) {
                        /*
                         * proactively fail path in the DM
                         */
@@ -1671,7 +1772,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks)
                }
                if (!disable_reinstate && reinstate_path(pp, add_active)) {
                        condlog(3, "%s: reload map", pp->dev);
-                       ev_add_path(pp, vecs);
+                       ev_add_path(pp, vecs, 1);
                        pp->tick = 1;
                        return 0;
                }
@@ -1694,7 +1795,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks)
                        /* Clear IO errors */
                        if (reinstate_path(pp, 0)) {
                                condlog(3, "%s: reload map", pp->dev);
-                               ev_add_path(pp, vecs);
+                               ev_add_path(pp, vecs, 1);
                                pp->tick = 1;
                                return 0;
                        }
@@ -1925,6 +2026,7 @@ configure (struct vectors * vecs, int start_waiters)
        vector mpvec;
        int i, ret;
        struct config *conf;
+       static int force_reload = FORCE_RELOAD_WEAK;
 
        if (!vecs->pathvec && !(vecs->pathvec = vector_alloc())) {
                condlog(0, "couldn't allocate path vec in configure");
@@ -1968,8 +2070,14 @@ configure (struct vectors * vecs, int start_waiters)
 
        /*
         * create new set of maps & push changed ones into dm
+        * In the first call, use FORCE_RELOAD_WEAK to avoid making
+        * superfluous ACT_RELOAD ioctls. Later calls are done
+        * with FORCE_RELOAD_YES.
         */
-       if (coalesce_paths(vecs, mpvec, NULL, 1, CMD_NONE)) {
+       ret = coalesce_paths(vecs, mpvec, NULL, force_reload, CMD_NONE);
+       if (force_reload == FORCE_RELOAD_WEAK)
+               force_reload = FORCE_RELOAD_YES;
+       if (ret) {
                condlog(0, "configure failed while coalescing paths");
                return 1;
        }
@@ -2069,6 +2177,10 @@ reconfigure (struct vectors * vecs)
                conf->verbosity = verbosity;
        if (bindings_read_only)
                conf->bindings_read_only = bindings_read_only;
+       if (conf->find_multipaths) {
+               condlog(2, "find_multipaths is set: -n is implied");
+               ignore_new_devs = 1;
+       }
        if (ignore_new_devs)
                conf->ignore_new_devs = ignore_new_devs;
        uxsock_timeout = conf->uxsock_timeout;
@@ -2166,6 +2278,12 @@ sigusr2 (int sig)
 static void
 signal_init(void)
 {
+       sigset_t set;
+
+       sigemptyset(&set);
+       sigaddset(&set, SIGUSR2);
+       pthread_sigmask(SIG_SETMASK, &set, NULL);
+
        signal_set(SIGHUP, sighup);
        signal_set(SIGUSR1, sigusr1);
        signal_set(SIGUSR2, sigusr2);
@@ -2247,6 +2365,7 @@ child (void * param)
        int i;
 #ifdef USE_SYSTEMD
        unsigned long checkint;
+       int startup_done = 0;
 #endif
        int rc;
        int pid_fd = -1;
@@ -2395,10 +2514,6 @@ child (void * param)
        }
        pthread_attr_destroy(&misc_attr);
 
-#ifdef USE_SYSTEMD
-       sd_notify(0, "READY=1");
-#endif
-
        while (running_state != DAEMON_SHUTDOWN) {
                pthread_cleanup_push(config_cleanup, NULL);
                pthread_mutex_lock(&config_lock);
@@ -2420,6 +2535,12 @@ child (void * param)
                        }
                        lock_cleanup_pop(vecs->lock);
                        post_config_state(DAEMON_IDLE);
+#ifdef USE_SYSTEMD
+                       if (!startup_done) {
+                               sd_notify(0, "READY=1");
+                               startup_done = 1;
+                       }
+#endif
                }
        }
 
@@ -2579,7 +2700,7 @@ main (int argc, char *argv[])
        umask(umask(077) | 022);
 
        pthread_cond_init_mono(&config_cond);
-       
+
        udev = udev_new();
 
        while ((arg = getopt(argc, argv, ":dsv:k::Bn")) != EOF ) {
@@ -2608,6 +2729,7 @@ main (int argc, char *argv[])
                                conf->verbosity = verbosity;
                        uxsock_timeout = conf->uxsock_timeout;
                        uxclnt(optarg, uxsock_timeout + 100);
+                       free_config(conf);
                        exit(0);
                case 'B':
                        bindings_read_only = 1;
@@ -2642,6 +2764,7 @@ main (int argc, char *argv[])
                }
                c += snprintf(c, s + CMDSIZE - c, "\n");
                uxclnt(s, uxsock_timeout + 100);
+               free_config(conf);
                exit(0);
        }
 
index f72580d..094b04f 100644 (file)
@@ -22,8 +22,8 @@ void exit_daemon(void);
 const char * daemon_status(void);
 int need_to_delay_reconfig (struct vectors *);
 int reconfigure (struct vectors *);
-int ev_add_path (struct path *, struct vectors *);
-int ev_remove_path (struct path *, struct vectors *);
+int ev_add_path (struct path *, struct vectors *, int);
+int ev_remove_path (struct path *, struct vectors *, int);
 int ev_add_map (char *, char *, struct vectors *);
 int ev_remove_map (char *, char *, int, struct vectors *);
 void sync_map_state (struct multipath *);
index e3d6f91..fd66cf6 100644 (file)
@@ -1,17 +1,19 @@
 [Unit]
 Description=Device-Mapper Multipath Device Controller
-Before=iscsi.service iscsid.service lvm2-activation-early.service
-Before=local-fs-pre.target systemd-udev-trigger.service
-After=multipathd.socket systemd-udevd.service
+Wants=systemd-udev-trigger.service systemd-udev-settle.service
+Before=iscsi.service iscsid.service lvm2-lvmetad.service lvm2-activation-early.service
+Before=local-fs-pre.target blk-availability.service
+After=multipathd.socket systemd-udev-trigger.service systemd-udev-settle.service
 DefaultDependencies=no
-Wants=local-fs-pre.target multipathd.socket blk-availability.service
 Conflicts=shutdown.target
+ConditionKernelCommandLine=!nompath
+ConditionKernelCommandLine=!multipath=off
 
 [Service]
 Type=notify
 NotifyAccess=main
 LimitCORE=infinity
-ExecStartPre=/sbin/modprobe -a scsi_dh_alua scsi_dh_emc scsi_dh_rdac dm-multipath
+ExecStartPre=-/sbin/modprobe -a scsi_dh_alua scsi_dh_emc scsi_dh_rdac dm-multipath
 ExecStart=/sbin/multipathd -d -s
 ExecReload=/sbin/multipathd reconfigure
 
index dfef03e..98ac25a 100644 (file)
@@ -22,6 +22,7 @@
 #include <poll.h>
 #include <sys/time.h>
 #include <signal.h>
+#include <stdbool.h>
 #include "checkers.h"
 #include "memory.h"
 #include "debug.h"
@@ -51,6 +52,23 @@ LIST_HEAD(clients);
 pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER;
 struct pollfd *polls;
 
+static bool _socket_client_is_root(int fd);
+
+static bool _socket_client_is_root(int fd)
+{
+       socklen_t len = 0;
+       struct ucred uc;
+
+       len = sizeof(struct ucred);
+       if ((fd >= 0) &&
+           (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) == 0) &&
+           (uc.uid == 0))
+                       return true;
+
+       /* Treat error as not root client */
+       return false;
+}
+
 /*
  * handle a new client joining
  */
@@ -241,18 +259,21 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, void * trigger_data)
                                if (clock_gettime(CLOCK_MONOTONIC, &start_time)
                                    != 0)
                                        start_time.tv_sec = 0;
-                               if (recv_packet(c->fd, &inbuf,
-                                               uxsock_timeout) != 0) {
+                               if (recv_packet_from_client(c->fd, &inbuf,
+                                                           uxsock_timeout)
+                                   != 0) {
                                        dead_client(c);
                                        continue;
                                }
                                if (!inbuf) {
-                                       condlog(4, "recv_packet get null request");
+                                       condlog(4, "recv_packet_from_client "
+                                               "get null request");
                                        continue;
                                }
                                condlog(4, "cli[%d]: Got request [%s]",
                                        i, inbuf);
                                uxsock_trigger(inbuf, &reply, &rlen,
+                                              _socket_client_is_root(c->fd),
                                               trigger_data);
                                if (reply) {
                                        if (send_packet(c->fd,
index 4ef47d5..d51a8f9 100644 (file)
@@ -1,7 +1,9 @@
 #ifndef _UXLSNR_H
 #define _UXLSNR_H
 
-typedef int (uxsock_trigger_fn)(char *, char **, int *, void *);
+#include <stdbool.h>
+
+typedef int (uxsock_trigger_fn)(char *, char **, int *, bool, void *);
 
 void * uxsock_listen(uxsock_trigger_fn uxsock_trigger,
                     void * trigger_data);
index 4b8ef75..6892007 100644 (file)
@@ -89,7 +89,7 @@
         || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6))
 */
 #define __VALGRIND_MAJOR__    3
-#define __VALGRIND_MINOR__    11
+#define __VALGRIND_MINOR__    12
 
 
 #include <stdarg.h>
@@ -946,7 +946,7 @@ typedef
                      "move %0, $11\n\t"     /*result*/            \
                      : "=r" (_zzq_result)                         \
                      : "r" (_zzq_default), "r" (&_zzq_args[0])    \
-                     : "$11", "$12");                             \
+                     : "$11", "$12", "memory");                   \
     _zzq_result;                                                  \
   })
 
@@ -1017,7 +1017,7 @@ typedef
                          "move %0, $11\n\t"     /*result*/          \
                          : "=r" (_zzq_result)                       \
                          : "r" (_zzq_default), "r" (&_zzq_args[0])  \
-                         : "$11", "$12");                           \
+                         : "$11", "$12", "memory");                 \
     _zzq_result;                                                    \
   })
 
@@ -6759,6 +6759,7 @@ __inline
 VALGRIND_PRINTF(const char *format, ...)
 {
 #if defined(NVALGRIND)
+   if (format) *(volatile const char *)format;   /* avoid compiler warning */
    return 0;
 #else /* NVALGRIND */
 #if defined(_MSC_VER) || defined(__MINGW64__)
@@ -6797,6 +6798,7 @@ __inline
 VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
 {
 #if defined(NVALGRIND)
+   if (format) *(volatile const char *)format;   /* avoid compiler warning */
    return 0;
 #else /* NVALGRIND */
 #if defined(_MSC_VER) || defined(__MINGW64__)
@@ -7007,6 +7009,38 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
     VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL,   \
                                     pool, rzB, is_zeroed, 0, 0)
 
+/* Create a memory pool with some flags specifying extended behaviour.
+   When flags is zero, the behaviour is identical to VALGRIND_CREATE_MEMPOOL.
+   
+   The flag VALGRIND_MEMPOOL_METAPOOL specifies that the pieces of memory 
+   associated with the pool using VALGRIND_MEMPOOL_ALLOC  will be used
+   by the application as superblocks to dole out MALLOC_LIKE blocks using
+   VALGRIND_MALLOCLIKE_BLOCK. In other words, a meta pool is a "2 levels"
+   pool : first level is the blocks described by VALGRIND_MEMPOOL_ALLOC.
+   The second level blocks are described using VALGRIND_MALLOCLIKE_BLOCK.
+   Note that the association between the pool and the second level blocks
+   is implicit : second level blocks will be located inside first level
+   blocks. It is necessary to use the VALGRIND_MEMPOOL_METAPOOL flag
+   for such 2 levels pools, as otherwise valgrind will detect overlapping
+   memory blocks, and will abort execution (e.g. during leak search).
+
+   Such a meta pool can also be marked as an 'auto free' pool using the flag
+   VALGRIND_MEMPOOL_AUTO_FREE, which must be OR-ed together with the
+   VALGRIND_MEMPOOL_METAPOOL. For an 'auto free' pool, VALGRIND_MEMPOOL_FREE
+   will automatically free the second level blocks that are contained
+   inside the first level block freed with VALGRIND_MEMPOOL_FREE.
+   In other words, calling VALGRIND_MEMPOOL_FREE will cause implicit calls
+   to VALGRIND_FREELIKE_BLOCK for all the second level blocks included
+   in the first level block.
+   Note: it is an error to use the VALGRIND_MEMPOOL_AUTO_FREE flag
+   without the VALGRIND_MEMPOOL_METAPOOL flag.
+*/
+#define VALGRIND_MEMPOOL_AUTO_FREE  1
+#define VALGRIND_MEMPOOL_METAPOOL   2
+#define VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags)        \
+   VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL,          \
+                                   pool, rzB, is_zeroed, flags, 0)
+
 /* Destroy a memory pool. */
 #define VALGRIND_DESTROY_MEMPOOL(pool)                            \
     VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL,  \