.nfs*
*.swp
*.patch
+*.rej
+*.orig
+libdmmp/docs/man/*.3.gz
+libdmmp/*.so.*
+libdmmp/test/libdmmp_test
+libdmmp/test/libdmmp_speed_test
mpathpersist \
kpartx
+ifneq ($(ENABLE_LIBDMMP),0)
+BUILDDIRS += \
+ libdmmp
+endif
+
all: recurse
recurse:
#
# 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 = ..
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 $@ $<
+++ /dev/null
-#!/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
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
#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>
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 {
#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;
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);
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;
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;
}
char *
-dm_mapuuid(int major, int minor)
+dm_mapuuid(const char *mapname)
{
struct dm_task *dmt;
const char *tmp;
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;
}
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;
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;
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))
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, ¶ms);
+
+ 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, ¶ms[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;
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 */
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;
}
#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>
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;
}
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);
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
return t;
}
-extern int
-is_loop_device (const char *device)
+int is_loop_device(const char *device)
{
struct stat statbuf;
int loopmajor;
#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;
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;
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;
return 0;
}
-extern int
-del_loop (const char *device)
+int del_loop(const char *device)
{
int retries = 5;
int fd;
--- /dev/null
+== 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.
--- /dev/null
+# 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
--- /dev/null
+#!/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;
+}
--- /dev/null
+#!/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 = '\\&((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 " <i>$1</i><b>$parameter</b>) <i>($2)</i>;<br>\n";
+ } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+ # bitfield
+ print " <i>$1</i> <b>$parameter</b>$2;<br>\n";
+ } else {
+ print " <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);
--- /dev/null
+.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>
--- /dev/null
+#!/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;
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+includedir=__INCLUDEDIR__
+libdir=__LIBDIR__
+
+Name: libdmmp
+Version: __VERSION__
+Description: Device mapper multipath management library
+Requires:
+Libs: -L${libdir} -ldmmp
+Cflags: -I${includedir}
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+# 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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
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
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;
if (err) {
free(*reply);
*reply = NULL;
- return err;
+ return -1;
}
return 0;
}
#endif
#define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd"
-#define DEFAULT_REPLY_TIMEOUT 1000
+#define DEFAULT_REPLY_TIMEOUT 4000
#define MAX_REPLY_LEN 65536
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
$(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:
#define __STDC_FORMAT_MACROS 1
struct udev *udev;
-struct config *conf;
struct config *
mpath_lib_init (struct udev *udev)
int i, j;
struct pathgroup * pgp;
struct path * pp;
+ struct config *conf;
if (!mpp->pg)
return 0;
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;
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);
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);
goto out1;
}
+ conf = get_multipath_config();
select_reservation_key(conf, mpp);
+ put_multipath_config(conf);
switch(rq_servact)
{
#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*/
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)
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);
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)
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
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:
*map_alias = NULL;
+ rewind(f);
while (fgets(buf, LINE_MAX, f)) {
char *c, *alias, *wwid;
int curr_id;
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)
#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;
}
-extern int
-alloc_ble_device (vector blist)
+int alloc_ble_device(vector blist)
{
struct blentry_device * ble = MALLOC(sizeof(struct blentry_device));
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;
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;
{
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;
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;
void checker_repair (struct checker * c)
{
- if (!c || !checker_selected(c))
+ if (!checker_selected(c))
return;
c->message[0] = '\0';
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 */
libcheckdirectio.so \
libcheckemc_clariion.so \
libcheckhp_sw.so \
- libcheckrdac.so \
- libcheckrbd.so
+ libcheckrdac.so
+ifneq ($(ENABLE_RADOS),0)
+LIBS += libcheckrbd.so
+endif
all: $(LIBS)
return;
}
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
{
int rc;
int ret;
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;
int inactive_snap;
};
-extern void
-hexadecimal_to_ascii(char * wwn, char *wwnstr)
+void hexadecimal_to_ascii(char * wwn, char *wwnstr)
{
int i,j, nbl;
return 0;
}
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
{
char buff[MX_ALLOC_LEN];
ct->image = strdup(image);
if (!ct->image)
- goto free_info;
+ goto free_username;
pool = udev_device_get_sysattr_value(bus_dev, "pool");
if (!pool)
}
}
-extern int
-libcheck_check (struct checker * c)
+int libcheck_check(struct checker * c)
{
struct volume_access_inq inq;
int ret, inqfail;
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
+#include <sys/sysmacros.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
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);
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;
return ret;
}
-extern struct mpentry *
-find_mpe (vector mptable, char * wwid)
+struct mpentry *find_mpe(vector mptable, char *wwid)
{
int i;
struct mpentry * mpe;
return NULL;
}
-extern char *
-get_mpe_wwid (vector mptable, char * alias)
+char *get_mpe_wwid(vector mptable, char *alias)
{
int i;
struct mpentry * mpe;
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
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;
if (conf->uid_attribute)
FREE(conf->uid_attribute);
+ if (conf->uid_attrs)
+ FREE(conf->uid_attrs);
+
if (conf->getuid)
FREE(conf->getuid);
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;
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
CMD_ADD_WWID,
};
+enum force_reload_types {
+ FORCE_RELOAD_NONE,
+ FORCE_RELOAD_YES,
+ FORCE_RELOAD_WEAK,
+};
+
struct hwentry {
char * vendor;
char * product;
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;
};
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;
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;
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;
#include "util.h"
#include "uxsock.h"
#include "wwids.h"
+#include "sysfs.h"
/* group paths in pg by host adapter
*/
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;
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);
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);
/* 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;
}
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;
}
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;
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;
#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;
switch (mpp->action) {
case ACT_REJECT:
case ACT_NOTHING:
+ case ACT_IMPOSSIBLE:
return DOMAP_EXIST;
case ACT_SWITCHPG:
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;
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:
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;
if (refwwid && !strlen(refwwid))
refwwid = NULL;
- if (force_reload) {
+ if (force_reload != FORCE_RELOAD_NONE) {
vector_foreach_slot (pathvec, pp1, k) {
pp1->mpp = NULL;
}
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);
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);
* 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;
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;
ACT_RESIZE,
ACT_FORCERENAME,
ACT_DRY_RUN,
+ ACT_IMPOSSIBLE,
};
#define FLUSH_ONE 1
#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
#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)
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
+#include <sys/sysmacros.h>
#include "checkers.h"
#include "vector.h"
return;
}
-extern void
-dm_init(int v) {
+void dm_init(int v)
+{
dm_log_init(&dm_write_log);
dm_log_init_verbose(v + 3);
}
return 1;
}
-extern int
-dm_prereq (void)
+int dm_prereq(void)
{
if (dm_lib_prereq())
return 1;
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);
}
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++) {
#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
* 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;
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;
return r;
}
-extern int
-dm_get_uuid(char *name, char *uuid)
+int dm_get_uuid(char *name, char *uuid)
{
char uuidtmp[WWID_SIZE];
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;
* 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;
return r;
}
-extern int
-dm_is_mpath(const char * name)
+int dm_is_mpath(const char *name)
{
int r = 0;
struct dm_task *dmt;
}
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;
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 */
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;
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;
}
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
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;
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);
sysfs_check_holders(dm_dep, dev_t);
}
- dm_task_destroy (dmt);
-
r = 1;
out:
+ dm_task_destroy (dmt);
return r;
}
#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);
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);
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)
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)
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)
{
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;
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)
{
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);
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);
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);
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);
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);
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);
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();
}
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 */
#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;
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 {
err = pathinfo(pp, conf, flag | DI_BLACKLIST);
}
- if (err)
+ if (err || !pp_ptr)
free_path(pp);
else if (pp_ptr)
*pp_ptr = pp;
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);
}
}
#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; \
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,
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);
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);
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
}
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");
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;
} 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;
return PATH_UNCHECKED;
}
}
+ select_detect_checker(conf, pp);
select_checker(conf, pp);
if (!checker_selected(c)) {
condlog(3, "%s: No checker selected", pp->dev);
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);
/*
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 &&
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 {
#endif
/*
- * exerpt from sg_err.h
+ * excerpt from sg_err.h
*/
#define SCSI_CHECK_CONDITION 0x2
#define SCSI_COMMAND_TERMINATED 0x22
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);
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;
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);
}
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();
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)
return 1;
}
-extern int
-disassemble_status (char * params, struct multipath * mpp)
+int disassemble_status(char *params, struct multipath *mpp)
{
char * word;
char * p;
/* FlashSystem 710/720/810/820/840/900 */
.vendor = "IBM",
.product = "FlashSystem",
- .no_path_retry = NO_PATH_RETRY_FAIL,
.pgpolicy = MULTIBUS,
},
{
.prio_name = PRIO_ALUA,
},
{
+ /* SANsymphony */
.vendor = "DataCore",
.product = "Virtual Disk",
.pgpolicy = GROUP_BY_PRIO,
.vendor = "PURE",
.product = "FlashArray",
.pgpolicy = MULTIBUS,
- .fast_io_fail = 10,
- .dev_loss = 60,
},
/*
* Huawei
{
.vendor = "Ceph",
.product = "RBD",
- .no_path_retry = NO_PATH_RETRY_FAIL,
.checker_name = RBD,
.deferred_remove = DEFERRED_REMOVE_ON,
},
.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
*/
.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,
},
/*
.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.
.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
/*
},
};
-extern int
-setup_default_hwtable (vector hw)
+int setup_default_hwtable(vector hw)
{
int r = 0;
struct hwentry * hwe = default_hw;
&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 */
-/* Display a buffer into a HEXA formated output */
+/* Display a buffer into a HEXA formatted output */
static void
dump_buffer(char *buff, int count)
{
#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;
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;
/*
* 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;
/*
* 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;
return 1;
}
-extern int
-one_path_per_group (struct multipath * mp)
+int one_path_per_group(struct multipath *mp)
{
int i;
struct path * pp;
return 1;
}
-extern int
-one_group (struct multipath * mp) /* aka multibus */
+int one_group(struct multipath *mp) /* aka multibus */
{
struct pathgroup * pgp;
return 1;
}
-extern int
-group_by_prio (struct multipath * mp)
+int group_by_prio(struct multipath *mp)
{
int i;
unsigned int prio;
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");
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) {
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;
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;
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");
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;
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;
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;
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;
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;
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;
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;
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;
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;
/*
* 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];
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];
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];
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",
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;
#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 {
#define VERSION_SPC3 0x05
/* Defined TPGS field values. */
+#define TPGS_UNDEF -1
#define TPGS_NONE 0x0
#define TPGS_IMPLICIT 0x1
#define TPGS_EXPLICIT 0x2
* 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:
}
/*
- * Retuns:
+ * Returns:
* -1: Unable to obtain proxy info
* 0: Device _not_ proxy path
* 1: Device _is_ proxy path
#include "prio.h"
#include "discovery.h"
#include "dict.h"
+#include "util.h"
#include "prioritizers/alua_rtpg.h"
#include <inttypes.h>
#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)
} 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;
return 0;
}
-extern int
-select_uid (struct config *conf, struct multipath *mp)
+int select_uid(struct config *conf, struct multipath *mp)
{
char *origin;
return 0;
}
-extern int
-select_gid (struct config *conf, struct multipath *mp)
+int select_gid(struct config *conf, struct multipath *mp)
{
char *origin;
* 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];
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];
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];
return 0;
}
-extern int
-select_selector (struct config *conf, struct multipath * mp)
+int select_selector(struct config *conf, struct multipath * mp)
{
char *origin;
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,
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;
}
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;
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;
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);
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;
} \
} 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;
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
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];
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;
}
{
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);
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};
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];
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];
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;
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];
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};
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;
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;
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];
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];
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;
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;
+}
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);
#include "structs_vec.h"
#include "blacklist.h"
#include "prio.h"
+#include "prioritizers/alua_spc3.h"
struct adapter_group *
alloc_adaptergroup(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;
return mpp;
}
-extern void
-free_multipath_attributes (struct multipath * mpp)
+void free_multipath_attributes(struct multipath *mpp)
{
if (!mpp)
return;
return NULL;
}
-extern int
-pathcountgr (struct pathgroup * pgp, int state)
+int pathcountgr(struct pathgroup *pgp, int state)
{
struct path *pp;
int count = 0;
return count;
}
-extern int
-pathcount (struct multipath * mpp, int state)
+int pathcount(struct multipath *mpp, int state)
{
struct pathgroup *pgp;
int count = 0;
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;
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)
}
/* 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++;
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++;
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;
SYSFS_BUS_CCW,
SYSFS_BUS_CCISS,
SYSFS_BUS_RBD,
+ SYSFS_BUS_NVME,
};
enum pathstates {
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,
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 */
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 {
int priority;
int pgindex;
int detect_prio;
+ int detect_checker;
int watch_checks;
int wait_checks;
int tpgs;
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;
};
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;
/*
* 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;
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;
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;
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;
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);
}
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);
}
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;
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);
}
}
-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;
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();
}
}
-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;
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;
#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;
pgp->priority = 0;
}
-extern int
-select_path_group (struct multipath * mpp)
+int select_path_group(struct multipath *mpp)
{
int i;
int max_priority = 0;
}
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;
#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,
#include <unistd.h>
#include <stdio.h>
+#include <stdbool.h>
#include <errno.h>
#include <stdlib.h>
#include <stddef.h>
#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);
{
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.
*/
pthread_mutex_unlock(uevq_lockp);
if (!my_uev_trigger)
break;
+ merge_uevq(&uevq_tmp);
service_uevq(&uevq_tmp);
}
condlog(3, "Terminating uev service queue");
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);
}
events = 0;
+ gettimeofday(&start_time, NULL);
while (1) {
struct uevent *uev;
struct udev_device *dev;
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");
pthread_mutex_unlock(uevq_lockp);
events = 0;
}
+ gettimeofday(&start_time, NULL);
timeout = 30;
}
need_failback = 0;
return err;
}
-extern int
-uevent_get_major(struct uevent *uev)
+int uevent_get_major(struct uevent *uev)
{
char *p, *q;
int i, major = -1;
return major;
}
-extern int
-uevent_get_minor(struct uevent *uev)
+int uevent_get_minor(struct uevent *uev)
{
char *p, *q;
int i, minor = -1;
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;
return ro;
}
-extern char *
-uevent_get_dm_name(struct uevent *uev)
+char *uevent_get_dm_name(struct uevent *uev)
{
char *p = NULL;
int i;
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];
};
#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)
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;
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)
{
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;
+}
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)
#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
}
/*
- * 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);
}
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);
#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"
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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
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"
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,
" . 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"
* 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;
int di_flag = 0;
char * refwwid = NULL;
char * dev = NULL;
- struct config *conf;
/*
* allocate core vectors to store paths and multipaths
/*
* 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 &&
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)
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;
enum devtypes dev_type = DEV_NONE;
char *dev = NULL;
struct config *conf;
+ int retries = -1;
udev = udev_new();
logsink = 0;
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;
}
break;
case 'r':
- conf->force_reload = 1;
+ conf->force_reload = FORCE_RELOAD_YES;
break;
case 'i':
conf->ignore_wwids = 1;
case 'a':
cmd = CMD_ADD_WWID;
break;
+ case 'R':
+ retries = atoi(optarg);
+ break;
case ':':
fprintf(stderr, "Missing option argument\n");
usage(argv[0]);
}
}
+ /*
+ * 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);
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");
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:
.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 \|]
.
.
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 ,
.\"
.\" ----------------------------------------------------------------------------
.
-.TH MULTIPATH.CONF 5 2016-10-18 "Linux"
+.TH MULTIPATH.CONF 5 2016-11-27 "Linux"
.
.
.\" ----------------------------------------------------------------------------
levels are between 0 and 6.
.RS
.TP
-Default value is: \fB2\fR
+The default is: \fB2\fR
.RE
.
.
setting in the multipathd.service definition if systemd is used.
.RS
.TP
-Default value is: \fB5\fR
+The default is: \fB5\fR
.RE
.
.
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
.
.
\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
.
.
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
.
.
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
.
.
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
.
.
.\" XXX
???. Requires prio_args keyword.
.TP
-Default value is: \fBconst\fR
+The default is: \fBconst\fR
.RE
.
.
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 ???.
.\" XXX
\fIpreferredip\fR ???.
.TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
.RE
.
.
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
.
.
.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
.
.
The \fIuser_friendly_names\fR prefix.
.RS
.TP
-Default value is: \fBmpath\fR
+The default is: \fBmpath\fR
.RE
.
.
.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
.
.
only apply to \fIround-robin\fR path_selector.
.RS
.TP
-Default value is: \fB1\fR
+The default is: \fB1\fR
.RE
.
.
if that number is greated than 1024.
.RS
.TP
-Default value is: \fBmax\fR
+The default is: \fBmax\fR
.RE
.
.
Only apply to \fIround-robin\fR path_selector.
.RS
.TP
-Default value is: \fBuniform\fR
+The default is: \fBuniform\fR
.RE
.
.
.TP
See KNOWN ISSUES.
.TP
-Default value is: \fBfail\fR
+The default is: \fBfail\fR
.RE
.
.
, avoids this problem.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
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
.
.
deleted.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
be overridden by any specific aliases in the \fImultipaths\fR section.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
will disable the timeout.
.RS
.TP
-Default value is: in \fB5\fR
+The default is: in \fB5\fR
.RE
.
.
is not set. See KNOWN ISSUES.
.RS
.TP
-Default value is: \fB600\fR
+The default is: \fB600\fR
.RE
.
.
option is set.
.RS
.TP
-Default value is: \fB/etc/multipath/bindings\fR
+The default is: \fB/etc/multipath/bindings\fR
.RE
.
.
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
.
.
, multipathd always logs the path checker error at logging level 2.
.RS
.TP
-Default value is: \fBalways\fR
+The default is: \fBalways\fR
.RE
.
.
device server to identify the I_T nexus.
.RS
.TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
.RE
.
.
multipath will continue to use its configured hardware handler.
.RS
.TP
-Default value is: \fByes\fR
+The default is: \fByes\fR
.RE
.
.
, 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
.
.
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,
seconds.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
will be canceled.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
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
.
.
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
.
.
have stayed up for \fIdelay_wait_checks\fR checks.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
\fIdelay_wait_checks\fR checks.
.RS
.TP
-Default value is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
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
.
.
those issues.
.RS
.TP
-Default value is: \fB1000\fR
+The default is: \fB1000\fR
.RE
.
.
WWID.
.RS
.TP
-Default value is: \fB3\fR
+The default is: \fB3\fR
.RE
.
.
Sets the amount of time, in seconds, to wait between retriggers.
.RS
.TP
-Default value is: \fB10\fR
+The default is: \fB10\fR
.RE
.
.
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
.
.
, 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
.
.
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
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
.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
(Hardware-dependent)
Hardware handler for SCSI-3 ALUA compatible arrays.
.TP
-Default value is: \fB<unset>\fR
+The default is: \fB<unset>\fR
.RE
.
.
.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
.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
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", \
include ../Makefile.inc
#
-# debuging stuff
+# debugging stuff
#
#CFLAGS += -DLCKDBG
#CFLAGS += -D_DEBUG_
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);
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)
{
}
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;
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;
condlog(0, "%s: path already removed", param);
return 1;
}
- return ev_remove_path(pp, vecs);
+ return ev_remove_path(pp, vecs, 1);
}
int
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();
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);
#endif
#include <semaphore.h>
#include <time.h>
+#include <stdbool.h>
/*
* libmultipath
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;
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();
}
}
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;
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);
*/
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)
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",
* 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};
(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;
/* 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
*/
}
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;
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 */
}
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;
goto out;
}
+ if (!need_do_map)
+ goto out;
/*
* reload the map
*/
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;
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
}
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);
}
}
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;
}
}
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;
*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 */
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 &&
}
/*
- * 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;
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);
}
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)
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
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);
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;
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
*/
}
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;
}
/* 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;
}
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");
/*
* 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;
}
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;
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);
int i;
#ifdef USE_SYSTEMD
unsigned long checkint;
+ int startup_done = 0;
#endif
int rc;
int pid_fd = -1;
}
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);
}
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
}
}
umask(umask(077) | 022);
pthread_cond_init_mono(&config_cond);
-
+
udev = udev_new();
while ((arg = getopt(argc, argv, ":dsv:k::Bn")) != EOF ) {
conf->verbosity = verbosity;
uxsock_timeout = conf->uxsock_timeout;
uxclnt(optarg, uxsock_timeout + 100);
+ free_config(conf);
exit(0);
case 'B':
bindings_read_only = 1;
}
c += snprintf(c, s + CMDSIZE - c, "\n");
uxclnt(s, uxsock_timeout + 100);
+ free_config(conf);
exit(0);
}
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 *);
[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
#include <poll.h>
#include <sys/time.h>
#include <signal.h>
+#include <stdbool.h>
#include "checkers.h"
#include "memory.h"
#include "debug.h"
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
*/
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,
#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);
|| (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6))
*/
#define __VALGRIND_MAJOR__ 3
-#define __VALGRIND_MINOR__ 11
+#define __VALGRIND_MINOR__ 12
#include <stdarg.h>
"move %0, $11\n\t" /*result*/ \
: "=r" (_zzq_result) \
: "r" (_zzq_default), "r" (&_zzq_args[0]) \
- : "$11", "$12"); \
+ : "$11", "$12", "memory"); \
_zzq_result; \
})
"move %0, $11\n\t" /*result*/ \
: "=r" (_zzq_result) \
: "r" (_zzq_default), "r" (&_zzq_args[0]) \
- : "$11", "$12"); \
+ : "$11", "$12", "memory"); \
_zzq_result; \
})
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__)
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__)
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, \