*.so.0
*.a
*.gz
+*.d
kpartx/kpartx
multipath/multipath
multipathd/multipathd
libdmmp/*.so.*
libdmmp/test/libdmmp_test
libdmmp/test/libdmmp_speed_test
+tests/*-test
+tests/*.out
+
libmultipath \
libmultipath/prioritizers \
libmultipath/checkers \
+ libmultipath/foreign \
libmpathpersist \
multipath \
multipathd \
@for dir in $(BUILDDIRS); do \
$(MAKE) -C $$dir clean || exit $?; \
done
+ $(MAKE) -C tests clean
recurse_install:
@for dir in $(BUILDDIRS); do \
uninstall: recurse_uninstall
+test: all
+ $(MAKE) -C tests
+
.PHONY: TAGS
TAGS:
etags -a libmultipath/*.c
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 \
+ -Werror=cast-qual -Werror=discarded-qualifiers \
-Wp,-D_FORTIFY_SOURCE=2 $(STACKPROT) \
--param=ssp-buffer-size=4
-CFLAGS = $(OPTFLAGS) -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\"
+CFLAGS := $(OPTFLAGS) -DBIN_DIR=\"$(bindir)\" -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\" \
+ -MMD -MP $(CFLAGS)
BIN_CFLAGS = -fPIE -DPIE
LIB_CFLAGS = -fPIC
SHARED_FLAGS = -shared
--- /dev/null
+This is a rough guide, consult your storage device manufacturer documentation.
+
+ALUA is supported in some devices, but usually it's disabled by default.
+To enable ALUA, the following options should be changed:
+
+- EMC CLARiiON/VNX:
+ "Failover Mode" should be changed to "4".
+
+- HPE 3PAR:
+ "Host:" should be changed to "Generic-ALUA Persona 2 (UARepLun, SESLun, ALUA)".
+
+- Promise VTrak/Vess:
+ "LUN Affinity" and "ALUA" should be changed to "Enable", "Redundancy Type"
+ must be "Active-Active".
+
+- LSI/Engenio/NetApp RDAC class, as NetApp SANtricity E/EF Series and OEM arrays:
+ "Select operating system:" should be changed to "Linux DM-MP (Kernel 3.10 or later)".
$(RM) $(DESTDIR)$(libudevdir)/rules.d/67-kpartx-compat.rules
$(RM) $(DESTDIR)$(libudevdir)/rules.d/68-del-part-nodes.rules
-clean:
+clean: dep_clean
$(RM) core *.o $(EXEC) *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
SUBSYSTEM!="block", GOTO="end_del_part_nodes"
KERNEL!="sd*|dasd*|rbd*", GOTO="end_del_part_nodes"
ACTION!="add|change", GOTO="end_del_part_nodes"
+ENV{DEVTYPE}=="partition", GOTO="end_del_part_nodes"
IMPORT{cmdline}="dont_del_part_nodes"
ENV{dont_del_part_nodes}=="1", GOTO="end_del_part_nodes"
#include <sys/sysmacros.h>
#include "devmapper.h"
+#define FREE_CONST(p) do { free((void*)(long)p); p = NULL; } while(0)
#define _UUID_PREFIX "part"
#define UUID_PREFIX _UUID_PREFIX "%d-"
#define _UUID_PREFIX_LEN (sizeof(_UUID_PREFIX) - 1)
} else
*part_uuid = uuid;
out:
- free((void*)tmp);
+ FREE_CONST(tmp);
return r;
}
IMPORT{program}=="kpartx_id %M %m $env{DM_UUID}"
# DM_TYPE only has a reasonable value for partitions on multipath.
-ENV{DM_UUID}=="*-mpath-*", ENV{DM_TYPE}=="?*" \
- SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_NAME}"
+ENV{DM_UUID}=="*-mpath-*", ENV{DM_TYPE}=="?*", ENV{DM_SERIAL}=="?*" \
+ SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_SERIAL}-part$env{DM_PART}"
ENV{DM_WWN}=="?*", ENV{DM_PART}=="?*", \
SYMLINK+="disk/by-id/wwn-$env{DM_WWN}-part$env{DM_PART}"
dmuuid=${UUID#*-}
dmtbl=${UUID%%-*}
dmpart=${dmtbl#part}
+dmserial=
# kpartx types are 'part<num>'
if [ "$dmpart" = "$dmtbl" ] ; then
dmpart=
case "$dmuuid" in
mpath-*)
dmdeps=$($DMSETUP deps -u $dmuuid)
+ dmserial=${dmuuid#mpath-}
;;
esac
elif [ "$dmtbl" = "mpath" ] ; then
dmname="$dmuuid"
+ dmserial="$dmuuid"
# We need the dependencies of the table to figure out the type
dmdeps=$($DMSETUP deps -u $UUID)
fi
;;
*)
echo "DM_TYPE=scsi"
- echo "DM_WWN=0x${dmname#?}"
+ echo "DM_WWN=0x${dmserial#?}"
;;
esac
else
echo "DM_TYPE=raid"
fi
+if [[ $dmserial ]]; then
+ echo "DM_SERIAL=$dmserial"
+fi
exit 0
# They also serve as DM devices to test partition removal on those.
TABLE="\
-0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS
+0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS
$((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS"
SPAN1=kpt
dmsetup create $SPAN2 <<<"$TABLE"
push_cleanup 'dmsetup remove -f $SPAN2'
+# This is a non-kpartx pseudo "partition" mapping
+USER1=user1
+push_cleanup 'dmsetup remove -f $USER1'
+dmsetup create $USER1 <<EOF
+0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS
+EOF
+
usleep $WAIT_US
[[ -b /dev/mapper/$SPAN1 ]]
[[ -b /dev/mapper/$SPAN2 ]]
+[[ -b /dev/mapper/$USER1 ]]
step "create vg on $LO3"
# On the 3rd loop device, we create a VG and an LV
# spans should not have been removed
[[ -b /dev/mapper/$SPAN1 ]]
[[ -b /dev/mapper/$SPAN2 ]]
+[[ -b /dev/mapper/$USER1 ]]
# LVs neither
[[ -b /dev/mapper/$VG-$LV ]]
#
include ../Makefile.inc
-LIBDMMP_VERSION=0.1.0
+LIBDMMP_VERSION=0.2.0
SONAME=$(LIBDMMP_VERSION)
DEVLIB = libdmmp.so
LIBS = $(DEVLIB).$(SONAME)
$(RM) $(DESTDIR)$(man3dir)/libdmmp.h*
$(RM) $(DESTDIR)$(pkgconfdir)/$(PKGFILE)
-clean:
+clean: dep_clean
$(RM) core *.a *.o *.gz *.so *.so.*
$(RM) -r docs/man
$(MAKE) -C test clean
+include $(wildcard $(OBJS:.o=.d))
+
check: all
$(MAKE) -C test check
gzip -f $$file; \
done
find docs/man -type f -name \*[0-9].gz
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
-#!/usr/bin/perl -w
+#!/usr/bin/env perl
+use warnings;
use strict;
## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ##
# 'funcname()' - function
# '$ENVVAR' - environmental variable
# '&struct_name' - name of a structure (up to two words including 'struct')
+# '&struct_name.member' - name of a structure member
# '@parameter' - name of a parameter
# '%CONST' - name of a constant.
+# '``LITERAL``' - literal string without any spaces on it.
## 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_constant = '\b``([^\`]+)``\b';
+my $type_constant2 = '\%([-_\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_enum = '\&(enum\s*([_\w]+))';
+my $type_struct = '\&(struct\s*([_\w]+))';
+my $type_typedef = '\&(typedef\s*([_\w]+))';
+my $type_union = '\&(union\s*([_\w]+))';
+my $type_member = '\&([_\w]+)(\.|->)([_\w]+)';
+my $type_fallback = '\&([_\w]+)';
+my $type_enum_xml = '\&(enum\s*([_\w]+))';
+my $type_struct_xml = '\&(struct\s*([_\w]+))';
+my $type_typedef_xml = '\&(typedef\s*([_\w]+))';
+my $type_union_xml = '\&(union\s*([_\w]+))';
+my $type_member_xml = '\&([_\w]+)(\.|-\>)([_\w]+)';
+my $type_fallback_xml = '\&([_\w]+)';
my $type_member_func = $type_member . '\(\)';
# Output conversion substitutions.
# these work fairly well
my @highlights_html = (
[$type_constant, "<i>\$1</i>"],
+ [$type_constant2, "<i>\$1</i>"],
[$type_func, "<b>\$1</b>"],
+ [$type_enum_xml, "<i>\$1</i>"],
[$type_struct_xml, "<i>\$1</i>"],
+ [$type_typedef_xml, "<i>\$1</i>"],
+ [$type_union_xml, "<i>\$1</i>"],
[$type_env, "<b><i>\$1</i></b>"],
- [$type_param, "<tt><b>\$1</b></tt>"]
+ [$type_param, "<tt><b>\$1</b></tt>"],
+ [$type_member_xml, "<tt><i>\$1</i>\$2\$3</tt>"],
+ [$type_fallback_xml, "<i>\$1</i>"]
);
my $local_lt = "\\\\\\\\lt:";
my $local_gt = "\\\\\\\\gt:";
# html version 5
my @highlights_html5 = (
[$type_constant, "<span class=\"const\">\$1</span>"],
+ [$type_constant2, "<span class=\"const\">\$1</span>"],
[$type_func, "<span class=\"func\">\$1</span>"],
+ [$type_enum_xml, "<span class=\"enum\">\$1</span>"],
[$type_struct_xml, "<span class=\"struct\">\$1</span>"],
+ [$type_typedef_xml, "<span class=\"typedef\">\$1</span>"],
+ [$type_union_xml, "<span class=\"union\">\$1</span>"],
[$type_env, "<span class=\"env\">\$1</span>"],
- [$type_param, "<span class=\"param\">\$1</span>]"]
+ [$type_param, "<span class=\"param\">\$1</span>]"],
+ [$type_member_xml, "<span class=\"literal\"><span class=\"struct\">\$1</span>\$2<span class=\"member\">\$3</span></span>"],
+ [$type_fallback_xml, "<span class=\"struct\">\$1</span>"]
);
my $blankline_html5 = $local_lt . "br /" . $local_gt;
my @highlights_xml = (
["([^=])\\\"([^\\\"<]+)\\\"", "\$1<quote>\$2</quote>"],
[$type_constant, "<constant>\$1</constant>"],
+ [$type_constant2, "<constant>\$1</constant>"],
+ [$type_enum_xml, "<type>\$1</type>"],
[$type_struct_xml, "<structname>\$1</structname>"],
+ [$type_typedef_xml, "<type>\$1</type>"],
+ [$type_union_xml, "<structname>\$1</structname>"],
[$type_param, "<parameter>\$1</parameter>"],
[$type_func, "<function>\$1</function>"],
- [$type_env, "<envar>\$1</envar>"]
+ [$type_env, "<envar>\$1</envar>"],
+ [$type_member_xml, "<literal><structname>\$1</structname>\$2<structfield>\$3</structfield></literal>"],
+ [$type_fallback_xml, "<structname>\$1</structname>"]
);
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_constant2, "<replaceable class=\"option\">\$1</replaceable>"],
[$type_func, "<function>\$1</function>"],
+ [$type_enum, "<type>\$1</type>"],
[$type_struct, "<structname>\$1</structname>"],
+ [$type_typedef, "<type>\$1</type>"],
+ [$type_union, "<structname>\$1</structname>"],
[$type_env, "<envar>\$1</envar>"],
- [$type_param, "<parameter>\$1</parameter>" ]
+ [$type_param, "<parameter>\$1</parameter>" ],
+ [$type_member, "<literal><structname>\$1</structname>\$2<structfield>\$3</structfield></literal>"],
+ [$type_fallback, "<structname>\$1</structname>"]
);
my $blankline_gnome = "</para><para>\n";
# these are pretty rough
my @highlights_man = (
[$type_constant, "\$1"],
+ [$type_constant2, "\$1"],
[$type_func, "\\\\fB\$1\\\\fP"],
+ [$type_enum, "\\\\fI\$1\\\\fP"],
[$type_struct, "\\\\fI\$1\\\\fP"],
- [$type_param, "\\\\fI\$1\\\\fP"]
+ [$type_typedef, "\\\\fI\$1\\\\fP"],
+ [$type_union, "\\\\fI\$1\\\\fP"],
+ [$type_param, "\\\\fI\$1\\\\fP"],
+ [$type_member, "\\\\fI\$1\$2\$3\\\\fP"],
+ [$type_fallback, "\\\\fI\$1\\\\fP"]
);
my $blankline_man = "";
# text-mode
my @highlights_text = (
[$type_constant, "\$1"],
+ [$type_constant2, "\$1"],
[$type_func, "\$1"],
+ [$type_enum, "\$1"],
[$type_struct, "\$1"],
- [$type_param, "\$1"]
+ [$type_typedef, "\$1"],
+ [$type_union, "\$1"],
+ [$type_param, "\$1"],
+ [$type_member, "\$1\$2\$3"],
+ [$type_fallback, "\$1"]
);
my $blankline_text = "";
# rst-mode
my @highlights_rst = (
[$type_constant, "``\$1``"],
+ [$type_constant2, "``\$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_member_func, "\\:c\\:type\\:`\$1\$2\$3\\\\(\\\\) <\$1>`"],
+ [$type_member, "\\:c\\:type\\:`\$1\$2\$3 <\$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>`"],
+ [$type_enum, "\\:c\\:type\\:`\$1 <\$2>`"],
+ [$type_struct, "\\:c\\:type\\:`\$1 <\$2>`"],
+ [$type_typedef, "\\:c\\:type\\:`\$1 <\$2>`"],
+ [$type_union, "\\:c\\:type\\:`\$1 <\$2>`"],
# in rst this can refer to any type
- [$type_struct, "\\:c\\:type\\:`\$1`"],
+ [$type_fallback, "\\:c\\:type\\:`\$1`"],
[$type_param, "**\$1**"]
);
my $blankline_rst = "\n";
# list mode
my @highlights_list = (
[$type_constant, "\$1"],
+ [$type_constant2, "\$1"],
[$type_func, "\$1"],
+ [$type_enum, "\$1"],
[$type_struct, "\$1"],
- [$type_param, "\$1"]
+ [$type_typedef, "\$1"],
+ [$type_union, "\$1"],
+ [$type_param, "\$1"],
+ [$type_member, "\$1"],
+ [$type_fallback, "\$1"]
);
my $blankline_list = "";
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 .
+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*(.*)?';
foreach $parameter (@{$args{'parameterlist'}}) {
my $parameter_name = $parameter;
$parameter_name =~ s/\[.*//;
+ $type = $args{'parametertypes'}{$parameter};
- print " <varlistentry>\n <term><parameter>$parameter</parameter></term>\n";
+ print " <varlistentry>\n <term><parameter>$type $parameter</parameter></term>\n";
print " <listitem>\n <para>\n";
$lineprefix=" ";
output_highlight($args{'parameterdescs'}{$parameter_name});
defined($args{'parameterdescs'}{$parameter_name}) || next;
($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+ $type = $args{'parametertypes'}{$parameter};
print " <varlistentry>";
- print " <term>$parameter</term>\n";
+ print " <term><literal>$type $parameter</literal></term>\n";
print " <listitem><para>\n";
output_highlight($args{'parameterdescs'}{$parameter_name});
print " </para></listitem>\n";
$lineprefix = " ";
foreach $parameter (@{$args{'parameterlist'}}) {
my $parameter_name = $parameter;
- #$parameter_name =~ s/\[.*//;
+ $parameter_name =~ s/\[.*//;
$type = $args{'parametertypes'}{$parameter};
if ($type ne "") {
my $nested;
if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) {
- #my $decl_type = $1;
+ my $decl_type = $1;
$declaration_name = $2;
my $members = $3;
# 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;
+ # replace DECLARE_HASHTABLE
+ $members =~ s/DECLARE_HASHTABLE\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[1 << (($2) - 1)\]/gos;
create_parameterlist($members, ';', $file);
- check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested);
+ check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual, $nested);
output_declaration($declaration_name,
'struct',
if ($x =~ /enum\s+(\w+)\s*{(.*)}/) {
$declaration_name = $1;
my $members = $2;
+ my %_members;
+
+ $members =~ s/\s+$//;
foreach my $arg (split ',', $members) {
$arg =~ s/^\s*(\w+).*/$1/;
print STDERR "${file}:$.: warning: Enum value '$arg' ".
"not described in enum '$declaration_name'\n";
}
-
+ $_members{$arg} = 1;
}
+ while (my ($k, $v) = each %parameterdescs) {
+ if (!exists($_members{$k})) {
+ print STDERR "${file}:$.: warning: Excess enum value " .
+ "'$k' description in '$declaration_name'\n";
+ }
+ }
+
output_declaration($declaration_name,
'enum',
{'enum' => $declaration_name,
}
$anon_struct_union = 0;
- my $param_name = $param;
- $param_name =~ s/\[.*//;
+ $param =~ s/[\[\)].*//;
if ($type eq "" && $param =~ /\.\.\.$/)
{
# but inline preprocessor statements);
# also ignore unnamed structs/unions;
if (!$anon_struct_union) {
- if (!defined $parameterdescs{$param_name} && $param_name !~ /^#/) {
+ if (!defined $parameterdescs{$param} && $param !~ /^#/) {
- $parameterdescs{$param_name} = $undescribed;
+ $parameterdescs{$param} = $undescribed;
if (($type eq 'function') || ($type eq 'enum')) {
print STDERR "${file}:$.: warning: Function parameter ".
# "[blah" in a parameter string;
###$param =~ s/\s*//g;
push @parameterlist, $param;
+ $type =~ s/\s\s+/ /g;
$parametertypes{$param} = $type;
}
} else {
if ($nested !~ m/\Q$sects[$sx]\E/) {
print STDERR "${file}:$.: warning: " .
- "Excess struct/union/enum/typedef member " .
+ "Excess $decl_type member " .
"'$sects[$sx]' " .
"description in '$decl_name'\n";
++$warnings;
$prototype =~ s/__must_check +//;
$prototype =~ s/__weak +//;
my $define = $prototype =~ s/^#\s*define\s+//; #ak added
- $prototype =~ s/__attribute__\s*\(\([a-z,]*\)\)//;
+ $prototype =~ s/__attribute__\s*\(\(
+ (?:
+ [\w\s]++ # attribute name
+ (?:\([^)]*+\))? # attribute arguments
+ \s*+,? # optional comma at the end
+ )+
+ \)\)\s+//x;
# Yes, this truly is vile. We are looking for:
# 1. Return type (may be nothing if we're looking at a macro)
$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*\*+)\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*\*+)\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*\*+)\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*\*+)\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*\*+)\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*\(([^\{]*)\)/) {
+ $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;
while (1) {
if ( $x =~ /([^{};]*)([{};])(.*)/ ) {
+ if( length $prototype ) {
+ $prototype .= " "
+ }
$prototype .= $1 . $2;
($2 eq '{') && $brcount++;
($2 eq '}') && $brcount--;
#define _ERRNO_STR_BUFF_SIZE 256
#define _IPC_MAX_CMD_LEN 512
/* ^ Was _MAX_CMD_LEN in ./libmultipath/uxsock.h */
+#define _LAST_ERR_MSG_BUFF_SIZE 1024
struct dmmp_context {
void (*log_func)(struct dmmp_context *ctx, int priority,
int log_priority;
void *userdata;
unsigned int tmo;
+ char last_err_msg[_LAST_ERR_MSG_BUFF_SIZE];
};
/*
_dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
unsigned int);
+_dmmp_getter_func_gen(dmmp_last_error_msg, struct dmmp_context, ctx,
+ last_err_msg, const char *);
+
_dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
_dmmp_mpath_free);
{
va_list args;
+ if (ctx->log_func == NULL)
+ return;
+
va_start(args, format);
ctx->log_func(ctx, priority, file, line, func_name, format, args);
+ if (priority == DMMP_LOG_PRIORITY_ERROR)
+ vsnprintf(ctx->last_err_msg, _LAST_ERR_MSG_BUFF_SIZE,
+ format, args);
va_end(args);
}
ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
ctx->userdata = NULL;
ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
+ memset(ctx->last_err_msg, 0, _LAST_ERR_MSG_BUFF_SIZE);
return ctx;
}
}
}
+ if ((*output != NULL) &&
+ strncmp(*output, "permission deny",
+ strlen("permission deny")) == 0) {
+ _error(ctx, "Permission deny, need to be root");
+ rc = DMMP_ERR_PERMISSION_DENY;
+ goto out;
+ }
+
out:
if (rc != DMMP_OK) {
free(*output);
#define DMMP_ERR_MPATH_BUSY 7
#define DMMP_ERR_MPATH_NOT_FOUND 8
#define DMMP_ERR_INVALID_ARGUMENT 9
+#define DMMP_ERR_PERMISSION_DENY 10
/*
* Use the syslog severity level as log priority
* Pointer of 'struct dmmp_context'.
* If this pointer is NULL, your program will be terminated by assert.
* @log_func:
- * Pointer of log handler function.
+ * Pointer of log handler function. If set to NULL, all log will be
+ * ignored.
*
* Return:
* void
*
* * DMMP_ERR_INVALID_ARGUMENT
*
+ * * DMMP_ERR_PERMISSION_DENY
+ *
* Error number could be converted to string by dmmp_strerror().
*/
DMMP_DLL_EXPORT int dmmp_flush_mpath(struct dmmp_context *ctx,
*
* * DMMP_ERR_NO_DAEMON
*
+ * * DMMP_ERR_PERMISSION_DENY
+ *
* Error number could be converted to string by dmmp_strerror().
*/
DMMP_DLL_EXPORT int dmmp_reconfig(struct dmmp_context *ctx);
+/**
+ * dmmp_last_error_msg() - Retrieves the last error message.
+ *
+ * Retrieves the last error message.
+ *
+ * @ctx:
+ * Pointer of 'struct dmmp_context'.
+ * 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_context_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_last_error_msg(struct dmmp_context *ctx);
+
#ifdef __cplusplus
} /* End of extern "C" */
#endif
{DMMP_ERR_MPATH_BUSY, "Specified multipath device map is in use"},
{DMMP_ERR_MPATH_NOT_FOUND, "Specified multipath not found"},
{DMMP_ERR_INVALID_ARGUMENT, "Invalid argument"},
+ {DMMP_ERR_PERMISSION_DENY, "Permission deny"},
};
_dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG_CONV);
sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \
time -p ./$(SPD_TEST_EXEC)
-clean:
+clean: dep_clean
rm -f $(TEST_EXEC) $(SPD_TEST_EXEC)
+
+OBJS = $(TEST_EXEC).o $(SPD_TEST_EXEC).o
+include $(wildcard $(OBJS:.o=.d))
+
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
"timeout to %u\n", TMO);
if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0)
- FAIL(rc, out, "dmmp_mpath_array_get(): rc != 0\n");
+ FAIL(rc, out, "dmmp_mpath_array_get() failed: %s\n",
+ dmmp_last_error_msg(ctx));
if (dmmp_mp_count == 0)
FAIL(rc, out, "dmmp_mpath_array_get(): "
"Got no multipath devices\n");
dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
if (dmmp_flush_mpath(ctx, old_name) != DMMP_OK)
- FAIL(rc, out, "dmmp_flush_mpath(): Failed\n");
+ FAIL(rc, out, "dmmp_flush_mpath(): failed %s\n",
+ dmmp_last_error_msg(ctx));
PASS("dmmp_flush_mpath(): OK\n");
if (dmmp_reconfig(ctx) != DMMP_OK)
- FAIL(rc, out, "dmmp_reconfig(): Failed\n");
+ FAIL(rc, out, "dmmp_reconfig() failed: %s\n",
+ dmmp_last_error_msg(ctx));
PASS("dmmp_reconfig(): OK\n");
if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0)
- FAIL(rc, out, "dmmp_mpath_array_get(): rc != 0\n");
+ FAIL(rc, out, "dmmp_mpath_array_get() failed: %s\n",
+ dmmp_last_error_msg(ctx));
if (dmmp_mp_count == 0)
FAIL(rc, out, "dmmp_mpath_array_get(): "
"Got no multipath devices\n");
$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
$(RM) $(DESTDIR)$(includedir)/mpath_cmd.h
-clean:
- $(RM) core *.a *.o *.so *.so.* *.gz
+clean: dep_clean
+ $(RM) core *.a *.o *.so *.so.* *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
}
if (!n)
return total;
- buf = n + (char *)buf;
+ buf = n + (const char *)buf;
len -= n;
total += n;
}
$(RM) $(DESTDIR)$(includedir)/mpath_persist.h
$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
-clean:
+clean: dep_clean
$(RM) core *.a *.o *.so *.so.* *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
switchgroup.o uxsock.o print.o alias.o log_pthread.o \
log.o configure.o structs_vec.o sysfs.o prio.o checkers.o \
lock.o waiter.o file.o wwids.o prioritizers/alua_rtpg.o prkey.o \
- io_err_stat.o
+ io_err_stat.o dm-generic.o generic.o foreign.o
all: $(LIBS)
$(RM) $(DESTDIR)$(syslibdir)/$(LIBS)
$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
-clean:
+clean: dep_clean
$(RM) core *.a *.o *.so *.so.* *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
uninstall:
for file in $(LIBS); do $(RM) $(DESTDIR)$(libdir)/$$file; done
-clean:
+clean: dep_clean
$(RM) core *.a *.o *.gz *.so
+
+OBJS := $(LIBS:libcheck%.so=%.o) libsg.o directio.o
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
static int rbd_is_blacklisted(struct rbd_checker_context *ct, char *msg)
{
char *addr_tok, *start, *save;
- char *cmd[2];
+ const char *cmd[2];
char *blklist, *stat;
size_t blklist_len, stat_len;
int ret;
static int rbd_rm_blacklist(struct rbd_checker_context *ct)
{
- char *cmd[2];
+ const char *cmd[2];
char *stat, *cmd_str;
size_t stat_len;
int ret;
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
+#include <urcu/uatomic.h>
#include "checkers.h"
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t active;
- pthread_spinlock_t hldr_lock;
int holders;
char message[CHECKER_MSG_LEN];
};
ct->state = PATH_UNCHECKED;
ct->fd = -1;
- ct->holders = 1;
+ uatomic_set(&ct->holders, 1);
pthread_cond_init_mono(&ct->active);
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&ct->lock, &attr);
pthread_mutexattr_destroy(&attr);
- pthread_spin_init(&ct->hldr_lock, PTHREAD_PROCESS_PRIVATE);
c->context = ct;
return 0;
{
pthread_mutex_destroy(&ct->lock);
pthread_cond_destroy(&ct->active);
- pthread_spin_destroy(&ct->hldr_lock);
free(ct);
}
if (c->context) {
struct tur_checker_context *ct = c->context;
int holders;
- pthread_t thread;
-
- pthread_spin_lock(&ct->hldr_lock);
- ct->holders--;
- holders = ct->holders;
- thread = ct->thread;
- pthread_spin_unlock(&ct->hldr_lock);
- if (holders)
- pthread_cancel(thread);
- else
+ int running;
+
+ running = uatomic_xchg(&ct->running, 0);
+ if (running)
+ pthread_cancel(ct->thread);
+ ct->thread = 0;
+ holders = uatomic_sub_return(&ct->holders, 1);
+ if (!holders)
cleanup_context(ct);
c->context = NULL;
}
{
int holders;
struct tur_checker_context *ct = data;
- pthread_spin_lock(&ct->hldr_lock);
- ct->holders--;
- holders = ct->holders;
- ct->thread = 0;
- pthread_spin_unlock(&ct->hldr_lock);
+
+ holders = uatomic_sub_return(&ct->holders, 1);
if (!holders)
cleanup_context(ct);
}
-static int tur_running(struct tur_checker_context *ct)
-{
- pthread_t thread;
-
- pthread_spin_lock(&ct->hldr_lock);
- thread = ct->thread;
- pthread_spin_unlock(&ct->hldr_lock);
-
- return thread != 0;
-}
-
static void copy_msg_to_tcc(void *ct_p, const char *msg)
{
struct tur_checker_context *ct = ct_p;
static void *tur_thread(void *ctx)
{
struct tur_checker_context *ct = ctx;
- int state;
+ int state, running;
char devt[32];
condlog(3, "%s: tur checker starting up",
condlog(3, "%s: tur checker finished, state %s",
tur_devt(devt, sizeof(devt), ct), checker_state_name(state));
+
+ running = uatomic_xchg(&ct->running, 0);
+ if (!running)
+ pause();
+
tur_thread_cleanup_pop(ct);
return ((void *)0);
int tur_status, r;
char devt[32];
-
if (!ct)
return PATH_UNCHECKED;
return PATH_WILD;
}
- if (ct->running) {
- /*
- * Check if TUR checker is still running. Hold hldr_lock
- * around the pthread_cancel() call to avoid that
- * pthread_cancel() gets called after the (detached) TUR
- * thread has exited.
- */
- pthread_spin_lock(&ct->hldr_lock);
- if (ct->thread) {
- if (tur_check_async_timeout(c)) {
- condlog(3, "%s: tur checker timeout",
- tur_devt(devt, sizeof(devt), ct));
+ if (ct->thread) {
+ if (tur_check_async_timeout(c)) {
+ int running = uatomic_xchg(&ct->running, 0);
+ if (running)
pthread_cancel(ct->thread);
- ct->running = 0;
- MSG(c, MSG_TUR_TIMEOUT);
- tur_status = PATH_TIMEOUT;
- } else {
- condlog(3, "%s: tur checker not finished",
+ condlog(3, "%s: tur checker timeout",
+ tur_devt(devt, sizeof(devt), ct));
+ ct->thread = 0;
+ MSG(c, MSG_TUR_TIMEOUT);
+ tur_status = PATH_TIMEOUT;
+ } else if (uatomic_read(&ct->running) != 0) {
+ condlog(3, "%s: tur checker not finished",
tur_devt(devt, sizeof(devt), ct));
- ct->running++;
- tur_status = PATH_PENDING;
- }
+ tur_status = PATH_PENDING;
} else {
/* TUR checker done */
- ct->running = 0;
+ ct->thread = 0;
tur_status = ct->state;
strlcpy(c->message, ct->message, sizeof(c->message));
}
- pthread_spin_unlock(&ct->hldr_lock);
pthread_mutex_unlock(&ct->lock);
} else {
- if (tur_running(ct)) {
+ if (uatomic_read(&ct->running) != 0) {
/* pthread cancel failed. continue in sync mode */
pthread_mutex_unlock(&ct->lock);
condlog(3, "%s: tur thread not responding",
ct->state = PATH_UNCHECKED;
ct->fd = c->fd;
ct->timeout = c->timeout;
- pthread_spin_lock(&ct->hldr_lock);
- ct->holders++;
- pthread_spin_unlock(&ct->hldr_lock);
+ uatomic_add(&ct->holders, 1);
+ uatomic_set(&ct->running, 1);
tur_set_async_timeout(c);
setup_thread_attr(&attr, 32 * 1024, 1);
r = pthread_create(&ct->thread, &attr, tur_thread, ct);
pthread_attr_destroy(&attr);
if (r) {
- pthread_spin_lock(&ct->hldr_lock);
- ct->holders--;
- pthread_spin_unlock(&ct->hldr_lock);
- pthread_mutex_unlock(&ct->lock);
+ uatomic_sub(&ct->holders, 1);
+ uatomic_set(&ct->running, 0);
ct->thread = 0;
+ pthread_mutex_unlock(&ct->lock);
condlog(3, "%s: failed to start tur thread, using"
" sync mode", tur_devt(devt, sizeof(devt), ct));
return tur_check(c->fd, c->timeout,
tur_status = ct->state;
strlcpy(c->message, ct->message, sizeof(c->message));
pthread_mutex_unlock(&ct->lock);
- if (tur_running(ct) &&
+ if (uatomic_read(&ct->running) != 0 &&
(tur_status == PATH_PENDING || tur_status == PATH_UNCHECKED)) {
condlog(3, "%s: tur checker still running",
tur_devt(devt, sizeof(devt), ct));
- ct->running = 1;
tur_status = PATH_PENDING;
+ } else {
+ int running = uatomic_xchg(&ct->running, 0);
+ if (running)
+ pthread_cancel(ct->thread);
+ ct->thread = 0;
}
}
static int
merge_hwe (struct hwentry * dst, struct hwentry * src)
{
- char id[SCSI_VENDOR_SIZE+SCSI_PRODUCT_SIZE];
+ char id[SCSI_VENDOR_SIZE+PATH_PRODUCT_SIZE];
merge_str(vendor);
merge_str(product);
merge_str(revision);
merge_num(delay_wait_checks);
merge_num(skip_kpartx);
merge_num(max_sectors_kb);
+ merge_num(ghost_delay);
snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product);
reconcile_features_with_options(id, &dst->features,
hwe->retain_hwhandler = dhwe->retain_hwhandler;
hwe->detect_prio = dhwe->detect_prio;
hwe->detect_checker = dhwe->detect_checker;
+ hwe->ghost_delay = dhwe->ghost_delay;
if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product)))
goto out;
conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT;
conf->disable_changed_wwids = DEFAULT_DISABLE_CHANGED_WWIDS;
conf->remove_retries = 0;
+ conf->ghost_delay = DEFAULT_GHOST_DELAY;
/*
* preload default hwtable
int marginal_path_double_failed_time;
int skip_kpartx;
int max_sectors_kb;
+ int ghost_delay;
char * bl_product;
};
int marginal_path_double_failed_time;
int skip_kpartx;
int max_sectors_kb;
+ int ghost_delay;
uid_t uid;
gid_t gid;
mode_t mode;
int disable_changed_wwids;
int remove_retries;
int max_sectors_kb;
+ int ghost_delay;
unsigned int version[3];
char * multipath_dir;
#include "discovery.h"
#include "debug.h"
#include "switchgroup.h"
+#include "dm-generic.h"
#include "print.h"
#include "configure.h"
#include "pgpolicies.h"
#include "uxsock.h"
#include "wwids.h"
#include "sysfs.h"
+#include "io_err_stat.h"
/* group paths in pg by host adapter
*/
return 0;
}
-int setup_map(struct multipath *mpp, char *params, int params_size)
+int setup_map(struct multipath *mpp, char *params, int params_size,
+ struct vectors *vecs)
{
struct pathgroup * pgp;
struct config *conf;
* free features, selector, and hwhandler properties if they are being reused
*/
free_multipath_attributes(mpp);
+ if (mpp->disable_queueing && VECTOR_SIZE(mpp->paths) != 0)
+ mpp->disable_queueing = 0;
/*
* properties selectors
+ *
+ * Ordering matters for some properties:
+ * - features after no_path_retry and retain_hwhandler
+ * - hwhandler after retain_hwhandler
+ * No guarantee that this list is complete, check code in
+ * propsel.c if in doubt.
*/
conf = get_multipath_config();
select_pgfailback(conf, mpp);
select_pgpolicy(conf, mpp);
select_selector(conf, mpp);
- select_hwhandler(conf, mpp);
select_no_path_retry(conf, mpp);
select_retain_hwhandler(conf, mpp);
select_features(conf, mpp);
+ select_hwhandler(conf, mpp);
select_rr_weight(conf, mpp);
select_minio(conf, mpp);
select_mode(conf, mpp);
select_marginal_path_double_failed_time(conf, mpp);
select_skip_kpartx(conf, mpp);
select_max_sectors_kb(conf, mpp);
+ select_ghost_delay(conf, mpp);
+ select_flush_on_last_del(conf, mpp);
sysfs_set_scsi_tmo(mpp, conf->checkint);
put_multipath_config(conf);
+
+ if (mpp->marginal_path_double_failed_time > 0 &&
+ mpp->marginal_path_err_sample_time > 0 &&
+ mpp->marginal_path_err_recheck_gap_time > 0 &&
+ mpp->marginal_path_err_rate_threshold >= 0)
+ start_io_err_stat_thread(vecs);
/*
* assign paths to path groups -- start with no groups and all paths
* in mpp->paths
}
if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF &&
- mpp->no_path_retry != cmpp->no_path_retry) {
+ !!strstr(mpp->features, "queue_if_no_path") !=
+ !!strstr(cmpp->features, "queue_if_no_path")) {
mpp->action = ACT_RELOAD;
condlog(3, "%s: set ACT_RELOAD (no_path_retry change)",
mpp->alias);
return;
}
- if (mpp->retain_hwhandler != RETAIN_HWHANDLER_ON &&
+ if ((mpp->retain_hwhandler != RETAIN_HWHANDLER_ON ||
+ strcmp(cmpp->hwhandler, "0") == 0) &&
(strlen(cmpp->hwhandler) != strlen(mpp->hwhandler) ||
strncmp(cmpp->hwhandler, mpp->hwhandler,
strlen(mpp->hwhandler)))) {
}
if (mpp->retain_hwhandler != RETAIN_HWHANDLER_UNDEF &&
- mpp->retain_hwhandler != cmpp->retain_hwhandler &&
+ !!strstr(mpp->features, "retain_attached_hw_handler") !=
+ !!strstr(cmpp->features, "retain_attached_hw_handler") &&
get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) {
mpp->action = ACT_RELOAD;
condlog(3, "%s: set ACT_RELOAD (retain_hwhandler change)",
}
sysfs_set_max_sectors_kb(mpp, 0);
+ if (is_daemon && mpp->ghost_delay > 0 && mpp->nr_active &&
+ pathcount(mpp, PATH_GHOST) == mpp->nr_active)
+ mpp->ghost_delay_tick = mpp->ghost_delay;
r = dm_addmap_create(mpp, params);
lock_multipath(mpp, 0);
case ACT_RELOAD:
sysfs_set_max_sectors_kb(mpp, 1);
+ if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP))
+ mpp->ghost_delay_tick = 0;
r = dm_addmap_reload(mpp, params, 0);
break;
case ACT_RESIZE:
sysfs_set_max_sectors_kb(mpp, 1);
+ if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP))
+ mpp->ghost_delay_tick = 0;
r = dm_addmap_reload(mpp, params, 1);
break;
put_multipath_config(conf);
if (r) {
sysfs_set_max_sectors_kb(mpp, 1);
+ if (mpp->ghost_delay_tick > 0 &&
+ pathcount(mpp, PATH_UP))
+ mpp->ghost_delay_tick = 0;
r = dm_addmap_reload(mpp, params, 0);
}
break;
verify_paths(mpp, vecs);
params[0] = '\0';
- if (setup_map(mpp, params, PARAMS_SIZE)) {
+ if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
remove_map(mpp, vecs, 0);
continue;
}
remove_feature(&mpp->features,
"queue_if_no_path");
}
- else if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF) {
- if (mpp->no_path_retry == NO_PATH_RETRY_FAIL) {
- condlog(3, "%s: unset queue_if_no_path feature",
- mpp->alias);
- if (!dm_queue_if_no_path(mpp->alias, 0))
- remove_feature(&mpp->features,
- "queue_if_no_path");
- } else {
- condlog(3, "%s: set queue_if_no_path feature",
- mpp->alias);
- if (!dm_queue_if_no_path(mpp->alias, 1))
- add_feature(&mpp->features,
- "queue_if_no_path");
- }
- }
if (!is_daemon && mpp->action != ACT_NOTHING) {
conf = get_multipath_config();
}
}
}
- if (setup_map(mpp, params, PARAMS_SIZE)) {
+ if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
condlog(0, "%s: failed to setup map", mpp->alias);
return 1;
}
"for reload map", mpp->alias, r);
return 1;
}
- if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF) {
- if (mpp->no_path_retry == NO_PATH_RETRY_FAIL)
- dm_queue_if_no_path(mpp->alias, 0);
- else
- dm_queue_if_no_path(mpp->alias, 1);
- }
return 0;
}
struct vectors;
-int setup_map (struct multipath * mpp, char * params, int params_size );
+int setup_map (struct multipath * mpp, char * params, int params_size,
+ struct vectors *vecs );
int domap (struct multipath * mpp, char * params, int is_daemon);
int reinstate_paths (struct multipath *mpp);
int coalesce_paths (struct vectors *vecs, vector curmp, char * refwwid, int force_reload, enum mpath_cmds cmd);
int get_refwwid (enum mpath_cmds cmd, char * dev, enum devtypes dev_type,
vector pathvec, char **wwid);
int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, int is_daemon);
-int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name);
struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type);
"%b %d %H:%M:%S", tb);
buff[sizeof(buff)-1] = '\0';
- fprintf(stdout, "%s | ", buff);
+ fprintf(stderr, "%s | ", buff);
}
- vfprintf(stdout, fmt, ap);
+ vfprintf(stderr, fmt, ap);
}
else
log_safe(prio + 3, fmt, ap);
#define DEFAULT_SKIP_KPARTX SKIP_KPARTX_OFF
#define DEFAULT_DISABLE_CHANGED_WWIDS 0
#define DEFAULT_MAX_SECTORS_KB MAX_SECTORS_KB_UNDEF
+#define DEFAULT_GHOST_DELAY GHOST_DELAY_OFF
#define DEFAULT_CHECKINT 5
#define MAX_CHECKINT(a) (a << 2)
#include <sys/types.h>
#include <time.h>
+#define FREE_CONST(p) do { free((void*)(unsigned long)p); p = NULL; } while(0)
#define MAX_WAIT 5
#define LOOPS_PER_SEC 5
-#define UUID_PREFIX "mpath-"
-#define UUID_PREFIX_LEN 6
-
static int dm_conf_verbosity;
#ifdef LIBDM_API_DEFERRED
/* DM_UDEV_DISABLE_LIBRARY_FALLBACK is added in dm_addmap */
return (mpp->skip_kpartx == SKIP_KPARTX_ON ?
MPATH_UDEV_NO_KPARTX_FLAG : 0) |
- (mpp->nr_active == 0 ?
+ ((mpp->nr_active == 0 || mpp->ghost_delay_tick > 0)?
MPATH_UDEV_NO_PATHS_FLAG : 0) |
(reload && !mpp->force_udev_reload ?
MPATH_UDEV_RELOAD_FLAG : 0);
return 0;
}
-int dm_get_status(char *name, char *outstatus)
+int dm_get_status(const char *name, char *outstatus)
{
int r = 1;
struct dm_task *dmt;
if (need_suspend &&
!dm_get_map(mapname, &mapsize, params) &&
strstr(params, "queue_if_no_path")) {
- if (!dm_queue_if_no_path((char *)mapname, 0))
+ if (!dm_queue_if_no_path(mapname, 0))
queue_if_no_path = 1;
else
/* Leave queue_if_no_path alone if unset failed */
} while (retries-- > 0);
if (queue_if_no_path == 1)
- dm_queue_if_no_path((char *)mapname, 1);
+ dm_queue_if_no_path(mapname, 1);
return 1;
}
}
int
-dm_fail_path(char * mapname, char * path)
+dm_fail_path(const char * mapname, char * path)
{
char message[32];
}
int
-dm_reinstate_path(char * mapname, char * path)
+dm_reinstate_path(const char * mapname, char * path)
{
char message[32];
}
int
-dm_queue_if_no_path(char *mapname, int enable)
+dm_queue_if_no_path(const char *mapname, int enable)
{
char *message;
}
static int
-dm_groupmsg (char * msg, char * mapname, int index)
+dm_groupmsg (const char * msg, const char * mapname, int index)
{
char message[32];
}
int
-dm_switchgroup(char * mapname, int index)
+dm_switchgroup(const char * mapname, int index)
{
return dm_groupmsg("switch", mapname, index);
}
int
-dm_enablegroup(char * mapname, int index)
+dm_enablegroup(const char * mapname, int index)
{
return dm_groupmsg("enable", mapname, index);
}
int
-dm_disablegroup(char * mapname, int index)
+dm_disablegroup(const char * mapname, int index)
{
return dm_groupmsg("disable", mapname, index);
}
}
int
-dm_geteventnr (char *name)
+dm_geteventnr (const char *name)
{
struct dm_info info;
map = dm_task_get_name(dmt);
if (map && strlen(map))
- response = STRDUP((char *)dm_task_get_name(dmt));
+ response = STRDUP((const char *)dm_task_get_name(dmt));
dm_task_destroy(dmt);
return response;
}
static int
-dm_get_deferred_remove (char * mapname)
+dm_get_deferred_remove (const char * mapname)
{
struct dm_info info;
return r;
}
-void dm_reassign_deps(char *table, char *dep, char *newdep)
+void dm_reassign_deps(char *table, const char *dep, const char *newdep)
{
- char *p, *n;
- char *newtable;
+ char *n;
+ const char *p, *newtable;
newtable = strdup(table);
if (!newtable)
n += strlen(newdep);
p += strlen(dep);
strcat(n, p);
- free(newtable);
+ FREE_CONST(newtable);
}
int dm_reassign_table(const char *name, char *old, char *new)
#define MPATH_UDEV_NO_PATHS_FLAG 0
#endif
+#define UUID_PREFIX "mpath-"
+#define UUID_PREFIX_LEN (sizeof(UUID_PREFIX) - 1)
+
void dm_init(int verbosity);
void libmp_dm_init(void);
void libmp_udev_set_sync_support(int on);
int dm_addmap_reload (struct multipath *mpp, char *params, int flush);
int dm_map_present (const char *);
int dm_get_map(const char *, unsigned long long *, char *);
-int dm_get_status(char *, char *);
+int dm_get_status(const char *, char *);
int dm_type(const char *, char *);
int dm_is_mpath(const char *);
int _dm_flush_map (const char *, int, int, int, int);
_dm_flush_map(mapname, 1, 0, 1, retries)
int dm_cancel_deferred_remove(struct multipath *mpp);
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);
-int dm_switchgroup(char * mapname, int index);
-int dm_enablegroup(char * mapname, int index);
-int dm_disablegroup(char * mapname, int index);
+int dm_fail_path(const char * mapname, char * path);
+int dm_reinstate_path(const char * mapname, char * path);
+int dm_queue_if_no_path(const char *mapname, int enable);
+int dm_switchgroup(const char * mapname, int index);
+int dm_enablegroup(const char * mapname, int index);
+int dm_disablegroup(const char * mapname, int index);
int dm_get_maps (vector mp);
-int dm_geteventnr (char *name);
+int dm_geteventnr (const char *name);
int dm_is_suspended(const char *name);
int dm_get_major_minor (const char *name, int *major, int *minor);
char * dm_mapname(int major, int minor);
}
static int
-print_int (char *buff, int len, void *ptr)
+print_int (char *buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
- return snprintf(buff, len, "%i", *int_ptr);
+ return snprintf(buff, len, "%li", v);
}
static int
-print_nonzero (char *buff, int len, void *ptr)
+print_nonzero (char *buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
- if (!*int_ptr)
- return 0;
- return snprintf(buff, len, "%i", *int_ptr);
+ return snprintf(buff, len, "%li", v);
}
static int
-print_str (char *buff, int len, void *ptr)
+print_str (char *buff, int len, const char *ptr)
{
- char **str_ptr = (char **)ptr;
- if (!*str_ptr)
- return 0;
- return snprintf(buff, len, "\"%s\"", *str_ptr);
+ return snprintf(buff, len, "\"%s\"", ptr);
}
static int
-print_yes_no (char *buff, int len, void *ptr)
+print_yes_no (char *buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
return snprintf(buff, len, "\"%s\"",
- (*int_ptr == YN_NO)? "no" : "yes");
+ (v == YN_NO)? "no" : "yes");
}
static int
-print_yes_no_undef (char *buff, int len, void *ptr)
+print_yes_no_undef (char *buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
- if (!*int_ptr)
- return 0;
return snprintf(buff, len, "\"%s\"",
- (*int_ptr == YNU_NO)? "no" : "yes");
+ (v == YNU_NO)? "no" : "yes");
}
#define declare_def_handler(option, function) \
#define declare_def_snprint(option, function) \
static int \
-snprint_def_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_def_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- return function (buff, len, &conf->option); \
+ return function (buff, len, conf->option); \
}
#define declare_def_snprint_defint(option, function, value) \
static int \
-snprint_def_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_def_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
int i = value; \
if (!conf->option) \
- return function (buff, len, &i); \
- return function (buff, len, &conf->option); \
+ return function (buff, len, i); \
+ return function (buff, len, conf->option); \
}
#define declare_def_snprint_defstr(option, function, value) \
static int \
-snprint_def_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_def_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- char *s = value; \
+ static const char *s = value; \
if (!conf->option) \
- return function (buff, len, &s); \
- return function (buff, len, &conf->option); \
+ return function (buff, len, s); \
+ return function (buff, len, conf->option); \
}
#define declare_hw_handler(option, function) \
#define declare_hw_snprint(option, function) \
static int \
-snprint_hw_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_hw_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- struct hwentry * hwe = (struct hwentry *)data; \
- return function (buff, len, &hwe->option); \
+ const struct hwentry * hwe = (const struct hwentry *)data; \
+ return function (buff, len, hwe->option); \
}
#define declare_ovr_handler(option, function) \
#define declare_ovr_snprint(option, function) \
static int \
-snprint_ovr_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_ovr_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- return function (buff, len, &conf->overrides->option); \
+ return function (buff, len, conf->overrides->option); \
}
#define declare_mp_handler(option, function) \
#define declare_mp_snprint(option, function) \
static int \
-snprint_mp_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_mp_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- struct mpentry * mpe = (struct mpentry *)data; \
- return function (buff, len, &mpe->option); \
+ const struct mpentry * mpe = (const struct mpentry *)data; \
+ return function (buff, len, mpe->option); \
}
declare_def_handler(checkint, set_int)
declare_def_handler(queue_without_daemon, set_yes_no)
static int
snprint_def_queue_without_daemon (struct config *conf,
- char * buff, int len, void * data)
+ char * buff, int len, const void * data)
{
switch (conf->queue_without_daemon) {
case QUE_NO_DAEMON_OFF:
#define declare_def_attr_snprint(option, function) \
static int \
-snprint_def_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_def_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- return function (buff, len, &conf->option, \
- &conf->attribute_flags); \
+ return function (buff, len, conf->option, \
+ conf->attribute_flags); \
}
#define declare_mp_attr_handler(option, function) \
#define declare_mp_attr_snprint(option, function) \
static int \
-snprint_mp_ ## option (struct config *conf, char * buff, int len, void * data) \
+snprint_mp_ ## option (struct config *conf, char * buff, int len, \
+ const void * data) \
{ \
- struct mpentry * mpe = (struct mpentry *)data; \
- return function (buff, len, &mpe->option, \
- &mpe->attribute_flags); \
+ const struct mpentry * mpe = (const struct mpentry *)data; \
+ return function (buff, len, mpe->option, \
+ mpe->attribute_flags); \
}
static int
}
static int
-print_mode(char * buff, int len, void *ptr, int *flags)
+print_mode(char * buff, int len, long v, int flags)
{
- mode_t *mode_ptr = (mode_t *)ptr;
- if ((*flags & (1 << ATTR_MODE)) == 0)
+ mode_t mode = (mode_t)v;
+ if ((flags & (1 << ATTR_MODE)) == 0)
return 0;
- return snprintf(buff, len, "0%o", *mode_ptr);
+ return snprintf(buff, len, "0%o", mode);
}
static int
-print_uid(char * buff, int len, void *ptr, int *flags)
+print_uid(char * buff, int len, long v, int flags)
{
- uid_t *uid_ptr = (uid_t *)ptr;
- if ((*flags & (1 << ATTR_UID)) == 0)
+ uid_t uid = (uid_t)v;
+ if ((flags & (1 << ATTR_UID)) == 0)
return 0;
- return snprintf(buff, len, "0%o", *uid_ptr);
+ return snprintf(buff, len, "0%o", uid);
}
static int
-print_gid(char * buff, int len, void *ptr, int *flags)
+print_gid(char * buff, int len, long v, int flags)
{
- gid_t *gid_ptr = (gid_t *)ptr;
- if ((*flags & (1 << ATTR_GID)) == 0)
+ gid_t gid = (gid_t)v;
+ if ((flags & (1 << ATTR_GID)) == 0)
return 0;
- return snprintf(buff, len, "0%o", *gid_ptr);
+ return snprintf(buff, len, "0%o", gid);
}
declare_def_attr_handler(mode, set_mode)
}
int
-print_fast_io_fail(char * buff, int len, void *ptr)
+print_fast_io_fail(char * buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
-
- if (*int_ptr == MP_FAST_IO_FAIL_UNSET)
+ if (v == MP_FAST_IO_FAIL_UNSET)
return 0;
- if (*int_ptr == MP_FAST_IO_FAIL_OFF)
+ if (v == MP_FAST_IO_FAIL_OFF)
return snprintf(buff, len, "\"off\"");
- if (*int_ptr == MP_FAST_IO_FAIL_ZERO)
+ if (v == MP_FAST_IO_FAIL_ZERO)
return snprintf(buff, len, "0");
- return snprintf(buff, len, "%d", *int_ptr);
+ return snprintf(buff, len, "%ld", v);
}
declare_def_handler(fast_io_fail, set_fast_io_fail)
}
int
-print_dev_loss(char * buff, int len, void *ptr)
+print_dev_loss(char * buff, int len, unsigned long v)
{
- unsigned int *uint_ptr = (unsigned int *)ptr;
-
- if (!*uint_ptr)
- return 0;
- if (*uint_ptr >= MAX_DEV_LOSS_TMO)
+ if (v >= MAX_DEV_LOSS_TMO)
return snprintf(buff, len, "\"infinity\"");
- return snprintf(buff, len, "%u", *uint_ptr);
+ return snprintf(buff, len, "%lu", v);
}
declare_def_handler(dev_loss, set_dev_loss)
}
int
-print_pgpolicy(char * buff, int len, void *ptr)
+print_pgpolicy(char * buff, int len, long pgpolicy)
{
char str[POLICY_NAME_SIZE];
- int pgpolicy = *(int *)ptr;
if (!pgpolicy)
return 0;
}
static int
-snprint_max_fds (struct config *conf, char * buff, int len, void * data)
+snprint_max_fds (struct config *conf, char * buff, int len, const void * data)
{
int r = 0, max_fds;
}
int
-print_rr_weight (char * buff, int len, void *ptr)
+print_rr_weight (char * buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
-
- if (!*int_ptr)
+ if (!v)
return 0;
- if (*int_ptr == RR_WEIGHT_PRIO)
+ if (v == RR_WEIGHT_PRIO)
return snprintf(buff, len, "\"priorities\"");
- if (*int_ptr == RR_WEIGHT_NONE)
+ if (v == RR_WEIGHT_NONE)
return snprintf(buff, len, "\"uniform\"");
return 0;
}
int
-print_pgfailback (char * buff, int len, void *ptr)
+print_pgfailback (char * buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
-
- switch(*int_ptr) {
+ switch(v) {
case FAILBACK_UNDEF:
return 0;
case -FAILBACK_MANUAL:
case -FAILBACK_FOLLOWOVER:
return snprintf(buff, len, "\"followover\"");
default:
- return snprintf(buff, len, "%i", *int_ptr);
+ return snprintf(buff, len, "%li", v);
}
}
}
int
-print_no_path_retry(char * buff, int len, void *ptr)
+print_no_path_retry(char * buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
-
- switch(*int_ptr) {
+ switch(v) {
case NO_PATH_RETRY_UNDEF:
return 0;
case NO_PATH_RETRY_FAIL:
case NO_PATH_RETRY_QUEUE:
return snprintf(buff, len, "\"queue\"");
default:
- return snprintf(buff, len, "%i", *int_ptr);
+ return snprintf(buff, len, "%li", v);
}
}
}
static int
-snprint_def_log_checker_err (struct config *conf, char * buff, int len, void * data)
+snprint_def_log_checker_err (struct config *conf, char * buff, int len,
+ const void * data)
{
if (conf->log_checker_err == LOG_CHKR_ERR_ONCE)
return snprintf(buff, len, "once");
static int
snprint_def_reservation_key (struct config *conf, char * buff, int len,
- void * data)
+ const void * data)
{
return print_reservation_key(buff, len, conf->reservation_key,
conf->prkey_source);
static int
snprint_mp_reservation_key (struct config *conf, char * buff, int len,
- void * data)
+ const void * data)
{
- struct mpentry * mpe = (struct mpentry *)data;
+ const struct mpentry * mpe = (const struct mpentry *)data;
return print_reservation_key(buff, len, mpe->reservation_key,
mpe->prkey_source);
}
}
int
-print_off_int_undef(char * buff, int len, void *ptr)
+print_off_int_undef(char * buff, int len, long v)
{
- int *int_ptr = (int *)ptr;
-
- switch(*int_ptr) {
+ switch(v) {
case NU_UNDEF:
return 0;
case NU_NO:
return snprintf(buff, len, "\"no\"");
default:
- return snprintf(buff, len, "%i", *int_ptr);
+ return snprintf(buff, len, "%li", v);
}
}
declare_mp_handler(marginal_path_double_failed_time, set_off_int_undef)
declare_mp_snprint(marginal_path_double_failed_time, print_off_int_undef)
+declare_def_handler(ghost_delay, set_off_int_undef)
+declare_def_snprint(ghost_delay, print_off_int_undef)
+declare_ovr_handler(ghost_delay, set_off_int_undef)
+declare_ovr_snprint(ghost_delay, print_off_int_undef)
+declare_hw_handler(ghost_delay, set_off_int_undef)
+declare_hw_snprint(ghost_delay, print_off_int_undef)
+declare_mp_handler(ghost_delay, set_off_int_undef)
+declare_mp_snprint(ghost_delay, print_off_int_undef)
static int
declare_ble_handler(elist_property)
static int
-snprint_def_uxsock_timeout(struct config *conf, char * buff, int len, void * data)
+snprint_def_uxsock_timeout(struct config *conf, char * buff, int len,
+ const void * data)
{
return snprintf(buff, len, "%u", conf->uxsock_timeout);
}
static int
-snprint_ble_simple (struct config *conf, char * buff, int len, void * data)
+snprint_ble_simple (struct config *conf, char * buff, int len,
+ const void * data)
{
- struct blentry * ble = (struct blentry *)data;
+ const struct blentry * ble = (const struct blentry *)data;
return snprintf(buff, len, "\"%s\"", ble->str);
}
declare_ble_device_handler(product, elist_device, NULL, buff)
static int
-snprint_bled_vendor (struct config *conf, char * buff, int len, void * data)
+snprint_bled_vendor (struct config *conf, char * buff, int len,
+ const void * data)
{
- struct blentry_device * bled = (struct blentry_device *)data;
+ const struct blentry_device * bled =
+ (const struct blentry_device *)data;
return snprintf(buff, len, "\"%s\"", bled->vendor);
}
static int
-snprint_bled_product (struct config *conf, char * buff, int len, void * data)
+snprint_bled_product (struct config *conf, char * buff, int len,
+ const void * data)
{
- struct blentry_device * bled = (struct blentry_device *)data;
+ const struct blentry_device * bled =
+ (const struct blentry_device *)data;
return snprintf(buff, len, "\"%s\"", bled->product);
}
}
static int
-snprint_deprecated (struct config *conf, char * buff, int len, void * data)
+snprint_deprecated (struct config *conf, char * buff, int len,
+ const void * data)
{
return 0;
}
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);
+ install_keyword("ghost_delay", &def_ghost_delay_handler, &snprint_def_ghost_delay);
__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("marginal_path_double_failed_time", &hw_marginal_path_double_failed_time_handler, &snprint_hw_marginal_path_double_failed_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_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay);
install_sublevel_end();
install_keyword_root("overrides", &overrides_handler);
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("ghost_delay", &ovr_ghost_delay_handler, &snprint_ovr_ghost_delay);
install_keyword_root("multipaths", &multipaths_handler);
install_keyword_multi("multipath", &multipath_handler, NULL);
install_keyword("marginal_path_double_failed_time", &mp_marginal_path_double_failed_time_handler, &snprint_mp_marginal_path_double_failed_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_keyword("ghost_delay", &mp_ghost_delay_handler, &snprint_mp_ghost_delay);
install_sublevel_end();
}
#include "prio.h"
#include "defaults.h"
#include "prioritizers/alua_rtpg.h"
+#include "foreign.h"
int
alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
- char *wwid, int flag, struct path **pp_ptr)
+ const char *wwid, int flag, struct path **pp_ptr)
{
int err = PATHINFO_FAILED;
struct path * pp;
if (!devname)
return PATHINFO_FAILED;
- pp = find_path_by_dev(pathvec, (char *)devname);
+ pp = find_path_by_dev(pathvec, devname);
if (!pp) {
char devt[BLK_DEV_SIZE];
dev_t devnum = udev_device_get_devnum(udevice);
}
*timeout = t;
- return 0;
+ return 1;
}
int
return 0;
}
-int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name)
+int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name)
{
int proto_id;
return sysfs_get_host_pci_name(pp, adapter_name);
}
-int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
+int sysfs_get_host_pci_name(const struct path *pp, char *pci_name)
{
struct udev_device *hostdev, *parent;
char host_name[HOST_NAME_LEN];
return 1;
}
-int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address)
+int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address)
{
struct udev_device *hostdev;
char host_name[HOST_NAME_LEN];
io_hdr.dxferp = resp;
io_hdr.cmdp = inqCmdBlk;
io_hdr.sbp = sense_b;
- io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.timeout = DEF_TIMEOUT * 1000;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0)
return -1;
#define DEFAULT_SGIO_LEN 254
static int
-sgio_get_vpd (unsigned char * buff, int maxlen, int fd)
+sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg)
{
int len = DEFAULT_SGIO_LEN;
return -1;
}
retry:
- if (0 == do_inq(fd, 0, 1, 0x83, buff, len)) {
+ if (0 == do_inq(fd, 0, 1, pg, buff, len)) {
len = buff[3] + (buff[2] << 8);
if (len >= maxlen)
return len;
if (len > DEFAULT_SGIO_LEN)
goto retry;
- return 0;
+ return len;
}
return -1;
}
parse_vpd_pg83(const unsigned char *in, size_t in_len,
char *out, size_t out_len)
{
- unsigned char *d;
- unsigned char *vpd = NULL;
+ const unsigned char *d;
+ const unsigned char *vpd = NULL;
int len = -ENODATA, vpd_type, vpd_len, prio = -1, i, naa_prio;
- d = (unsigned char *)in + 4;
- while (d < (unsigned char *)in + in_len) {
+ d = in + 4;
+ while (d < in + in_len) {
/* Select 'association: LUN' */
if ((d[1] & 0x30) != 0) {
d += d[3] + 4;
out[len] = '\0';
}
} else if (vpd_type == 0x1) {
- unsigned char *p;
+ const unsigned char *p;
int p_len;
out[0] = '1';
unsigned char buff[4096];
memset(buff, 0x0, 4096);
- if (sgio_get_vpd(buff, 4096, fd) <= 0) {
+ if (sgio_get_vpd(buff, 4096, fd, pg) <= 0) {
condlog(3, "failed to issue vpd inquiry for pg%02x",
pg);
return -errno;
len = parse_vpd_pg80(buff, str, maxlen);
else if (pg == 0x83)
len = parse_vpd_pg83(buff, buff_len, str, maxlen);
- else
+ else if (pg == 0xc9 && maxlen >= 8) {
+ len = buff_len < 8 ? -ENODATA :
+ (buff_len <= maxlen ? buff_len : maxlen);
+ memcpy (str, buff, len);
+ } else
len = -ENOSYS;
return len;
condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
- if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
+ if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
return 1;
condlog(3, "%s: product = %s", pp->dev, pp->product_id);
- if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) < 0)
+ if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) < 0)
return 1;
condlog(3, "%s: rev = %s", pp->dev, pp->rev);
{
struct udev_device *parent;
const char *attr_path = NULL;
+ const char *attr;
attr_path = udev_device_get_sysname(pp->udev);
if (!attr_path)
&pp->sg_id.host_no,
&pp->sg_id.scsi_id) != 2)
return 1;
- pp->sg_id.channel = 0;
- pp->sg_id.lun = 0;
- parent = udev_device_get_parent(pp->udev);
+ parent = udev_device_get_parent_with_subsystem_devtype(pp->udev,
+ "nvme", NULL);
if (!parent)
return 1;
+ attr = udev_device_get_sysattr_value(pp->udev, "nsid");
+ pp->sg_id.lun = attr ? atoi(attr) : 0;
+
+ attr = udev_device_get_sysattr_value(parent, "cntlid");
+ pp->sg_id.channel = attr ? atoi(attr) : 0;
+
snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME");
- snprintf(pp->product_id, SCSI_PRODUCT_SIZE, "%s",
+ snprintf(pp->product_id, PATH_PRODUCT_SIZE, "%s",
udev_device_get_sysattr_value(parent, "model"));
snprintf(pp->serial, SERIAL_SIZE, "%s",
udev_device_get_sysattr_value(parent, "serial"));
- snprintf(pp->rev, SCSI_REV_SIZE, "%s",
+ snprintf(pp->rev, PATH_REV_SIZE, "%s",
udev_device_get_sysattr_value(parent, "firmware_rev"));
condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
- if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
+ if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
return 1;
condlog(3, "%s: product = %s", pp->dev, pp->product_id);
- if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) <= 0)
+ if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) <= 0)
return 1;
condlog(3, "%s: rev = %s", pp->dev, pp->rev);
* limited by DI_BLACKLIST and occurs before this debug
* message with the mask value.
*/
- if (pp->udev && filter_property(conf, pp->udev) > 0)
+ if (pp->udev && (is_claimed_by_foreign(pp->udev) ||
+ filter_property(conf, pp->udev) > 0))
return PATHINFO_SKIPPED;
if (filter_devnode(conf->blist_devnode,
int get_vpd_sgio (int fd, int pg, char * str, int maxlen);
int pathinfo (struct path * pp, struct config * conf, int mask);
int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
- char *wwid, int flag, struct path **pp_ptr);
+ const 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);
int sysfs_set_scsi_tmo (struct multipath *mpp, int checkint);
int sysfs_get_timeout(struct path *pp, unsigned int *timeout);
-int sysfs_get_host_pci_name(struct path *pp, char *pci_name);
-int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address);
+int sysfs_get_host_pci_name(const struct path *pp, char *pci_name);
+int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address);
+int sysfs_get_host_adapter_name(const struct path *pp,
+ char *adapter_name);
ssize_t sysfs_get_vpd (struct udev_device * udev, int pg, unsigned char * buff,
size_t len);
int sysfs_get_asymmetric_access_state(struct path *pp,
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include "generic.h"
+#include "dm-generic.h"
+#include "structs.h"
+#include "structs_vec.h"
+#include "config.h"
+#include "print.h"
+
+static const struct _vector*
+dm_mp_get_pgs(const struct gen_multipath *gmp)
+{
+ return vector_convert(NULL, gen_multipath_to_dm(gmp)->pg,
+ struct pathgroup, dm_pathgroup_to_gen);
+}
+
+static void dm_mp_rel_pgs(const struct gen_multipath *gmp,
+ const struct _vector* v)
+{
+ vector_free_const(v);
+}
+
+static const struct _vector*
+dm_pg_get_paths(const struct gen_pathgroup *gpg)
+{
+ return vector_convert(NULL, gen_pathgroup_to_dm(gpg)->paths,
+ struct path, dm_path_to_gen);
+}
+
+static void dm_mp_rel_paths(const struct gen_pathgroup *gpg,
+ const struct _vector* v)
+{
+ vector_free_const(v);
+}
+
+const struct gen_multipath_ops dm_gen_multipath_ops = {
+ .get_pathgroups = dm_mp_get_pgs,
+ .rel_pathgroups = dm_mp_rel_pgs,
+ .snprint = snprint_multipath_attr,
+ .style = snprint_multipath_style,
+};
+
+const struct gen_pathgroup_ops dm_gen_pathgroup_ops = {
+ .get_paths = dm_pg_get_paths,
+ .rel_paths = dm_mp_rel_paths,
+ .snprint = snprint_pathgroup_attr,
+};
+
+const struct gen_path_ops dm_gen_path_ops = {
+ .snprint = snprint_path_attr,
+};
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+ */
+#ifndef _DM_GENERIC_H
+#define _DM_GENERIC_H
+#include "generic.h"
+#include "list.h" /* for container_of */
+#include "structs.h"
+
+#define dm_multipath_to_gen(mpp) (&((mpp)->generic_mp))
+#define gen_multipath_to_dm(gm) \
+ container_of_const((gm), struct multipath, generic_mp)
+
+#define dm_pathgroup_to_gen(pg) (&(pg->generic_pg))
+#define gen_pathgroup_to_dm(gpg) \
+ container_of_const((gpg), struct pathgroup, generic_pg)
+
+#define dm_path_to_gen(pp) (&((pp)->generic_path))
+#define gen_path_to_dm(gp) \
+ container_of_const((gp), struct path, generic_path)
+
+extern const struct gen_multipath_ops dm_gen_multipath_ops;
+extern const struct gen_pathgroup_ops dm_gen_pathgroup_ops;
+extern const struct gen_path_ops dm_gen_path_ops;
+
+#endif /* _DM_GENERIC_H */
nr_priority_groups = VECTOR_SIZE(mp->pg);
initial_pg_nr = (nr_priority_groups ? mp->bestpg : 0);
- f = STRDUP(mp->features);
-
- /*
- * We have to set 'queue_if_no_path' here even
- * to avoid path failures during map reload.
- */
- if (mp->no_path_retry == NO_PATH_RETRY_FAIL) {
- /* remove queue_if_no_path settings */
- condlog(3, "%s: remove queue_if_no_path from '%s'",
- mp->alias, mp->features);
- remove_feature(&f, no_path_retry);
- } else if (mp->no_path_retry != NO_PATH_RETRY_UNDEF) {
- add_feature(&f, no_path_retry);
+ if (mp->no_path_retry != NO_PATH_RETRY_UNDEF &&
+ mp->no_path_retry != NO_PATH_RETRY_FAIL) {
+ add_feature(&mp->features, no_path_retry);
}
if (mp->retain_hwhandler == RETAIN_HWHANDLER_ON &&
get_linux_version_code() < KERNEL_VERSION(4, 3, 0))
- add_feature(&f, retain_hwhandler);
+ add_feature(&mp->features, retain_hwhandler);
+
+ f = STRDUP(mp->features);
APPEND(p, end, "%s %s %i %i", f, mp->hwhandler, nr_priority_groups,
initial_pg_nr);
APPEND(p, end, " %s %d", pp->dev_t, tmp_minio);
}
}
- APPEND(p, end, "\n");
FREE(f);
condlog(3, "%s: assembled map [%s]", mp->alias, params);
int num_paths = 0;
int num_paths_args = 0;
int def_minio = 0;
- int no_path_retry = NO_PATH_RETRY_UNDEF;
struct path * pp;
struct pathgroup * pgp;
return 1;
num_features = atoi(mpp->features);
- no_path_retry = mpp->no_path_retry;
- mpp->no_path_retry = NO_PATH_RETRY_UNDEF;
for (i = 0; i < num_features; i++) {
p += get_word(p, &word);
FREE(word);
return 1;
}
- setup_feature(mpp, word);
FREE(word);
}
/*
- * Reset no_path_retry.
- * - if not set from features
- * - if queue_if_no_path is set from features but
- * no_path_retry > 0 is selected.
- */
- if ((mpp->no_path_retry == NO_PATH_RETRY_UNDEF ||
- mpp->no_path_retry == NO_PATH_RETRY_QUEUE) &&
- mpp->no_path_retry != no_path_retry)
- mpp->no_path_retry = no_path_retry;
-
- /*
* hwhandler
*/
p += get_word(p, &mpp->hwhandler);
if (!pgp)
goto out;
- if (store_pathgroup(mpp->pg, pgp))
+ if (add_pathgroup(mpp, pgp))
goto out;
p += get_word(p, &word);
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+*/
+
+#include <sys/sysmacros.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <dlfcn.h>
+#include <libudev.h>
+#include "vector.h"
+#include "debug.h"
+#include "util.h"
+#include "foreign.h"
+#include "structs.h"
+#include "structs_vec.h"
+#include "print.h"
+
+static vector foreigns;
+
+/* This protects vector foreigns */
+static pthread_rwlock_t foreign_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+static void rdlock_foreigns(void)
+{
+ pthread_rwlock_rdlock(&foreign_lock);
+}
+
+static void wrlock_foreigns(void)
+{
+ pthread_rwlock_wrlock(&foreign_lock);
+}
+
+static void unlock_foreigns(void *unused)
+{
+ pthread_rwlock_unlock(&foreign_lock);
+}
+
+#define get_dlsym(foreign, sym, lbl) \
+ do { \
+ foreign->sym = dlsym(foreign->handle, #sym); \
+ if (foreign->sym == NULL) { \
+ condlog(0, "%s: symbol \"%s\" not found in \"%s\"", \
+ __func__, #sym, foreign->name); \
+ goto lbl; \
+ } \
+ } while(0)
+
+static void free_foreign(struct foreign *fgn)
+{
+ struct context *ctx;
+
+ if (fgn == NULL)
+ return;
+
+ ctx = fgn->context;
+ fgn->context = NULL;
+ if (ctx != NULL)
+ fgn->cleanup(ctx);
+
+ if (fgn->handle != NULL)
+ dlclose(fgn->handle);
+ free(fgn);
+}
+
+void _cleanup_foreign(void)
+{
+ struct foreign *fgn;
+ int i;
+
+ if (foreigns == NULL)
+ return;
+
+ vector_foreach_slot_backwards(foreigns, fgn, i) {
+ vector_del_slot(foreigns, i);
+ free_foreign(fgn);
+ }
+ vector_free(foreigns);
+ foreigns = NULL;
+}
+
+void cleanup_foreign(void)
+{
+ wrlock_foreigns();
+ _cleanup_foreign();
+ unlock_foreigns(NULL);
+}
+
+static const char foreign_pattern[] = "libforeign-*.so";
+
+static int select_foreign_libs(const struct dirent *di)
+{
+
+ return fnmatch(foreign_pattern, di->d_name, FNM_FILE_NAME) == 0;
+}
+
+static int _init_foreign(const char *multipath_dir)
+{
+ char pathbuf[PATH_MAX];
+ struct dirent **di;
+ int r, i;
+
+ foreigns = vector_alloc();
+ if (foreigns == NULL)
+ return -ENOMEM;
+
+ r = scandir(multipath_dir, &di, select_foreign_libs, alphasort);
+
+ if (r == 0) {
+ condlog(3, "%s: no foreign multipath libraries found",
+ __func__);
+ return 0;
+ } else if (r < 0) {
+ r = errno;
+ condlog(1, "%s: error %d scanning foreign multipath libraries",
+ __func__, r);
+ _cleanup_foreign();
+ return -r;
+ }
+
+ pthread_cleanup_push(free, di);
+ for (i = 0; i < r; i++) {
+ const char *msg, *fn, *c;
+ struct foreign *fgn;
+ int len, namesz;
+
+ fn = di[i]->d_name;
+
+ len = strlen(fn);
+ c = strchr(fn, '-');
+ if (len < sizeof(foreign_pattern) - 1 || c == NULL) {
+ condlog(0, "%s: bad file name %s, fnmatch error?",
+ __func__, fn);
+ continue;
+ }
+ c++;
+ condlog(4, "%s: found %s", __func__, fn);
+
+ namesz = len - sizeof(foreign_pattern) + 3;
+ fgn = malloc(sizeof(*fgn) + namesz);
+ if (fgn == NULL)
+ continue;
+ memset(fgn, 0, sizeof(*fgn));
+ strlcpy((char*)fgn + offsetof(struct foreign, name), c, namesz);
+
+ snprintf(pathbuf, sizeof(pathbuf), "%s/%s", multipath_dir, fn);
+ fgn->handle = dlopen(pathbuf, RTLD_NOW|RTLD_LOCAL);
+ msg = dlerror();
+ if (fgn->handle == NULL) {
+ condlog(1, "%s: failed to dlopen %s: %s", __func__,
+ pathbuf, msg);
+ goto dl_err;
+ }
+
+ get_dlsym(fgn, init, dl_err);
+ get_dlsym(fgn, cleanup, dl_err);
+ get_dlsym(fgn, add, dl_err);
+ get_dlsym(fgn, change, dl_err);
+ get_dlsym(fgn, delete, dl_err);
+ get_dlsym(fgn, delete_all, dl_err);
+ get_dlsym(fgn, check, dl_err);
+ get_dlsym(fgn, lock, dl_err);
+ get_dlsym(fgn, unlock, dl_err);
+ get_dlsym(fgn, get_multipaths, dl_err);
+ get_dlsym(fgn, release_multipaths, dl_err);
+ get_dlsym(fgn, get_paths, dl_err);
+ get_dlsym(fgn, release_paths, dl_err);
+
+ fgn->context = fgn->init(LIBMP_FOREIGN_API, fgn->name);
+ if (fgn->context == NULL) {
+ condlog(0, "%s: init() failed for %s", __func__, fn);
+ goto dl_err;
+ }
+
+ if (vector_alloc_slot(foreigns) == NULL) {
+ goto dl_err;
+ }
+
+ vector_set_slot(foreigns, fgn);
+ condlog(3, "foreign library \"%s\" loaded successfully",
+ fgn->name);
+
+ continue;
+
+ dl_err:
+ free_foreign(fgn);
+ }
+ pthread_cleanup_pop(1);
+ return 0;
+}
+
+int init_foreign(const char *multipath_dir)
+{
+ int ret;
+
+ wrlock_foreigns();
+
+ if (foreigns != NULL) {
+ unlock_foreigns(NULL);
+ condlog(0, "%s: already initialized", __func__);
+ return -EEXIST;
+ }
+
+ pthread_cleanup_push(unlock_foreigns, NULL);
+ ret = _init_foreign(multipath_dir);
+ pthread_cleanup_pop(1);
+
+ return ret;
+}
+
+int add_foreign(struct udev_device *udev)
+{
+ struct foreign *fgn;
+ dev_t dt;
+ int j;
+ int r = FOREIGN_IGNORED;
+
+ if (udev == NULL) {
+ condlog(1, "%s called with NULL udev", __func__);
+ return FOREIGN_ERR;
+ }
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return FOREIGN_ERR;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ dt = udev_device_get_devnum(udev);
+ vector_foreach_slot(foreigns, fgn, j) {
+ r = fgn->add(fgn->context, udev);
+
+ if (r == FOREIGN_CLAIMED) {
+ condlog(3, "%s: foreign \"%s\" claims device %d:%d",
+ __func__, fgn->name, major(dt), minor(dt));
+ break;
+ } else if (r == FOREIGN_OK) {
+ condlog(4, "%s: foreign \"%s\" owns device %d:%d",
+ __func__, fgn->name, major(dt), minor(dt));
+ break;
+ } else if (r != FOREIGN_IGNORED) {
+ condlog(1, "%s: unexpected return value %d from \"%s\"",
+ __func__, r, fgn->name);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return r;
+}
+
+int change_foreign(struct udev_device *udev)
+{
+ struct foreign *fgn;
+ int j;
+ dev_t dt;
+ int r = FOREIGN_IGNORED;
+
+ if (udev == NULL) {
+ condlog(1, "%s called with NULL udev", __func__);
+ return FOREIGN_ERR;
+ }
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return FOREIGN_ERR;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ dt = udev_device_get_devnum(udev);
+ vector_foreach_slot(foreigns, fgn, j) {
+ r = fgn->change(fgn->context, udev);
+
+ if (r == FOREIGN_OK) {
+ condlog(4, "%s: foreign \"%s\" completed %d:%d",
+ __func__, fgn->name, major(dt), minor(dt));
+ break;
+ } else if (r != FOREIGN_IGNORED) {
+ condlog(1, "%s: unexpected return value %d from \"%s\"",
+ __func__, r, fgn->name);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return r;
+}
+
+int delete_foreign(struct udev_device *udev)
+{
+ struct foreign *fgn;
+ int j;
+ dev_t dt;
+ int r = FOREIGN_IGNORED;
+
+ if (udev == NULL) {
+ condlog(1, "%s called with NULL udev", __func__);
+ return FOREIGN_ERR;
+ }
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return FOREIGN_ERR;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ dt = udev_device_get_devnum(udev);
+ vector_foreach_slot(foreigns, fgn, j) {
+ r = fgn->delete(fgn->context, udev);
+
+ if (r == FOREIGN_OK) {
+ condlog(3, "%s: foreign \"%s\" deleted device %d:%d",
+ __func__, fgn->name, major(dt), minor(dt));
+ break;
+ } else if (r != FOREIGN_IGNORED) {
+ condlog(1, "%s: unexpected return value %d from \"%s\"",
+ __func__, r, fgn->name);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return r;
+}
+
+int delete_all_foreign(void)
+{
+ struct foreign *fgn;
+ int j;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return FOREIGN_ERR;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, j) {
+ int r;
+
+ r = fgn->delete_all(fgn->context);
+ if (r != FOREIGN_IGNORED && r != FOREIGN_OK) {
+ condlog(1, "%s: unexpected return value %d from \"%s\"",
+ __func__, r, fgn->name);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return FOREIGN_OK;
+}
+
+void check_foreign(void)
+{
+ struct foreign *fgn;
+ int j;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, j) {
+ fgn->check(fgn->context);
+ }
+
+ pthread_cleanup_pop(1);
+}
+
+/* Call this after get_path_layout */
+void foreign_path_layout(void)
+{
+ struct foreign *fgn;
+ int i;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, i) {
+ const struct _vector *vec;
+
+ fgn->lock(fgn->context);
+ pthread_cleanup_push(fgn->unlock, fgn->context);
+
+ vec = fgn->get_paths(fgn->context);
+ if (vec != NULL) {
+ _get_path_layout(vec, LAYOUT_RESET_NOT);
+ }
+ fgn->release_paths(fgn->context, vec);
+
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_cleanup_pop(1);
+}
+
+/* Call this after get_multipath_layout */
+void foreign_multipath_layout(void)
+{
+ struct foreign *fgn;
+ int i;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, i) {
+ const struct _vector *vec;
+
+ fgn->lock(fgn->context);
+ pthread_cleanup_push(fgn->unlock, fgn->context);
+
+ vec = fgn->get_multipaths(fgn->context);
+ if (vec != NULL) {
+ _get_multipath_layout(vec, LAYOUT_RESET_NOT);
+ }
+ fgn->release_multipaths(fgn->context, vec);
+
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_cleanup_pop(1);
+}
+
+int snprint_foreign_topology(char *buf, int len, int verbosity)
+{
+ struct foreign *fgn;
+ int i;
+ char *c = buf;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return 0;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, i) {
+ const struct _vector *vec;
+ const struct gen_multipath *gm;
+ int j;
+
+ fgn->lock(fgn->context);
+ pthread_cleanup_push(fgn->unlock, fgn->context);
+
+ vec = fgn->get_multipaths(fgn->context);
+ if (vec != NULL) {
+ vector_foreach_slot(vec, gm, j) {
+
+ c += _snprint_multipath_topology(gm, c,
+ buf + len - c,
+ verbosity);
+ if (c >= buf + len - 1)
+ break;
+ }
+ if (c >= buf + len - 1)
+ break;
+ }
+ fgn->release_multipaths(fgn->context, vec);
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_cleanup_pop(1);
+ return c - buf;
+}
+
+void print_foreign_topology(int verbosity)
+{
+ int buflen = MAX_LINE_LEN * MAX_LINES;
+ char *buf = NULL, *tmp = NULL;
+
+ buf = malloc(buflen);
+ buf[0] = '\0';
+ while (buf != NULL) {
+ char *c = buf;
+
+ c += snprint_foreign_topology(buf, buflen,
+ verbosity);
+ if (c < buf + buflen - 1)
+ break;
+
+ buflen *= 2;
+ tmp = buf;
+ buf = realloc(buf, buflen);
+ }
+
+ if (buf == NULL && tmp != NULL)
+ buf = tmp;
+
+ if (buf != NULL) {
+ printf("%s", buf);
+ free(buf);
+ }
+}
+
+int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
+{
+ struct foreign *fgn;
+ int i;
+ char *c = buf;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return 0;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, i) {
+ const struct _vector *vec;
+ const struct gen_path *gp;
+ int j;
+
+ fgn->lock(fgn->context);
+ pthread_cleanup_push(fgn->unlock, fgn->context);
+
+ vec = fgn->get_paths(fgn->context);
+ if (vec != NULL) {
+ vector_foreach_slot(vec, gp, j) {
+ c += _snprint_path(gp, c, buf + len - c,
+ style, pretty);
+ if (c >= buf + len - 1)
+ break;
+ }
+ if (c >= buf + len - 1)
+ break;
+ }
+ fgn->release_paths(fgn->context, vec);
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_cleanup_pop(1);
+ return c - buf;
+}
+
+int snprint_foreign_multipaths(char *buf, int len,
+ const char *style, int pretty)
+{
+ struct foreign *fgn;
+ int i;
+ char *c = buf;
+
+ rdlock_foreigns();
+ if (foreigns == NULL) {
+ unlock_foreigns(NULL);
+ return 0;
+ }
+ pthread_cleanup_push(unlock_foreigns, NULL);
+
+ vector_foreach_slot(foreigns, fgn, i) {
+ const struct _vector *vec;
+ const struct gen_multipath *gm;
+ int j;
+
+ fgn->lock(fgn->context);
+ pthread_cleanup_push(fgn->unlock, fgn->context);
+
+ vec = fgn->get_multipaths(fgn->context);
+ if (vec != NULL) {
+ vector_foreach_slot(vec, gm, j) {
+ c += _snprint_multipath(gm, c, buf + len - c,
+ style, pretty);
+ if (c >= buf + len - 1)
+ break;
+ }
+ if (c >= buf + len - 1)
+ break;
+ }
+ fgn->release_multipaths(fgn->context, vec);
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_cleanup_pop(1);
+ return c - buf;
+}
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+*/
+#ifndef _FOREIGN_H
+#define _FOREIGN_H
+#include <stdbool.h>
+#include <libudev.h>
+
+#define LIBMP_FOREIGN_API ((1 << 8) | 0)
+
+struct context;
+
+/* return codes of functions below returning "int" */
+enum foreign_retcode {
+ FOREIGN_OK,
+ FOREIGN_CLAIMED,
+ FOREIGN_IGNORED,
+ FOREIGN_UNCLAIMED,
+ FOREIGN_NODEV,
+ FOREIGN_ERR,
+ __LAST_FOREIGN_RETCODE,
+};
+
+/**
+ * Foreign multipath library API
+ * Foreign libraries must implement the following methods.
+ */
+struct foreign {
+ /**
+ * method: init(api, name)
+ * Initialize foreign library, and check API compatibility
+ * return pointer to opaque internal data strucure if successful,
+ * NULL otherwise.
+ *
+ * @param[in] api: API version
+ * @param[in] name: name to use for references to self in log messages,
+ * doesn't need to be strdup'd
+ * @returns context pointer to use in future method calls.
+ */
+ struct context* (*init)(unsigned int api, const char *name);
+
+ /**
+ * method: cleanup(context)
+ * Free data structures used by foreign library, including
+ * context itself.
+ *
+ * @param[in] context foreign library context. This shouldn't be
+ * referenced any more after calling cleanup().
+ */
+ void (*cleanup)(struct context *);
+
+ /**
+ * method: add(context, udev)
+ * This is called during path detection, and for udev ADD events.
+ *
+ * @param[in] context foreign library context
+ * @param[in] udev udev device to add
+ * @returns status code
+ * @retval FOREIGN_CLAIMED: device newly claimed
+ * @retval FOREIGN_OK: device already registered, no action taken
+ * @retval FOREIGN_IGNORED: device is ignored, no action taken
+ * @retval FOREIGN_ERR: an error occured (e.g. out-of-memory)
+ */
+ int (*add)(struct context *, struct udev_device *);
+
+ /**
+ * method: change
+ * This is called on udev CHANGE events.
+ *
+ * @param[in] context foreign library context
+ * @param[in] udev udev device that has generated the event
+ * @returns status code
+ * @retval FOREIGN_OK: event processed
+ * @retval FOREIGN_IGNORED: the device is ignored
+ * @retval FOREIGN_ERR: an error occured (e.g. out-of-memory)
+ *
+ * Note: theoretically it can happen that the status of a foreign device
+ * (claimed vs. not claimed) changes in a change event.
+ * Supporting this correctly would require big efforts. For now, we
+ * don't support it. "multipathd reconfigure" starts foreign device
+ * detection from scratch and should be able to handle this situation.
+ */
+ int (*change)(struct context *, struct udev_device *);
+
+ /**
+ * method: delete
+ * This is called on udev DELETE events.
+ *
+ * @param[in] context foreign library context
+ * @param[in] udev udev device that has generated the event and
+ * should be deleted
+ * @returns status code
+ * @retval FOREIGN_OK: processed correctly (device deleted)
+ * @retval FOREIGN_IGNORED: device wasn't registered internally
+ * @retval FOREIGN_ERR: error occured.
+ */
+ int (*delete)(struct context *, struct udev_device *);
+
+ /**
+ * method: delete_all
+ * This is called if multipathd reconfigures itself.
+ * Deletes all registered devices (maps and paths)
+ *
+ * @param[in] context foreign library context
+ * @returns status code
+ * @retval FOREIGN_OK: processed correctly
+ * @retval FOREIGN_IGNORED: nothing to delete
+ * @retval FOREIGN_ERR: error occured
+ */
+ int (*delete_all)(struct context*);
+
+ /**
+ * method: check
+ * This is called from multipathd's checker loop.
+ *
+ * Check status of managed devices, update internal status, and print
+ * log messages if appropriate.
+ * @param[in] context foreign library context
+ */
+ void (*check)(struct context *);
+
+ /**
+ * lock internal data stuctures.
+ * @param[in] ctx: foreign context
+ */
+ void (*lock)(struct context *ctx);
+
+ /**
+ * unlock internal data stuctures.
+ * @param[in] ctx: foreign context (void* in order to use the function
+ * as argument to pthread_cleanup_push())
+ */
+ void (*unlock)(void *ctx);
+
+ /**
+ * method: get_multipaths(context)
+ * Returned vector must be freed by calling release_multipaths().
+ * Lock must be held until release_multipaths() is called.
+ *
+ * @param[in] context foreign library context
+ * @returns a vector of "struct gen_multipath*" with the map devices
+ * belonging to this library (see generic.h).
+ */
+ const struct _vector* (*get_multipaths)(const struct context *);
+
+ /**
+ * method: release_multipaths(context, mpvec)
+ * release data structures obtained with get_multipaths (if any)
+ *
+ * @param[in] ctx the foreign context
+ * @param[in] mpvec the vector allocated with get_multipaths()
+ */
+ void (*release_multipaths)(const struct context *ctx,
+ const struct _vector* mpvec);
+
+ /**
+ * method: get_paths
+ * Returned vector must be freed by calling release_paths().
+ * Lock must be held until release_paths() is called.
+ *
+ * @param[in] context foreign library context
+ * @returns a vector of "struct gen_path*" with the path devices
+ * belonging to this library (see generic.h)
+ */
+ const struct _vector* (*get_paths)(const struct context *);
+
+ /**
+ * release data structures obtained with get_multipaths (if any)
+ *
+ * @param[in] ctx the foreign context
+ * @param[in] ppvec the vector allocated with get_paths()
+ */
+ void (*release_paths)(const struct context *ctx,
+ const struct _vector* ppvec);
+
+ void *handle;
+ struct context *context;
+ const char name[0];
+};
+
+/**
+ * init_foreign(dir)
+ * load and initialize foreign multipath libraries in dir (libforeign-*.so).
+ * @param dir: directory to search
+ * @returns: 0 on success, negative value on failure.
+ */
+int init_foreign(const char *multipath_dir);
+
+/**
+ * cleanup_foreign(dir)
+ * cleanup and free all data structures owned by foreign libraries
+ */
+void cleanup_foreign(void);
+
+/**
+ * add_foreign(udev)
+ * check if a device belongs to any foreign library.
+ * calls add() for all known foreign libs, in the order registered,
+ * until the first one returns FOREIGN_CLAIMED or FOREIGN_OK.
+ * @param udev: udev device to check
+ * @returns: status code
+ * @retval FOREIGN_CLAIMED: newly claimed by a foreign lib
+ * @retval FOREIGN_OK: already claimed by a foreign lib
+ * @retval FOREIGN_IGNORED: ignored by all foreign libs
+ * @retval FOREIGN_ERR: an error occured
+ */
+int add_foreign(struct udev_device *);
+
+/**
+ * change_foreign(udev)
+ * Notify foreign libraries of an udev CHANGE event
+ * @param udev: udev device to check
+ * @returns: status code (see change() method above).
+ */
+int change_foreign(struct udev_device *);
+
+/**
+ * delete_foreign(udev)
+ * @param udev: udev device being removed
+ * @returns: status code (see remove() above)
+ */
+int delete_foreign(struct udev_device *);
+
+/**
+ * delete_all_foreign()
+ * call delete_all() for all foreign libraries
+ * @returns: status code (see delete_all() above)
+ */
+int delete_all_foreign(void);
+
+/**
+ * check_foreign()
+ * call check() (see above) for all foreign libraries
+ */
+void check_foreign(void);
+
+/**
+ * foreign_path_layout()
+ * call this before printing paths, after get_path_layout(), to determine
+ * output field width.
+ */
+void foreign_path_layout(void);
+
+/**
+ * foreign_multipath_layout()
+ * call this before printing maps, after get_multipath_layout(), to determine
+ * output field width.
+ */
+void foreign_multipath_layout(void);
+
+/**
+ * snprint_foreign_topology(buf, len, verbosity);
+ * prints topology information from foreign libraries into buffer,
+ * '\0' - terminated.
+ * @param buf: output buffer
+ * @param len: size of output buffer
+ * @param verbosity: verbosity level
+ * @returns: number of printed characters excluding trailing '\0'.
+ */
+int snprint_foreign_topology(char *buf, int len, int verbosity);
+
+/**
+ * snprint_foreign_paths(buf, len, style, pad);
+ * prints formatted path information from foreign libraries into buffer,
+ * '\0' - terminated.
+ * @param buf: output buffer
+ * @param len: size of output buffer
+ * @param style: format string
+ * @param pad: whether to pad field width
+ * @returns: number of printed characters excluding trailing '\0'.
+ */
+int snprint_foreign_paths(char *buf, int len, const char *style, int pad);
+
+/**
+ * snprint_foreign_multipaths(buf, len, style, pad);
+ * prints formatted map information from foreign libraries into buffer,
+ * '\0' - terminated.
+ * @param buf: output buffer
+ * @param len: size of output buffer
+ * @param style: format string
+ * @param pad: whether to pad field width
+ * @returns: number of printed characters excluding trailing '\0'.
+ */
+int snprint_foreign_multipaths(char *buf, int len,
+ const char *style, int pretty);
+
+/**
+ * print_foreign_topology(v)
+ * print foreign topology to stdout
+ * @param verbosity: verbosity level
+ */
+void print_foreign_topology(int verbosity);
+
+/**
+ * is_claimed_by_foreign(ud)
+ * @param udev: udev device
+ * @returns: true iff device is (newly or already) claimed by a foreign lib
+ */
+static inline bool
+is_claimed_by_foreign(struct udev_device *ud)
+{
+ int rc = add_foreign(ud);
+
+ return (rc == FOREIGN_CLAIMED || rc == FOREIGN_OK);
+}
+
+#endif /* _FOREIGN_H */
--- /dev/null
+#
+# Copyright (C) 2003 Christophe Varoqui, <christophe.varoqui@opensvc.com>
+#
+include ../../Makefile.inc
+
+CFLAGS += $(LIB_CFLAGS) -I..
+
+# If you add or remove a checker also update multipath/multipath.conf.5
+LIBS= \
+ libforeign-nvme.so
+
+all: $(LIBS)
+
+libforeign-%.so: %.o
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^
+
+install:
+ $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(libdir)
+
+uninstall:
+ for file in $(LIBS); do $(RM) $(DESTDIR)$(libdir)/$$file; done
+
+clean: dep_clean
+ $(RM) core *.a *.o *.gz *.so
+
+OBJS := $(LIBS:libforeign-%.so=%.o)
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+*/
+
+#include <sys/sysmacros.h>
+#include <libudev.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <libudev.h>
+#include <pthread.h>
+#include <limits.h>
+#include <dirent.h>
+#include <errno.h>
+#include "vector.h"
+#include "generic.h"
+#include "foreign.h"
+#include "debug.h"
+#include "structs.h"
+#include "sysfs.h"
+
+static const char nvme_vendor[] = "NVMe";
+static const char N_A[] = "n/a";
+const char *THIS;
+
+struct nvme_map;
+struct nvme_path {
+ struct gen_path gen;
+ struct udev_device *udev;
+ struct udev_device *ctl;
+ struct nvme_map *map;
+ bool seen;
+};
+
+struct nvme_pathgroup {
+ struct gen_pathgroup gen;
+ vector pathvec;
+};
+
+struct nvme_map {
+ struct gen_multipath gen;
+ struct udev_device *udev;
+ struct udev_device *subsys;
+ dev_t devt;
+ /* Just one static pathgroup for NVMe for now */
+ struct nvme_pathgroup pg;
+ struct gen_pathgroup *gpg;
+ struct _vector pgvec;
+ vector pathvec;
+ int nr_live;
+};
+
+#define NAME_LEN 64 /* buffer length for temp attributes */
+#define const_gen_mp_to_nvme(g) ((const struct nvme_map*)(g))
+#define gen_mp_to_nvme(g) ((struct nvme_map*)(g))
+#define nvme_mp_to_gen(n) &((n)->gen)
+#define const_gen_pg_to_nvme(g) ((const struct nvme_pathgroup*)(g))
+#define gen_pg_to_nvme(g) ((struct nvme_pathgroup*)(g))
+#define nvme_pg_to_gen(n) &((n)->gen)
+#define const_gen_path_to_nvme(g) ((const struct nvme_path*)(g))
+#define gen_path_to_nvme(g) ((struct nvme_path*)(g))
+#define nvme_path_to_gen(n) &((n)->gen)
+
+static void cleanup_nvme_path(struct nvme_path *path)
+{
+ condlog(5, "%s: %p %p", __func__, path, path->udev);
+ if (path->udev)
+ udev_device_unref(path->udev);
+ /* ctl is implicitly referenced by udev, no need to unref */
+ free(path);
+}
+
+static void cleanup_nvme_map(struct nvme_map *map)
+{
+ if (map->pathvec) {
+ struct nvme_path *path;
+ int i;
+
+ vector_foreach_slot_backwards(map->pathvec, path, i) {
+ condlog(5, "%s: %d %p", __func__, i, path);
+ cleanup_nvme_path(path);
+ vector_del_slot(map->pathvec, i);
+ }
+ }
+ vector_free(map->pathvec);
+ if (map->udev)
+ udev_device_unref(map->udev);
+ /* subsys is implicitly referenced by udev, no need to unref */
+ free(map);
+}
+
+static const struct _vector*
+nvme_mp_get_pgs(const struct gen_multipath *gmp) {
+ const struct nvme_map *nvme = const_gen_mp_to_nvme(gmp);
+
+ /* This is all used under the lock, no need to copy */
+ return &nvme->pgvec;
+}
+
+static void
+nvme_mp_rel_pgs(const struct gen_multipath *gmp, const struct _vector *v)
+{
+ /* empty */
+}
+
+static void rstrip(char *str)
+{
+ int n;
+
+ for (n = strlen(str) - 1; n >= 0 && str[n] == ' '; n--);
+ str[n+1] = '\0';
+}
+
+static int snprint_nvme_map(const struct gen_multipath *gmp,
+ char *buff, int len, char wildcard)
+{
+ const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp);
+ char fld[NAME_LEN];
+ const char *val;
+
+ switch (wildcard) {
+ case 'd':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysname(nvm->udev));
+ case 'n':
+ return snprintf(buff, len, "%s:NQN:%s",
+ udev_device_get_sysname(nvm->subsys),
+ udev_device_get_sysattr_value(nvm->subsys,
+ "subsysnqn"));
+ case 'w':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(nvm->udev,
+ "wwid"));
+ case 'N':
+ return snprintf(buff, len, "%u", nvm->nr_live);
+ case 'S':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(nvm->udev,
+ "size"));
+ case 'v':
+ return snprintf(buff, len, "%s", nvme_vendor);
+ case 's':
+ case 'p':
+ snprintf(fld, sizeof(fld), "%s",
+ udev_device_get_sysattr_value(nvm->subsys,
+ "model"));
+ rstrip(fld);
+ if (wildcard == 'p')
+ return snprintf(buff, len, "%s", fld);
+ return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
+ udev_device_get_sysattr_value(nvm->subsys,
+ "firmware_rev"));
+ case 'e':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(nvm->subsys,
+ "firmware_rev"));
+ case 'r':
+ val = udev_device_get_sysattr_value(nvm->udev, "ro");
+ if (val[0] == 1)
+ return snprintf(buff, len, "%s", "ro");
+ else
+ return snprintf(buff, len, "%s", "rw");
+ case 'G':
+ return snprintf(buff, len, "%s", THIS);
+ default:
+ return snprintf(buff, len, N_A);
+ break;
+ }
+ return 0;
+}
+
+static const struct _vector*
+nvme_pg_get_paths(const struct gen_pathgroup *gpg) {
+ const struct nvme_pathgroup *gp = const_gen_pg_to_nvme(gpg);
+
+ /* This is all used under the lock, no need to copy */
+ return gp->pathvec;
+}
+
+static void
+nvme_pg_rel_paths(const struct gen_pathgroup *gpg, const struct _vector *v)
+{
+ /* empty */
+}
+
+static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
+ char *buff, int len, char wildcard)
+{
+ return snprintf(buff, len, N_A);
+}
+
+static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
+{
+ unsigned int nvmeid, ctlid, nsid;
+ int rc;
+ const char *sysname = udev_device_get_sysname(np->udev);
+
+ rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid);
+ if (rc != 3) {
+ condlog(1, "%s: failed to scan %s", __func__, sysname);
+ rc = snprintf(buf, len, "(ERR:%s)", sysname);
+ } else
+ rc = snprintf(buf, len, "%u:%u:%u", nvmeid, ctlid, nsid);
+ return (rc < len ? rc : len);
+}
+
+static int snprint_nvme_path(const struct gen_path *gp,
+ char *buff, int len, char wildcard)
+{
+ const struct nvme_path *np = const_gen_path_to_nvme(gp);
+ dev_t devt;
+ char fld[NAME_LEN];
+ struct udev_device *pci;
+
+ switch (wildcard) {
+ case 'w':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(np->udev,
+ "wwid"));
+ case 'd':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysname(np->udev));
+ case 'i':
+ return snprint_hcil(np, buff, len);
+ case 'D':
+ devt = udev_device_get_devnum(np->udev);
+ return snprintf(buff, len, "%u:%u", major(devt), minor(devt));
+ case 'o':
+ sysfs_attr_get_value(np->ctl, "state", fld, sizeof(fld));
+ return snprintf(buff, len, "%s", fld);
+ case 's':
+ snprintf(fld, sizeof(fld), "%s",
+ udev_device_get_sysattr_value(np->ctl,
+ "model"));
+ rstrip(fld);
+ return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
+ udev_device_get_sysattr_value(np->ctl,
+ "firmware_rev"));
+ case 'S':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(np->udev,
+ "size"));
+ case 'z':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysattr_value(np->ctl,
+ "serial"));
+ case 'm':
+ return snprintf(buff, len, "%s",
+ udev_device_get_sysname(np->map->udev));
+ case 'N':
+ case 'R':
+ return snprintf(buff, len, "%s:%s",
+ udev_device_get_sysattr_value(np->ctl,
+ "transport"),
+ udev_device_get_sysattr_value(np->ctl,
+ "address"));
+ case 'G':
+ return snprintf(buff, len, "[%s]", THIS);
+ case 'a':
+ pci = udev_device_get_parent_with_subsystem_devtype(np->ctl,
+ "pci",
+ NULL);
+ if (pci != NULL)
+ return snprintf(buff, len, "PCI:%s",
+ udev_device_get_sysname(pci));
+ /* fall through */
+ default:
+ return snprintf(buff, len, "%s", N_A);
+ break;
+ }
+ return 0;
+}
+
+static const struct gen_multipath_ops nvme_map_ops = {
+ .get_pathgroups = nvme_mp_get_pgs,
+ .rel_pathgroups = nvme_mp_rel_pgs,
+ .style = generic_style,
+ .snprint = snprint_nvme_map,
+};
+
+static const struct gen_pathgroup_ops nvme_pg_ops __attribute__((unused)) = {
+ .get_paths = nvme_pg_get_paths,
+ .rel_paths = nvme_pg_rel_paths,
+ .snprint = snprint_nvme_pg,
+};
+
+static const struct gen_path_ops nvme_path_ops __attribute__((unused)) = {
+ .snprint = snprint_nvme_path,
+};
+
+struct context {
+ pthread_mutex_t mutex;
+ vector mpvec;
+ struct udev *udev;
+};
+
+void lock(struct context *ctx)
+{
+ pthread_mutex_lock(&ctx->mutex);
+}
+
+void unlock(void *arg)
+{
+ struct context *ctx = arg;
+
+ pthread_mutex_unlock(&ctx->mutex);
+}
+
+static int _delete_all(struct context *ctx)
+{
+ struct nvme_map *nm;
+ int n = VECTOR_SIZE(ctx->mpvec), i;
+
+ if (n == 0)
+ return FOREIGN_IGNORED;
+
+ vector_foreach_slot_backwards(ctx->mpvec, nm, i) {
+ vector_del_slot(ctx->mpvec, i);
+ cleanup_nvme_map(nm);
+ }
+ return FOREIGN_OK;
+}
+
+int delete_all(struct context *ctx)
+{
+ int rc;
+
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+
+ lock(ctx);
+ pthread_cleanup_push(unlock, ctx);
+ rc = _delete_all(ctx);
+ pthread_cleanup_pop(1);
+
+ return rc;
+}
+
+void cleanup(struct context *ctx)
+{
+ (void)delete_all(ctx);
+
+ lock(ctx);
+ /*
+ * Locking is not strictly necessary here, locking in foreign.c
+ * makes sure that no other code is called with this ctx any more.
+ * But this should make static checkers feel better.
+ */
+ pthread_cleanup_push(unlock, ctx);
+ if (ctx->udev)
+ udev_unref(ctx->udev);
+ if (ctx->mpvec)
+ vector_free(ctx->mpvec);
+ ctx->mpvec = NULL;
+ ctx->udev = NULL;
+ pthread_cleanup_pop(1);
+ pthread_mutex_destroy(&ctx->mutex);
+
+ free(ctx);
+}
+
+struct context *init(unsigned int api, const char *name)
+{
+ struct context *ctx;
+
+ if (api > LIBMP_FOREIGN_API) {
+ condlog(0, "%s: api version mismatch: %08x > %08x\n",
+ __func__, api, LIBMP_FOREIGN_API);
+ return NULL;
+ }
+
+ if ((ctx = calloc(1, sizeof(*ctx)))== NULL)
+ return NULL;
+
+ pthread_mutex_init(&ctx->mutex, NULL);
+
+ ctx->udev = udev_new();
+ if (ctx->udev == NULL)
+ goto err;
+
+ ctx->mpvec = vector_alloc();
+ if (ctx->mpvec == NULL)
+ goto err;
+
+ THIS = name;
+ return ctx;
+err:
+ cleanup(ctx);
+ return NULL;
+}
+
+static struct nvme_map *_find_nvme_map_by_devt(const struct context *ctx,
+ dev_t devt)
+{
+ struct nvme_map *nm;
+ int i;
+
+ if (ctx->mpvec == NULL)
+ return NULL;
+
+ vector_foreach_slot(ctx->mpvec, nm, i) {
+ if (nm->devt == devt)
+ return nm;
+ }
+
+ return NULL;
+}
+
+static struct nvme_path *
+_find_path_by_syspath(struct nvme_map *map, const char *syspath)
+{
+ struct nvme_path *path;
+ char real[PATH_MAX];
+ const char *ppath;
+ int i;
+
+ ppath = realpath(syspath, real);
+ if (ppath == NULL) {
+ condlog(1, "%s: %s: error in realpath", __func__, THIS);
+ ppath = syspath;
+ }
+
+ vector_foreach_slot(map->pathvec, path, i) {
+ if (!strcmp(ppath,
+ udev_device_get_syspath(path->udev)))
+ return path;
+ }
+ condlog(4, "%s: %s: %s not found", __func__, THIS, ppath);
+ return NULL;
+}
+
+static int no_dotfiles(const struct dirent *di)
+{
+ return di->d_name[0] != '.';
+}
+
+static void _find_slaves(struct context *ctx, struct nvme_map *map)
+{
+ char pathbuf[PATH_MAX];
+ struct dirent **di = NULL;
+ struct nvme_path *path;
+ int r, i;
+
+ if (map == NULL || map->udev == NULL)
+ return;
+
+ vector_foreach_slot(map->pathvec, path, i)
+ path->seen = false;
+
+ snprintf(pathbuf, sizeof(pathbuf),
+ "%s/slaves",
+ udev_device_get_syspath(map->udev));
+
+ r = scandir(pathbuf, &di, no_dotfiles, alphasort);
+
+ if (r == 0) {
+ condlog(3, "%s: %s: no paths for %s", __func__, THIS,
+ udev_device_get_sysname(map->udev));
+ return;
+ } else if (r < 0) {
+ condlog(1, "%s: %s: error %d scanning paths of %s", __func__,
+ THIS, errno, udev_device_get_sysname(map->udev));
+ return;
+ }
+
+ pthread_cleanup_push(free, di);
+ for (i = 0; i < r; i++) {
+ char *fn = di[i]->d_name;
+ struct udev_device *udev;
+
+ if (snprintf(pathbuf, sizeof(pathbuf), "%s/slaves/%s",
+ udev_device_get_syspath(map->udev), fn)
+ >= sizeof(pathbuf))
+ continue;
+
+ path = _find_path_by_syspath(map, pathbuf);
+ if (path != NULL) {
+ path->seen = true;
+ condlog(4, "%s: %s already known",
+ __func__, fn);
+ continue;
+ }
+
+ udev = udev_device_new_from_syspath(ctx->udev, pathbuf);
+ if (udev == NULL) {
+ condlog(1, "%s: %s: failed to get udev device for %s",
+ __func__, THIS, fn);
+ continue;
+ }
+
+ path = calloc(1, sizeof(*path));
+ if (path == NULL)
+ continue;
+
+ path->gen.ops = &nvme_path_ops;
+ path->udev = udev;
+ path->seen = true;
+ path->map = map;
+ path->ctl = udev_device_get_parent_with_subsystem_devtype
+ (udev, "nvme", NULL);
+ if (path->ctl == NULL) {
+ condlog(1, "%s: %s: failed to get controller for %s",
+ __func__, THIS, fn);
+ cleanup_nvme_path(path);
+ continue;
+ }
+
+ if (vector_alloc_slot(map->pathvec) == NULL) {
+ cleanup_nvme_path(path);
+ continue;
+ }
+ condlog(3, "%s: %s: new path %s added to %s",
+ __func__, THIS, udev_device_get_sysname(udev),
+ udev_device_get_sysname(map->udev));
+ vector_set_slot(map->pathvec, path);
+ }
+ pthread_cleanup_pop(1);
+
+ map->nr_live = 0;
+ vector_foreach_slot_backwards(map->pathvec, path, i) {
+ if (!path->seen) {
+ condlog(1, "path %d not found in %s any more",
+ i, udev_device_get_sysname(map->udev));
+ vector_del_slot(map->pathvec, i);
+ cleanup_nvme_path(path);
+ } else {
+ static const char live_state[] = "live";
+ char state[16];
+
+ if ((sysfs_attr_get_value(path->ctl, "state", state,
+ sizeof(state)) > 0) &&
+ !strncmp(state, live_state, sizeof(live_state) - 1))
+ map->nr_live++;
+ }
+ }
+ condlog(3, "%s: %s: map %s has %d/%d live paths", __func__, THIS,
+ udev_device_get_sysname(map->udev), map->nr_live,
+ VECTOR_SIZE(map->pathvec));
+}
+
+static int _add_map(struct context *ctx, struct udev_device *ud,
+ struct udev_device *subsys)
+{
+ dev_t devt = udev_device_get_devnum(ud);
+ struct nvme_map *map;
+
+ if (_find_nvme_map_by_devt(ctx, devt) != NULL)
+ return FOREIGN_OK;
+
+ map = calloc(1, sizeof(*map));
+ if (map == NULL)
+ return FOREIGN_ERR;
+
+ map->devt = devt;
+ map->udev = udev_device_ref(ud);
+ /*
+ * subsys is implicitly referenced by map->udev,
+ * no need to take a reference here.
+ */
+ map->subsys = subsys;
+ map->gen.ops = &nvme_map_ops;
+
+ map->pathvec = vector_alloc();
+ if (map->pathvec == NULL) {
+ cleanup_nvme_map(map);
+ return FOREIGN_ERR;
+ }
+
+ map->pg.gen.ops = &nvme_pg_ops;
+ map->pg.pathvec = map->pathvec;
+ map->gpg = nvme_pg_to_gen(&map->pg);
+
+ map->pgvec.allocated = 1;
+ map->pgvec.slot = (void**)&map->gpg;
+
+ if (vector_alloc_slot(ctx->mpvec) == NULL) {
+ cleanup_nvme_map(map);
+ return FOREIGN_ERR;
+ }
+ vector_set_slot(ctx->mpvec, map);
+ _find_slaves(ctx, map);
+
+ return FOREIGN_CLAIMED;
+}
+
+int add(struct context *ctx, struct udev_device *ud)
+{
+ struct udev_device *subsys;
+ int rc;
+
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+
+ if (ud == NULL)
+ return FOREIGN_ERR;
+ if (strcmp("disk", udev_device_get_devtype(ud)))
+ return FOREIGN_IGNORED;
+
+ subsys = udev_device_get_parent_with_subsystem_devtype(ud,
+ "nvme-subsystem",
+ NULL);
+ if (subsys == NULL)
+ return FOREIGN_IGNORED;
+
+ lock(ctx);
+ pthread_cleanup_push(unlock, ctx);
+ rc = _add_map(ctx, ud, subsys);
+ pthread_cleanup_pop(1);
+
+ if (rc == FOREIGN_CLAIMED)
+ condlog(3, "%s: %s: added map %s", __func__, THIS,
+ udev_device_get_sysname(ud));
+ else if (rc != FOREIGN_OK)
+ condlog(1, "%s: %s: retcode %d adding %s",
+ __func__, THIS, rc, udev_device_get_sysname(ud));
+
+ return rc;
+}
+
+int change(struct context *ctx, struct udev_device *ud)
+{
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+ return FOREIGN_IGNORED;
+}
+
+static int _delete_map(struct context *ctx, struct udev_device *ud)
+{
+ int k;
+ struct nvme_map *map;
+ dev_t devt = udev_device_get_devnum(ud);
+
+ map = _find_nvme_map_by_devt(ctx, devt);
+ if (map ==NULL)
+ return FOREIGN_IGNORED;
+
+ k = find_slot(ctx->mpvec, map);
+ if (k == -1)
+ return FOREIGN_ERR;
+ else
+ vector_del_slot(ctx->mpvec, k);
+
+ cleanup_nvme_map(map);
+
+ return FOREIGN_OK;
+}
+
+int delete(struct context *ctx, struct udev_device *ud)
+{
+ int rc;
+
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+
+ if (ud == NULL)
+ return FOREIGN_ERR;
+
+ lock(ctx);
+ pthread_cleanup_push(unlock, ctx);
+ rc = _delete_map(ctx, ud);
+ pthread_cleanup_pop(1);
+
+ if (rc == FOREIGN_OK)
+ condlog(3, "%s: %s: map %s deleted", __func__, THIS,
+ udev_device_get_sysname(ud));
+ else if (rc != FOREIGN_IGNORED)
+ condlog(1, "%s: %s: retcode %d deleting map %s", __func__,
+ THIS, rc, udev_device_get_sysname(ud));
+
+ return rc;
+}
+
+void _check(struct context *ctx)
+{
+ struct gen_multipath *gm;
+ int i;
+
+ vector_foreach_slot(ctx->mpvec, gm, i) {
+ struct nvme_map *map = gen_mp_to_nvme(gm);
+
+ _find_slaves(ctx, map);
+ }
+}
+
+void check(struct context *ctx)
+{
+ condlog(4, "%s called for \"%s\"", __func__, THIS);
+ lock(ctx);
+ pthread_cleanup_push(unlock, ctx);
+ _check(ctx);
+ pthread_cleanup_pop(1);
+ return;
+}
+
+/*
+ * It's safe to pass our internal pointer, this is only used under the lock.
+ */
+const struct _vector *get_multipaths(const struct context *ctx)
+{
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+ return ctx->mpvec;
+}
+
+void release_multipaths(const struct context *ctx, const struct _vector *mpvec)
+{
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+ /* NOP */
+}
+
+/*
+ * It's safe to pass our internal pointer, this is only used under the lock.
+ */
+const struct _vector * get_paths(const struct context *ctx)
+{
+ vector paths = NULL;
+ const struct gen_multipath *gm;
+ int i;
+
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+ vector_foreach_slot(ctx->mpvec, gm, i) {
+ const struct nvme_map *nm = const_gen_mp_to_nvme(gm);
+ paths = vector_convert(paths, nm->pathvec,
+ struct gen_path, identity);
+ }
+ return paths;
+}
+
+void release_paths(const struct context *ctx, const struct _vector *mpvec)
+{
+ condlog(5, "%s called for \"%s\"", __func__, THIS);
+ vector_free_const(mpvec);
+}
+
+/* compile-time check whether all methods are present and correctly typed */
+#define _METHOD_INIT(x) .x = x
+static struct foreign __methods __attribute__((unused)) = {
+ _METHOD_INIT(init),
+ _METHOD_INIT(cleanup),
+ _METHOD_INIT(change),
+ _METHOD_INIT(delete),
+ _METHOD_INIT(delete_all),
+ _METHOD_INIT(check),
+ _METHOD_INIT(lock),
+ _METHOD_INIT(unlock),
+ _METHOD_INIT(get_multipaths),
+ _METHOD_INIT(release_multipaths),
+ _METHOD_INIT(get_paths),
+ _METHOD_INIT(release_paths),
+};
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+ */
+
+
+#include <string.h>
+#include "generic.h"
+#include "structs.h"
+
+int generic_style(const struct gen_multipath* gm,
+ char *buf, int len, int verbosity)
+{
+ char alias_buf[WWID_SIZE];
+ char wwid_buf[WWID_SIZE];
+ int n = 0;
+
+ gm->ops->snprint(gm, alias_buf, sizeof(alias_buf), 'n');
+ gm->ops->snprint(gm, wwid_buf, sizeof(wwid_buf), 'w');
+
+ n += snprintf(buf, len, "%%n %s[%%G]:%%d %%s",
+ strcmp(alias_buf, wwid_buf) ? "(%w) " : "");
+
+ return (n < len ? n : len - 1);
+}
--- /dev/null
+/*
+ Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
+
+ 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 2
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.
+ */
+#ifndef _GENERIC_H
+#define _GENERIC_H
+#include "vector.h"
+
+struct gen_multipath;
+struct gen_pathgroup;
+struct gen_path;
+
+/**
+ * Methods implemented for gen_multipath "objects"
+ */
+struct gen_multipath_ops {
+ /**
+ * method: get_pathgroups(gmp)
+ * caller is responsible to returned data using rel_pathgroups()
+ * caller is also responsible to lock the gmp (directly or indirectly)
+ * while working with the return value.
+ * @param gmp: generic multipath object to act on
+ * @returns a vector of const struct gen_pathgroup*
+ */
+ const struct _vector* (*get_pathgroups)(const struct gen_multipath*);
+ /**
+ * method: rel_pathgroups(gmp, v)
+ * free data allocated by get_pathgroups(), if any
+ * @param gmp: generic multipath object to act on
+ * @param v the value returned by get_pathgroups()
+ */
+ void (*rel_pathgroups)(const struct gen_multipath*,
+ const struct _vector*);
+ /**
+ * method: snprint(gmp, buf, len, wildcard)
+ * prints the property of the multipath map matching
+ * the passed-in wildcard character into "buf",
+ * 0-terminated, no more than "len" characters including trailing '\0'.
+ *
+ * @param gmp: generic multipath object to act on
+ * @param buf: output buffer
+ * @param buflen: buffer size
+ * @param wildcard: the multipath wildcard (see print.c)
+ * @returns the number of characters printed (without trailing '\0').
+ */
+ int (*snprint)(const struct gen_multipath*,
+ char *buf, int len, char wildcard);
+ /**
+ * method: style(gmp, buf, len, verbosity)
+ * returns the format string to be used for the multipath object,
+ * defined with the wildcards as defined in print.c
+ * generic_style() should work well in most cases.
+ * @param gmp: generic multipath object to act on
+ * @param buf: output buffer
+ * @param buflen: buffer size
+ * @param verbosity: verbosity level
+ * @returns number of format chars printed
+ */
+ int (*style)(const struct gen_multipath*,
+ char *buf, int len, int verbosity);
+};
+
+/**
+ * Methods implemented for gen_pathgroup "objects"
+ */
+struct gen_pathgroup_ops {
+ /**
+ * method: get_paths(gpg)
+ * caller is responsible to returned data using rel_paths()
+ * @param gpg: generic pathgroup object to act on
+ * @returns a vector of const struct gen_path*
+ */
+ const struct _vector* (*get_paths)(const struct gen_pathgroup*);
+ /**
+ * method: rel_paths(gpg, v)
+ * free data allocated by get_paths(), if any
+ * @param gmp: generic pathgroup object to act on
+ * @param v the value returned by get_paths()
+ */
+ void (*rel_paths)(const struct gen_pathgroup*, const struct _vector*);
+ /**
+ * Method snprint()
+ * see gen_multipath_ops->snprint() above
+ */
+ int (*snprint)(const struct gen_pathgroup*,
+ char *buf, int len, char wildcard);
+};
+
+struct gen_path_ops {
+ /**
+ * Method snprint()
+ * see gen_multipath_ops->snprint() above
+ */
+ int (*snprint)(const struct gen_path*,
+ char *buf, int len, char wildcard);
+};
+
+struct gen_multipath {
+ const struct gen_multipath_ops *ops;
+};
+
+struct gen_pathgroup {
+ const struct gen_pathgroup_ops *ops;
+};
+
+struct gen_path {
+ const struct gen_path_ops *ops;
+};
+
+/**
+ * Helper functions for setting up the various generic_X_ops
+ */
+
+/**
+ * generic_style()
+ * A simple style() method (see above) that should fit most
+ * foreign libraries.
+ */
+int generic_style(const struct gen_multipath*,
+ char *buf, int len, int verbosity);
+
+#endif /* _GENERIC_H */
.delay_wait_checks = DELAY_CHECKS_OFF,
.skip_kpartx = SKIP_KPARTX_OFF,
.max_sectors_kb = MAX_SECTORS_KB_UNDEF,
+ .ghost_delay = GHOST_DELAY_OFF
},
#endif
static struct hwentry default_hw[] = {
+ /*
+ * Generic NVMe devices
+ *
+ * Due to the parsing logic in find_hwe(), generic entries
+ * have to be put on top of this list, and more specific ones
+ * below.
+ */
+ {
+ .vendor = "NVME",
+ .product = ".*",
+ .uid_attribute = "ID_WWN",
+ .checker_name = NONE,
+ .retain_hwhandler = RETAIN_HWHANDLER_OFF,
+ },
/*
* Apple
*
/*
* SANtricity(RDAC) family
*
- * Maintainer : Sean Stewart
- * Mail : sean.stewart@netapp.com
+ * Maintainer : NetApp RDAC team
+ * Mail : ng-eseries-upstream-maintainers@netapp.com
*/
.vendor = "(NETAPP|LSI|ENGENIO)",
.product = "INF-01-00",
.no_path_retry = 24,
},
/*
+ * NetApp NVMe-FC namespace devices: MULTIBUS, queueing preferred
+ *
+ * The table is searched backwards, so place this after generic NVMe
+ */
+ {
+ .vendor = "NVME",
+ .product = "^NetApp ONTAP Controller",
+ .uid_attribute = "ID_WWN",
+ .checker_name = NONE,
+ .pgpolicy = MULTIBUS,
+ .no_path_retry = NO_PATH_RETRY_QUEUE,
+ .retain_hwhandler = RETAIN_HWHANDLER_OFF,
+ },
+ /*
* Nexenta
*
* Maintainer : Yacine Kheddache
.no_path_retry = 30,
},
/*
- * Generic NVMe devices
- */
- {
- .vendor = "NVME",
- .product = ".*",
- .uid_attribute = "ID_WWN",
- .checker_name = NONE,
- .retain_hwhandler = RETAIN_HWHANDLER_OFF,
- },
- /*
* Dot Hill Systems - Seagate Technology
*/
{
.no_path_retry = 30,
},
/*
+ * INSPUR
+ */
+ {
+ /* AS5300/AS5500 G2 */
+ .vendor = "INSPUR",
+ .product = "MCS",
+ .pgpolicy = GROUP_BY_PRIO,
+ .pgfailback = -FAILBACK_IMMEDIATE,
+ .prio_name = PRIO_ALUA,
+ },
+ /*
* EOL
*/
{
#include <libaio.h>
#include <errno.h>
#include <sys/mman.h>
+#include <sys/select.h>
#include "vector.h"
#include "memory.h"
pthread_t io_err_stat_thr;
pthread_attr_t io_err_stat_attr;
+static pthread_mutex_t io_err_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t io_err_thread_cond = PTHREAD_COND_INITIALIZER;
+static int io_err_thread_running = 0;
+
static struct io_err_stat_pathvec *paths;
struct vectors *vecs;
io_context_t ioctx;
struct timespec curr_time;
int res;
+ if (uatomic_read(&io_err_thread_running) == 0)
+ return 1;
+
if (path->io_err_disable_reinstate) {
io_err_stat_log(3, "%s: reinstate is already disabled",
path->dev);
struct timespec curr_time;
int r;
- if (pp->io_err_disable_reinstate == 0)
- return 1;
- if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0)
- return 1;
- if (pp->io_err_pathfail_cnt != PATH_IO_ERR_IN_POLLING_RECHECK)
- return 1;
+ if (uatomic_read(&io_err_thread_running) == 0)
+ return 0;
if (pp->mpp->nr_active <= 0) {
io_err_stat_log(2, "%s: recover path early", pp->dev);
goto recover;
}
- if ((curr_time.tv_sec - pp->io_err_dis_reinstate_time) >
+ if (pp->io_err_pathfail_cnt != PATH_IO_ERR_IN_POLLING_RECHECK)
+ return 1;
+ if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0 ||
+ (curr_time.tv_sec - pp->io_err_dis_reinstate_time) >
pp->mpp->marginal_path_err_recheck_gap_time) {
io_err_stat_log(4, "%s: reschedule checking after %d seconds",
pp->dev,
pthread_mutex_unlock(&paths->mutex);
}
+static void cleanup_unlock(void *arg)
+{
+ pthread_mutex_unlock((pthread_mutex_t*) arg);
+}
+
+static void cleanup_exited(void *arg)
+{
+ uatomic_set(&io_err_thread_running, 0);
+}
+
static void *io_err_stat_loop(void *data)
{
+ sigset_t set;
+
vecs = (struct vectors *)data;
pthread_cleanup_push(rcu_unregister, NULL);
rcu_register_thread();
+ pthread_cleanup_push(cleanup_exited, NULL);
+
+ sigfillset(&set);
+ sigdelset(&set, SIGUSR2);
+
mlockall(MCL_CURRENT | MCL_FUTURE);
+
+ pthread_mutex_lock(&io_err_thread_lock);
+ uatomic_set(&io_err_thread_running, 1);
+ pthread_cond_broadcast(&io_err_thread_cond);
+ pthread_mutex_unlock(&io_err_thread_lock);
+
while (1) {
+ struct timespec ts;
+
service_paths();
- usleep(100000);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100 * 1000 * 1000;
+ /*
+ * pselect() with no fds, a timeout, and a sigmask:
+ * sleep for 100ms and react on SIGUSR2.
+ */
+ pselect(1, NULL, NULL, NULL, &ts, &set);
}
pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
return NULL;
}
int start_io_err_stat_thread(void *data)
{
+ int ret;
+
+ if (uatomic_read(&io_err_thread_running) == 1)
+ return 0;
+
if (io_setup(CONCUR_NR_EVENT, &ioctx) != 0) {
io_err_stat_log(4, "io_setup failed");
return 1;
if (!paths)
goto destroy_ctx;
- if (pthread_create(&io_err_stat_thr, &io_err_stat_attr,
- io_err_stat_loop, data)) {
+ pthread_mutex_lock(&io_err_thread_lock);
+ pthread_cleanup_push(cleanup_unlock, &io_err_thread_lock);
+
+ ret = pthread_create(&io_err_stat_thr, &io_err_stat_attr,
+ io_err_stat_loop, data);
+
+ while (!ret && !uatomic_read(&io_err_thread_running) &&
+ pthread_cond_wait(&io_err_thread_cond,
+ &io_err_thread_lock) == 0);
+
+ pthread_cleanup_pop(1);
+
+ if (ret) {
io_err_stat_log(0, "cannot create io_error statistic thread");
goto out_free;
}
- io_err_stat_log(3, "thread started");
+
+ io_err_stat_log(2, "io_error statistic thread started");
return 0;
out_free:
void stop_io_err_stat_thread(void)
{
- pthread_cancel(io_err_stat_thr);
- pthread_kill(io_err_stat_thr, SIGUSR2);
+ if (uatomic_read(&io_err_thread_running) == 1)
+ pthread_cancel(io_err_stat_thr);
+
+ pthread_join(io_err_stat_thr, NULL);
free_io_err_pathvec(paths);
io_destroy(ioctx);
}
* @member: the name of the member within the struct.
*
*/
-#define container_of(ptr, type, member) ({ \
+#define container_of_const(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (const type *)( (const char *)__mptr - offsetof(type,member) );})
+
+#define container_of(ptr, type, member) ({ \
+ typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/*
(__FILE__), (char *)(__FUNCTION__), (__LINE__)) )
#define STRDUP(n) ( dbg_strdup((n), \
(__FILE__), (char *)(__FUNCTION__), (__LINE__)) )
+#define FREE_CONST(p) do { FREE((void*)(unsigned long)p); } while(0)
/* Memory debug prototypes defs */
extern void *dbg_malloc(unsigned long, char *, char *, int);
#define MALLOC(n) (calloc(1,(n)))
#define FREE(p) do { free(p); p = NULL; } while(0)
+/*
+ * Double cast to avoid warnings with -Wcast-qual
+ * use this for valid free() operations on const pointers
+ */
+#define FREE_CONST(p) do { free((void*)(unsigned long)p); p = NULL; } while(0)
#define REALLOC(p,n) (realloc((p),(n)))
#define STRDUP(n) (strdup(n))
int
keyword_alloc(vector keywords, char *string,
int (*handler) (struct config *, vector),
- int (*print) (struct config *, char *, int, void *), int unique)
+ int (*print) (struct config *, char *, int, const void*),
+ int unique)
{
struct keyword *keyword;
int
_install_keyword(vector keywords, char *string,
int (*handler) (struct config *, vector),
- int (*print) (struct config *, char *, int, void *), int unique)
+ int (*print) (struct config *, char *, int, const void*),
+ int unique)
{
int i = 0;
struct keyword *keyword;
}
int
-snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw, void *data)
+snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
+ const void *data)
{
int r;
int fwd = 0;
struct keyword {
char *string;
int (*handler) (struct config *, vector);
- int (*print) (struct config *, char *, int, void *);
+ int (*print) (struct config *, char *, int, const void *);
vector sub;
int unique;
};
/* Prototypes */
extern int keyword_alloc(vector keywords, char *string,
int (*handler) (struct config *, vector),
- int (*print) (struct config *, char *, int, void *), int unique);
+ int (*print) (struct config *, char *, int,
+ const void *),
+ int unique);
#define install_keyword_root(str, h) keyword_alloc(keywords, str, h, NULL, 1)
extern void install_sublevel(void);
extern void install_sublevel_end(void);
extern int _install_keyword(vector keywords, char *string,
int (*handler) (struct config *, vector),
- int (*print) (struct config *, char *, int, void *), int unique);
+ int (*print) (struct config *, char *, int,
+ const void *),
+ int unique);
#define install_keyword(str, vec, pri) _install_keyword(keywords, str, vec, pri, 1)
#define install_keyword_multi(str, vec, pri) _install_keyword(keywords, str, vec, pri, 0)
extern void dump_keywords(vector keydump, int level);
extern int process_file(struct config *conf, char *conf_file);
extern struct keyword * find_keyword(vector keywords, vector v, char * name);
int snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
- void *data);
+ const void *data);
#endif
if (!pgp)
goto out1;
- if (store_pathgroup(mp->pg, pgp))
+ if (add_pathgroup(mp, pgp))
goto out2;
/* feed the first path */
if (!pgp)
goto out1;
- if (store_pathgroup(mp->pg, pgp))
+ if (add_pathgroup(mp, pgp))
goto out2;
/* feed the first path */
if (!pgp)
goto out;
- if (store_pathgroup(mp->pg, pgp))
+ if (add_pathgroup(mp, pgp))
goto out1;
if (store_path(pgp->paths, pp))
vector_free(pgp->paths);
- if (store_pathgroup(mp->pg, pgp))
+ if (add_pathgroup(mp, pgp))
goto out1;
pgp->paths = mp->paths;
if (i < VECTOR_SIZE(mp->pg)) {
if (!vector_insert_slot(mp->pg, i, pgp))
goto out2;
+ pgp->mpp = mp;
} else {
- if (store_pathgroup(mp->pg, pgp))
+ if (add_pathgroup(mp, pgp))
goto out2;
}
#include "devmapper.h"
#include "uevent.h"
#include "debug.h"
+#include "discovery.h"
-#define MAX(x,y) (x > y) ? x : y
+#define MAX(x,y) (((x) > (y)) ? (x) : (y))
+#define MIN(x,y) (((x) > (y)) ? (y) : (x))
#define TAIL (line + len - 1 - c)
#define NOPAD s = c
#define PAD(x) \
* multipath info printing functions
*/
static int
-snprint_name (char * buff, size_t len, struct multipath * mpp)
+snprint_name (char * buff, size_t len, const struct multipath * mpp)
{
if (mpp->alias)
return snprintf(buff, len, "%s", mpp->alias);
}
static int
-snprint_sysfs (char * buff, size_t len, struct multipath * mpp)
+snprint_sysfs (char * buff, size_t len, const struct multipath * mpp)
{
if (mpp->dmi)
return snprintf(buff, len, "dm-%i", mpp->dmi->minor);
}
static int
-snprint_ro (char * buff, size_t len, struct multipath * mpp)
+snprint_ro (char * buff, size_t len, const struct multipath * mpp)
{
if (!mpp->dmi)
return snprintf(buff, len, "undef");
}
static int
-snprint_failback (char * buff, size_t len, struct multipath * mpp)
+snprint_failback (char * buff, size_t len, const struct multipath * mpp)
{
if (mpp->pgfailback == -FAILBACK_IMMEDIATE)
return snprintf(buff, len, "immediate");
}
static int
-snprint_queueing (char * buff, size_t len, struct multipath * mpp)
+snprint_queueing (char * buff, size_t len, const struct multipath * mpp)
{
if (mpp->no_path_retry == NO_PATH_RETRY_FAIL)
return snprintf(buff, len, "off");
}
static int
-snprint_nb_paths (char * buff, size_t len, struct multipath * mpp)
+snprint_nb_paths (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_int(buff, len, mpp->nr_active);
}
static int
-snprint_dm_map_state (char * buff, size_t len, struct multipath * mpp)
+snprint_dm_map_state (char * buff, size_t len, const struct multipath * mpp)
{
if (mpp->dmi && mpp->dmi->suspended)
return snprintf(buff, len, "suspend");
}
static int
-snprint_multipath_size (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_size (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_size(buff, len, mpp->size);
}
static int
-snprint_features (char * buff, size_t len, struct multipath * mpp)
+snprint_features (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_str(buff, len, mpp->features);
}
static int
-snprint_hwhandler (char * buff, size_t len, struct multipath * mpp)
+snprint_hwhandler (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_str(buff, len, mpp->hwhandler);
}
static int
-snprint_path_faults (char * buff, size_t len, struct multipath * mpp)
+snprint_path_faults (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_path_failures);
}
static int
-snprint_switch_grp (char * buff, size_t len, struct multipath * mpp)
+snprint_switch_grp (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_switchgroup);
}
static int
-snprint_map_loads (char * buff, size_t len, struct multipath * mpp)
+snprint_map_loads (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_map_loads);
}
static int
-snprint_total_q_time (char * buff, size_t len, struct multipath * mpp)
+snprint_total_q_time (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_total_queueing_time);
}
static int
-snprint_q_timeouts (char * buff, size_t len, struct multipath * mpp)
+snprint_q_timeouts (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_queueing_timeouts);
}
static int
-snprint_map_failures (char * buff, size_t len, struct multipath * mpp)
+snprint_map_failures (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_uint(buff, len, mpp->stat_map_failures);
}
static int
-snprint_multipath_uuid (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_uuid (char * buff, size_t len, const struct multipath * mpp)
{
return snprint_str(buff, len, mpp->wwid);
}
static int
-snprint_multipath_vpr (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_vpr (char * buff, size_t len, const struct multipath * mpp)
{
struct pathgroup * pgp;
struct path * pp;
static int
-snprint_multipath_vend (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_vend (char * buff, size_t len, const struct multipath * mpp)
{
struct pathgroup * pgp;
struct path * pp;
}
static int
-snprint_multipath_prod (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_prod (char * buff, size_t len, const struct multipath * mpp)
{
struct pathgroup * pgp;
struct path * pp;
}
static int
-snprint_multipath_rev (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp)
{
struct pathgroup * pgp;
struct path * pp;
}
static int
-snprint_action (char * buff, size_t len, struct multipath * mpp)
+snprint_multipath_foreign (char * buff, size_t len, const struct multipath * pp)
+{
+ return snprintf(buff, len, "%s", "--");
+}
+
+static int
+snprint_action (char * buff, size_t len, const struct multipath * mpp)
{
switch (mpp->action) {
case ACT_REJECT:
* path info printing functions
*/
static int
-snprint_path_uuid (char * buff, size_t len, struct path * pp)
+snprint_path_uuid (char * buff, size_t len, const struct path * pp)
{
return snprint_str(buff, len, pp->wwid);
}
static int
-snprint_hcil (char * buff, size_t len, struct path * pp)
+snprint_hcil (char * buff, size_t len, const struct path * pp)
{
if (!pp || pp->sg_id.host_no < 0)
return snprintf(buff, len, "#:#:#:#");
}
static int
-snprint_dev (char * buff, size_t len, struct path * pp)
+snprint_dev (char * buff, size_t len, const struct path * pp)
{
if (!pp || !strlen(pp->dev))
return snprintf(buff, len, "-");
}
static int
-snprint_dev_t (char * buff, size_t len, struct path * pp)
+snprint_dev_t (char * buff, size_t len, const struct path * pp)
{
if (!pp || !strlen(pp->dev))
return snprintf(buff, len, "#:#");
}
static int
-snprint_offline (char * buff, size_t len, struct path * pp)
+snprint_offline (char * buff, size_t len, const struct path * pp)
{
if (!pp || !pp->mpp)
return snprintf(buff, len, "unknown");
}
static int
-snprint_chk_state (char * buff, size_t len, struct path * pp)
+snprint_chk_state (char * buff, size_t len, const struct path * pp)
{
if (!pp || !pp->mpp)
return snprintf(buff, len, "undef");
}
static int
-snprint_dm_path_state (char * buff, size_t len, struct path * pp)
+snprint_dm_path_state (char * buff, size_t len, const struct path * pp)
{
if (!pp)
return snprintf(buff, len, "undef");
}
static int
-snprint_vpr (char * buff, size_t len, struct path * pp)
+snprint_vpr (char * buff, size_t len, const struct path * pp)
{
return snprintf(buff, len, "%s,%s",
pp->vendor_id, pp->product_id);
}
static int
-snprint_next_check (char * buff, size_t len, struct path * pp)
+snprint_next_check (char * buff, size_t len, const struct path * pp)
{
if (!pp || !pp->mpp)
return snprintf(buff, len, "orphan");
}
static int
-snprint_pri (char * buff, size_t len, struct path * pp)
+snprint_pri (char * buff, size_t len, const struct path * pp)
{
return snprint_int(buff, len, pp ? pp->priority : -1);
}
static int
-snprint_pg_selector (char * buff, size_t len, struct pathgroup * pgp)
+snprint_pg_selector (char * buff, size_t len, const struct pathgroup * pgp)
{
- return snprint_str(buff, len, pgp->selector);
+ const char *s = pgp->mpp->selector;
+
+ return snprint_str(buff, len, s ? s : "");
}
static int
-snprint_pg_pri (char * buff, size_t len, struct pathgroup * pgp)
-{
- /*
- * path group priority is not updated for every path prio change,
- * but only on switch group code path.
- *
- * Printing is another reason to update.
- */
- path_group_prio_update(pgp);
+snprint_pg_pri (char * buff, size_t len, const struct pathgroup * pgp)
+{
return snprint_int(buff, len, pgp->priority);
}
static int
-snprint_pg_state (char * buff, size_t len, struct pathgroup * pgp)
+snprint_pg_state (char * buff, size_t len, const struct pathgroup * pgp)
{
switch (pgp->status) {
case PGSTATE_ENABLED:
}
static int
-snprint_path_size (char * buff, size_t len, struct path * pp)
+snprint_path_size (char * buff, size_t len, const struct path * pp)
{
return snprint_size(buff, len, pp->size);
}
int
-snprint_path_serial (char * buff, size_t len, struct path * pp)
+snprint_path_serial (char * buff, size_t len, const struct path * pp)
{
return snprint_str(buff, len, pp->serial);
}
static int
-snprint_path_mpp (char * buff, size_t len, struct path * pp)
+snprint_path_mpp (char * buff, size_t len, const struct path * pp)
{
if (!pp->mpp)
return snprintf(buff, len, "[orphan]");
}
static int
-snprint_host_attr (char * buff, size_t len, struct path * pp, char *attr)
+snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
{
struct udev_device *host_dev = NULL;
char host_id[32];
}
int
-snprint_host_wwnn (char * buff, size_t len, struct path * pp)
+snprint_host_wwnn (char * buff, size_t len, const struct path * pp)
{
return snprint_host_attr(buff, len, pp, "node_name");
}
int
-snprint_host_wwpn (char * buff, size_t len, struct path * pp)
+snprint_host_wwpn (char * buff, size_t len, const struct path * pp)
{
return snprint_host_attr(buff, len, pp, "port_name");
}
int
-snprint_tgt_wwpn (char * buff, size_t len, struct path * pp)
+snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
{
struct udev_device *rport_dev = NULL;
char rport_id[32];
int
-snprint_tgt_wwnn (char * buff, size_t len, struct path * pp)
+snprint_tgt_wwnn (char * buff, size_t len, const struct path * pp)
{
if (pp->tgt_node_name[0] == '\0')
return snprintf(buff, len, "[undef]");
}
static int
-snprint_host_adapter (char * buff, size_t len, struct path * pp)
+snprint_host_adapter (char * buff, size_t len, const struct path * pp)
{
char adapter[SLOT_NAME_SIZE];
}
static int
-snprint_path_checker (char * buff, size_t len, struct path * pp)
+snprint_path_checker (char * buff, size_t len, const struct path * pp)
{
- struct checker * c = &pp->checker;
+ const struct checker * c = &pp->checker;
return snprint_str(buff, len, c->name);
}
+static int
+snprint_path_foreign (char * buff, size_t len, const struct path * pp)
+{
+ return snprintf(buff, len, "%s", "--");
+}
+
struct multipath_data mpd[] = {
{'n', "name", 0, snprint_name},
{'w', "uuid", 0, snprint_multipath_uuid},
{'v', "vend", 0, snprint_multipath_vend},
{'p', "prod", 0, snprint_multipath_prod},
{'e', "rev", 0, snprint_multipath_rev},
+ {'G', "foreign", 0, snprint_multipath_foreign},
{0, NULL, 0 , NULL}
};
{'R', "host WWPN", 0, snprint_host_wwpn},
{'r', "target WWPN", 0, snprint_tgt_wwpn},
{'a', "host adapter", 0, snprint_host_adapter},
+ {'G', "foreign", 0, snprint_path_foreign},
{0, NULL, 0 , NULL}
};
}
void
-get_path_layout (vector pathvec, int header)
+get_path_layout(vector pathvec, int header)
+{
+ vector gpvec = vector_convert(NULL, pathvec, struct path,
+ dm_path_to_gen);
+ _get_path_layout(gpvec,
+ header ? LAYOUT_RESET_HEADER : LAYOUT_RESET_ZERO);
+ vector_free(gpvec);
+}
+
+static void
+reset_width(int *width, enum layout_reset reset, const char *header)
+{
+ switch (reset) {
+ case LAYOUT_RESET_HEADER:
+ *width = strlen(header);
+ break;
+ case LAYOUT_RESET_ZERO:
+ *width = 0;
+ break;
+ default:
+ /* don't reset */
+ break;
+ }
+}
+
+void
+_get_path_layout (const struct _vector *gpvec, enum layout_reset reset)
{
int i, j;
char buff[MAX_FIELD_LEN];
- struct path * pp;
+ const struct gen_path *gp;
for (j = 0; pd[j].header; j++) {
- if (header)
- pd[j].width = strlen(pd[j].header);
- else
- pd[j].width = 0;
- vector_foreach_slot (pathvec, pp, i) {
- pd[j].snprint(buff, MAX_FIELD_LEN, pp);
+ reset_width(&pd[j].width, reset, pd[j].header);
+
+ if (gpvec == NULL)
+ continue;
+
+ vector_foreach_slot (gpvec, gp, i) {
+ gp->ops->snprint(gp, buff, MAX_FIELD_LEN,
+ pd[j].wildcard);
pd[j].width = MAX(pd[j].width, strlen(buff));
}
}
}
void
-get_multipath_layout (vector mpvec, int header)
+get_multipath_layout (vector mpvec, int header) {
+ vector gmvec = vector_convert(NULL, mpvec, struct multipath,
+ dm_multipath_to_gen);
+ _get_multipath_layout(gmvec,
+ header ? LAYOUT_RESET_HEADER : LAYOUT_RESET_ZERO);
+ vector_free(gmvec);
+}
+
+void
+_get_multipath_layout (const struct _vector *gmvec,
+ enum layout_reset reset)
{
int i, j;
char buff[MAX_FIELD_LEN];
- struct multipath * mpp;
+ const struct gen_multipath * gm;
for (j = 0; mpd[j].header; j++) {
- if (header)
- mpd[j].width = strlen(mpd[j].header);
- else
- mpd[j].width = 0;
- vector_foreach_slot (mpvec, mpp, i) {
- mpd[j].snprint(buff, MAX_FIELD_LEN, mpp);
+ reset_width(&mpd[j].width, reset, mpd[j].header);
+
+ if (gmvec == NULL)
+ continue;
+
+ vector_foreach_slot (gmvec, gm, i) {
+ gm->ops->snprint(gm, buff, MAX_FIELD_LEN,
+ mpd[j].wildcard);
mpd[j].width = MAX(mpd[j].width, strlen(buff));
}
+ condlog(4, "%s: width %d", mpd[j].header, mpd[j].width);
}
}
return NULL;
}
+int snprint_multipath_attr(const struct gen_multipath* gm,
+ char *buf, int len, char wildcard)
+{
+ const struct multipath *mpp = gen_multipath_to_dm(gm);
+ struct multipath_data *mpd = mpd_lookup(wildcard);
+
+ if (mpd == NULL)
+ return 0;
+ return mpd->snprint(buf, len, mpp);
+}
+
static struct path_data *
pd_lookup(char wildcard)
{
return NULL;
}
+int snprint_path_attr(const struct gen_path* gp,
+ char *buf, int len, char wildcard)
+{
+ const struct path *pp = gen_path_to_dm(gp);
+ struct path_data *pd = pd_lookup(wildcard);
+
+ if (pd == NULL)
+ return 0;
+ return pd->snprint(buf, len, pp);
+}
+
static struct pathgroup_data *
pgd_lookup(char wildcard)
{
return NULL;
}
+int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
+ char *buf, int len, char wildcard)
+{
+ const struct pathgroup *pg = gen_pathgroup_to_dm(gpg);
+ struct pathgroup_data *pdg = pgd_lookup(wildcard);
+
+ if (pdg == NULL)
+ return 0;
+ return pdg->snprint(buf, len, pg);
+}
+
int
-snprint_multipath_header (char * line, int len, char * format)
+snprint_multipath_header (char * line, int len, const char * format)
{
char * c = line; /* line cursor */
char * s = line; /* for padding */
- char * f = format; /* format string cursor */
+ const char * f = format; /* format string cursor */
int fwd;
struct multipath_data * data;
}
int
-snprint_multipath (char * line, int len, char * format,
- struct multipath * mpp, int pad)
+_snprint_multipath (const struct gen_multipath * gmp,
+ char * line, int len, const char * format, int pad)
{
char * c = line; /* line cursor */
char * s = line; /* for padding */
- char * f = format; /* format string cursor */
+ const char * f = format; /* format string cursor */
int fwd;
struct multipath_data * data;
char buff[MAX_FIELD_LEN] = {};
if (!(data = mpd_lookup(*f)))
continue;
- data->snprint(buff, MAX_FIELD_LEN, mpp);
+ gmp->ops->snprint(gmp, buff, MAX_FIELD_LEN, *f);
PRINT(c, TAIL, "%s", buff);
if (pad)
PAD(data->width);
}
int
-snprint_path_header (char * line, int len, char * format)
+snprint_path_header (char * line, int len, const char * format)
{
char * c = line; /* line cursor */
char * s = line; /* for padding */
- char * f = format; /* format string cursor */
+ const char * f = format; /* format string cursor */
int fwd;
struct path_data * data;
}
int
-snprint_path (char * line, int len, char * format,
- struct path * pp, int pad)
+_snprint_path (const struct gen_path * gp, char * line, int len,
+ const char * format, int pad)
{
char * c = line; /* line cursor */
char * s = line; /* for padding */
- char * f = format; /* format string cursor */
+ const char * f = format; /* format string cursor */
int fwd;
struct path_data * data;
char buff[MAX_FIELD_LEN];
if (!(data = pd_lookup(*f)))
continue;
- data->snprint(buff, MAX_FIELD_LEN, pp);
+ gp->ops->snprint(gp, buff, MAX_FIELD_LEN, *f);
PRINT(c, TAIL, "%s", buff);
if (pad)
PAD(data->width);
}
int
-snprint_pathgroup (char * line, int len, char * format,
- struct pathgroup * pgp)
+_snprint_pathgroup (const struct gen_pathgroup * ggp, char * line, int len,
+ char * format)
{
char * c = line; /* line cursor */
char * s = line; /* for padding */
if (!(data = pgd_lookup(*f)))
continue;
- data->snprint(buff, MAX_FIELD_LEN, pgp);
+ ggp->ops->snprint(ggp, buff, MAX_FIELD_LEN, *f);
PRINT(c, TAIL, "%s", buff);
PAD(data->width);
} while (*f++);
__endline(line, len, c);
return (c - line);
}
+#define snprint_pathgroup(line, len, fmt, pgp) \
+ _snprint_pathgroup(dm_pathgroup_to_gen(pgp), line, len, fmt)
-void print_multipath_topology(struct multipath *mpp, int verbosity)
+void _print_multipath_topology(const struct gen_multipath *gmp, int verbosity)
{
int resize;
char *buff = NULL;
return;
}
- len = snprint_multipath_topology(buff, maxlen, mpp, verbosity);
+ len = _snprint_multipath_topology(gmp, buff, maxlen, verbosity);
resize = (len == maxlen - 1);
if (resize) {
FREE(buff);
}
-int snprint_multipath_topology(char *buff, int len, struct multipath *mpp,
- int verbosity)
+int
+snprint_multipath_style(const struct gen_multipath *gmp, char *style, int len,
+ int verbosity)
+{
+ int n;
+ const struct multipath *mpp = gen_multipath_to_dm(gmp);
+ bool need_action = (verbosity > 1 &&
+ mpp->action != ACT_NOTHING &&
+ mpp->action != ACT_UNDEF &&
+ mpp->action != ACT_IMPOSSIBLE);
+ bool need_wwid = (strncmp(mpp->alias, mpp->wwid, WWID_SIZE));
+
+ n = snprintf(style, len, "%s%s%s%s",
+ need_action ? "%A: " : "", "%n",
+ need_wwid ? " (%w)" : "", " %d %s");
+ return MIN(n, len - 1);
+}
+
+int _snprint_multipath_topology(const struct gen_multipath *gmp,
+ char *buff, int len, int verbosity)
{
int j, i, fwd = 0;
- struct path * pp = NULL;
- struct pathgroup * pgp = NULL;
+ const struct _vector *pgvec;
+ const struct gen_pathgroup *gpg;
char style[64];
char * c = style;
char fmt[64];
reset_multipath_layout();
if (verbosity == 1)
- return snprint_multipath(buff, len, "%n", mpp, 1);
+ return _snprint_multipath(gmp, buff, len, "%n", 1);
if(isatty(1))
c += sprintf(c, "%c[%dm", 0x1B, 1); /* bold on */
- if (verbosity > 1 &&
- mpp->action != ACT_NOTHING &&
- mpp->action != ACT_UNDEF && mpp->action != ACT_IMPOSSIBLE)
- c += sprintf(c, "%%A: ");
-
- c += sprintf(c, "%%n");
-
- if (strncmp(mpp->alias, mpp->wwid, WWID_SIZE))
- c += sprintf(c, " (%%w)");
-
- c += sprintf(c, " %%d %%s");
+ c += gmp->ops->style(gmp, c, sizeof(style) - (c - style),
+ verbosity);
if(isatty(1))
c += sprintf(c, "%c[%dm", 0x1B, 0); /* bold off */
- fwd += snprint_multipath(buff + fwd, len - fwd, style, mpp, 1);
+ fwd += _snprint_multipath(gmp, buff + fwd, len - fwd, style, 1);
if (fwd >= len)
return len;
- fwd += snprint_multipath(buff + fwd, len - fwd, PRINT_MAP_PROPS, mpp,
- 1);
+ fwd += _snprint_multipath(gmp, buff + fwd, len - fwd,
+ PRINT_MAP_PROPS, 1);
if (fwd >= len)
return len;
- if (!mpp->pg)
+ pgvec = gmp->ops->get_pathgroups(gmp);
+ if (pgvec == NULL)
return fwd;
- vector_foreach_slot (mpp->pg, pgp, j) {
+ vector_foreach_slot (pgvec, gpg, j) {
+ const struct _vector *pathvec;
+ struct gen_path *gp;
+
f=fmt;
- pgp->selector = mpp->selector; /* hack */
- if (j + 1 < VECTOR_SIZE(mpp->pg)) {
+
+ if (j + 1 < VECTOR_SIZE(pgvec)) {
strcpy(f, "|-+- " PRINT_PG_INDENT);
} else
strcpy(f, "`-+- " PRINT_PG_INDENT);
- fwd += snprint_pathgroup(buff + fwd, len - fwd, fmt, pgp);
- if (fwd >= len)
- return len;
+ fwd += _snprint_pathgroup(gpg, buff + fwd, len - fwd, fmt);
+
+ if (fwd >= len) {
+ fwd = len;
+ break;
+ }
- vector_foreach_slot (pgp->paths, pp, i) {
+ pathvec = gpg->ops->get_paths(gpg);
+ if (pathvec == NULL)
+ continue;
+
+ vector_foreach_slot (pathvec, gp, i) {
f=fmt;
if (*f != '|')
*f=' ';
f++;
- if (i + 1 < VECTOR_SIZE(pgp->paths))
+ if (i + 1 < VECTOR_SIZE(pathvec))
strcpy(f, " |- " PRINT_PATH_INDENT);
else
strcpy(f, " `- " PRINT_PATH_INDENT);
- fwd += snprint_path(buff + fwd, len - fwd, fmt, pp, 1);
- if (fwd >= len)
- return len;
+ fwd += _snprint_path(gp, buff + fwd, len - fwd, fmt, 1);
+ if (fwd >= len) {
+ fwd = len;
+ break;
+ }
}
+ gpg->ops->rel_paths(gpg, pathvec);
+
+ if (fwd == len)
+ break;
}
+ gmp->ops->rel_pathgroups(gmp, pgvec);
return fwd;
}
static int
snprint_multipath_fields_json (char * buff, int len,
- struct multipath * mpp, int last)
+ const struct multipath * mpp, int last)
{
int i, j, fwd = 0;
struct path *pp;
vector_foreach_slot (mpp->pg, pgp, i) {
- pgp->selector = mpp->selector;
fwd += snprint_pathgroup(buff + fwd, len - fwd, PRINT_JSON_GROUP, pgp);
if (fwd >= len)
return fwd;
int
snprint_multipath_map_json (char * buff, int len,
- struct multipath * mpp, int last){
+ const struct multipath * mpp, int last){
int fwd = 0;
fwd += snprint_json_header(buff, len);
}
int
-snprint_multipath_topology_json (char * buff, int len, struct vectors * vecs)
+snprint_multipath_topology_json (char * buff, int len, const struct vectors * vecs)
{
int i, fwd = 0;
struct multipath * mpp;
}
static int
-snprint_hwentry (struct config *conf, char * buff, int len, struct hwentry * hwe)
+snprint_hwentry (struct config *conf, char * buff, int len, const struct hwentry * hwe)
{
int i;
int fwd = 0;
}
static int
-snprint_mpentry (struct config *conf, char * buff, int len, struct mpentry * mpe)
+snprint_mpentry (struct config *conf, char * buff, int len, const struct mpentry * mpe)
{
int i;
int fwd = 0;
}
int snprint_overrides(struct config *conf, char * buff, int len,
- struct hwentry *overrides)
+ const struct hwentry *overrides)
{
int fwd = 0;
int i;
return fwd;
}
-int snprint_status(char *buff, int len, struct vectors *vecs)
+int snprint_status(char *buff, int len, const struct vectors *vecs)
{
int fwd = 0;
int i;
}
int snprint_devices(struct config *conf, char * buff, int len,
- struct vectors *vecs)
+ const struct vectors *vecs)
{
DIR *blkdir;
struct dirent *blkdev;
printf("%s", line);
}
-void print_multipath(struct multipath *mpp, char *style)
-{
- char line[MAX_LINE_LEN];
-
- memset(&line[0], 0, MAX_LINE_LEN);
- snprint_multipath(&line[0], MAX_LINE_LEN, style, mpp, 1);
- printf("%s", line);
-}
-
-void print_pathgroup(struct pathgroup *pgp, char *style)
-{
- char line[MAX_LINE_LEN];
-
- memset(&line[0], 0, MAX_LINE_LEN);
- snprint_pathgroup(&line[0], MAX_LINE_LEN, style, pgp);
- printf("%s", line);
-}
-
-void print_map(struct multipath *mpp, char *params)
-{
- if (mpp->size && params)
- printf("0 %llu %s %s\n",
- mpp->size, TGT_MPATH, params);
- return;
-}
-
void print_all_paths(vector pathvec, int banner)
{
print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
+#ifndef _PRINT_H
+#define _PRINT_H
+#include "dm-generic.h"
+
#define PRINT_PATH_LONG "%w %i %d %D %p %t %T %s %o"
#define PRINT_PATH_INDENT "%i %d %D %t %T %o"
#define PRINT_PATH_CHECKER "%i %d %D %p %t %T %o %C"
char wildcard;
char * header;
int width;
- int (*snprint)(char * buff, size_t len, struct path * pp);
+ int (*snprint)(char * buff, size_t len, const struct path * pp);
};
struct multipath_data {
char wildcard;
char * header;
int width;
- int (*snprint)(char * buff, size_t len, struct multipath * mpp);
+ int (*snprint)(char * buff, size_t len, const struct multipath * mpp);
};
struct pathgroup_data {
char wildcard;
char * header;
int width;
- int (*snprint)(char * buff, size_t len, struct pathgroup * pgp);
+ int (*snprint)(char * buff, size_t len, const struct pathgroup * pgp);
+};
+
+enum layout_reset {
+ LAYOUT_RESET_NOT,
+ LAYOUT_RESET_ZERO,
+ LAYOUT_RESET_HEADER,
};
+void _get_path_layout (const struct _vector *gpvec, enum layout_reset);
void get_path_layout (vector pathvec, int header);
+void _get_multipath_layout (const struct _vector *gmvec, enum layout_reset);
void get_multipath_layout (vector mpvec, int header);
-int snprint_path_header (char *, int, char *);
-int snprint_multipath_header (char *, int, char *);
-int snprint_path (char *, int, char *, struct path *, int);
-int snprint_multipath (char *, int, char *, struct multipath *, int);
-int snprint_multipath_topology (char *, int, struct multipath * mpp,
- int verbosity);
+int snprint_path_header (char *, int, const char *);
+int snprint_multipath_header (char *, int, const char *);
+int _snprint_path (const struct gen_path *, char *, int, const char *, int);
+#define snprint_path(buf, len, fmt, pp, v) \
+ _snprint_path(dm_path_to_gen(pp), buf, len, fmt, v)
+int _snprint_multipath (const struct gen_multipath *, char *, int,
+ const char *, int);
+#define snprint_multipath(buf, len, fmt, mp, v) \
+ _snprint_multipath(dm_multipath_to_gen(mp), buf, len, fmt, v)
+int _snprint_multipath_topology (const struct gen_multipath *, char *, int,
+ int verbosity);
+#define snprint_multipath_topology(buf, len, mpp, v) \
+ _snprint_multipath_topology (dm_multipath_to_gen(mpp), buf, len, v)
int snprint_multipath_topology_json (char * buff, int len,
- struct vectors * vecs);
+ const struct vectors * vecs);
int snprint_multipath_map_json (char * buff, int len,
- struct multipath * mpp, int last);
+ const struct multipath * mpp, int last);
int snprint_defaults (struct config *, char *, int);
int snprint_blacklist (struct config *, char *, int);
int snprint_blacklist_except (struct config *, char *, int);
int snprint_blacklist_report (struct config *, char *, int);
int snprint_wildcards (char *, int);
-int snprint_status (char *, int, struct vectors *);
-int snprint_devices (struct config *, char *, int, struct vectors *);
-int snprint_hwtable (struct config *, char *, int, vector);
-int snprint_mptable (struct config *, char *, int, vector);
-int snprint_overrides (struct config *, char *, int, struct hwentry *);
-int snprint_path_serial (char *, size_t, struct path *);
-int snprint_host_wwnn (char *, size_t, struct path *);
-int snprint_host_wwpn (char *, size_t, struct path *);
-int snprint_tgt_wwnn (char *, size_t, struct path *);
-int snprint_tgt_wwpn (char *, size_t, struct path *);
+int snprint_status (char *, int, const struct vectors *);
+int snprint_devices (struct config *, char *, int, const struct vectors *);
+int snprint_hwtable (struct config *, char *, int, const vector);
+int snprint_mptable (struct config *, char *, int, const vector);
+int snprint_overrides (struct config *, char *, int, const struct hwentry *);
+int snprint_path_serial (char *, size_t, const struct path *);
+int snprint_host_wwnn (char *, size_t, const struct path *);
+int snprint_host_wwpn (char *, size_t, const struct path *);
+int snprint_tgt_wwnn (char *, size_t, const struct path *);
+int snprint_tgt_wwpn (char *, size_t, const struct path *);
+
+void _print_multipath_topology (const struct gen_multipath * gmp,
+ int verbosity);
+#define print_multipath_topology(mpp, v) \
+ _print_multipath_topology(dm_multipath_to_gen(mpp), v)
-void print_multipath_topology (struct multipath * mpp, int verbosity);
-void print_path (struct path * pp, char * style);
-void print_multipath (struct multipath * mpp, char * style);
-void print_pathgroup (struct pathgroup * pgp, char * style);
-void print_map (struct multipath * mpp, char * params);
void print_all_paths (vector pathvec, int banner);
void print_all_paths_custo (vector pathvec, int banner, char *fmt);
-void print_hwtable (vector hwtable);
+
+int snprint_path_attr(const struct gen_path* gp,
+ char *buf, int len, char wildcard);
+int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
+ char *buf, int len, char wildcard);
+int snprint_multipath_attr(const struct gen_multipath* gm,
+ char *buf, int len, char wildcard);
+int snprint_multipath_style(const struct gen_multipath *gmp,
+ char *style, int len, int verbosity);
+#endif /* _PRINT_H */
uninstall:
for file in $(LIBS); do $(RM) $(DESTDIR)$(libdir)/$$file; done
-clean:
+clean: dep_clean
$(RM) core *.a *.o *.gz *.so
+
+OBJS = $(LIBS:libprio%.so=%.o) alua_rtpg.o
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
#include "prio.h"
#include "structs.h"
#include "util.h"
+#include "time-util.h"
#define pp_pl_log(prio, fmt, args...) condlog(prio, "path_latency prio: " fmt, ##args)
#define DEF_IO_NUM 100
#define MAX_BASE_NUM 10
-#define MIN_BASE_NUM 1.01
-#define DEF_BASE_NUM 1.5
+#define MIN_BASE_NUM 1.1
+// This is 10**(1/4). 4 prio steps correspond to a factor of 10.
+#define DEF_BASE_NUM 1.77827941004
#define MAX_AVG_LATENCY 100000000. /* Unit: us */
#define MIN_AVG_LATENCY 1. /* Unit: us */
#define DEF_BLK_SIZE 4096
-static double lg_path_latency[MAX_IO_NUM];
-
-static inline long long timeval_to_us(const struct timespec *tv)
-{
- return ((long long)tv->tv_sec * USEC_PER_SEC) +
- (tv->tv_nsec / NSEC_PER_USEC);
-}
-
static int prepare_directio_read(int fd, int *blksz, char **pbuf,
int *restore_flags)
{
return 0;
}
-double calc_standard_deviation(double *lg_path_latency, int size,
- double lg_avglatency)
-{
- int index;
- double sum = 0;
-
- for (index = 0; index < size; index++) {
- sum += (lg_path_latency[index] - lg_avglatency) *
- (lg_path_latency[index] - lg_avglatency);
- }
-
- sum /= (size - 1);
-
- return sqrt(sum);
-}
-
/*
* Do not scale the prioriy in a certain range such as [0, 1024]
* because scaling will eliminate the effect of base_num.
int getprio(struct path *pp, char *args, unsigned int timeout)
{
int rc, temp;
- int index = 0;
int io_num = 0;
double base_num = 0;
double lg_avglatency, lg_maxavglatency, lg_minavglatency;
double standard_deviation;
double lg_toldelay = 0;
- long long before, after;
- struct timespec tv;
int blksize;
char *buf;
int restore_flags = 0;
double lg_base;
- long long sum_latency = 0;
- long long arith_mean_lat;
+ double sum_squares = 0;
if (pp->fd < 0)
return -1;
pp->dev, io_num, base_num);
}
- memset(lg_path_latency, 0, sizeof(lg_path_latency));
lg_base = log(base_num);
lg_maxavglatency = log(MAX_AVG_LATENCY) / lg_base;
lg_minavglatency = log(MIN_AVG_LATENCY) / lg_base;
temp = io_num;
while (temp-- > 0) {
- (void)clock_gettime(CLOCK_MONOTONIC, &tv);
- before = timeval_to_us(&tv);
+ struct timespec tv_before, tv_after, tv_diff;
+ double diff, reldiff;
+
+ (void)clock_gettime(CLOCK_MONOTONIC, &tv_before);
if (do_directio_read(pp->fd, timeout, buf, blksize)) {
pp_pl_log(0, "%s: path down", pp->dev);
return -1;
}
- (void)clock_gettime(CLOCK_MONOTONIC, &tv);
- after = timeval_to_us(&tv);
+ (void)clock_gettime(CLOCK_MONOTONIC, &tv_after);
+
+ timespecsub(&tv_after, &tv_before, &tv_diff);
+ diff = tv_diff.tv_sec * 1000 * 1000 + tv_diff.tv_nsec / 1000;
+
+ if (diff == 0)
+ /*
+ * Avoid taking log(0).
+ * This unlikely case is treated as minimum -
+ * the sums don't increase
+ */
+ continue;
+
+ /* we scale by lg_base here */
+ reldiff = log(diff) / lg_base;
+
/*
* We assume that the latency complies with Log-normal
* distribution. The logarithm of latency is in normal
* distribution.
*/
- lg_path_latency[index] = log(after - before) / lg_base;
- lg_toldelay += lg_path_latency[index++];
- sum_latency += after - before;
+ lg_toldelay += reldiff;
+ sum_squares += reldiff * reldiff;
}
cleanup_directio_read(pp->fd, buf, restore_flags);
lg_avglatency = lg_toldelay / (long long)io_num;
- arith_mean_lat = sum_latency / (long long)io_num;
- pp_pl_log(4, "%s: arithmetic mean latency is (%lld us), geometric mean latency is (%lld us)",
- pp->dev, arith_mean_lat,
- (long long)pow(base_num, lg_avglatency));
if (lg_avglatency > lg_maxavglatency) {
- pp_pl_log(0,
+ pp_pl_log(2,
"%s: average latency (%lld us) is outside the thresold (%lld us)",
pp->dev, (long long)pow(base_num, lg_avglatency),
(long long)MAX_AVG_LATENCY);
return DEFAULT_PRIORITY;
}
- standard_deviation = calc_standard_deviation(lg_path_latency,
- index, lg_avglatency);
- /*
- * In calPrio(), we let prio y = f(x) = log(max, base) - log (x, base);
- * So if we want to let the priority of the latency outside 2 standard
- * deviations can be distinguished from the latency inside 2 standard
- * deviation, in others words at most 95% are the same and at least 5%
- * are different according interval estimation of normal distribution,
- * we should warn the user to set the base_num to be smaller if the
- * log(x_threshold, base) is small than 2 standard deviation.
- * x_threshold is derived from:
- * y + 1 = f(x) + 1 = f(x) + log(base, base), so x_threadshold =
- * base_num; Note that we only can compare the logarithm of x_threshold
- * with the standard deviation because the standard deviation is derived
- * from logarithm of latency.
- *
- * therefore , we recommend the base_num to meet the condition :
- * 1 <= 2 * standard_deviation
- */
- pp_pl_log(5, "%s: standard deviation for logarithm of latency = %.6f",
- pp->dev, standard_deviation);
- if (standard_deviation <= 0.5)
- pp_pl_log(3, "%s: the base_num(%.3lf) is too big to distinguish different priority "
- "of two far-away latency. It is recommend to be set smaller",
- pp->dev, base_num);
- /*
- * If the standard deviation is too large , we should also warn the user
- */
-
- if (standard_deviation > 4)
- pp_pl_log(3, "%s: the base_num(%.3lf) is too small to avoid noise disturbance "
- ".It is recommend to be set larger",
- pp->dev, base_num);
-
+ standard_deviation = sqrt((sum_squares - lg_toldelay * lg_avglatency)
+ / (io_num - 1));
rc = calcPrio(lg_avglatency, lg_maxavglatency, lg_minavglatency);
+ pp_pl_log(3, "%s: latency avg=%.2e uncertainty=%.1f prio=%d\n",
+ pp->dev, exp(lg_avglatency * lg_base),
+ exp(standard_deviation * lg_base), rc);
+
return rc;
}
#include "discovery.h"
#include "dict.h"
#include "util.h"
+#include "sysfs.h"
#include "prioritizers/alua_rtpg.h"
#include "prkey.h"
#include <inttypes.h>
+#include <libudev.h>
pgpolicyfn *pgpolicies[] = {
NULL,
return 0;
}
+static int get_dh_state(struct path *pp, char *value, size_t value_len)
+{
+ struct udev_device *ud;
+
+ if (pp->udev == NULL)
+ return -1;
+
+ ud = udev_device_get_parent_with_subsystem_devtype(
+ pp->udev, "scsi", "scsi_device");
+ if (ud == NULL)
+ return -1;
+
+ return sysfs_attr_get_value(ud, "dh_state", value, value_len);
+}
+
int select_hwhandler(struct config *conf, struct multipath *mp)
{
char *origin;
+ struct path *pp;
+ /* dh_state is no longer than "detached" */
+ char handler[12];
+ char *dh_state;
+ int i;
+
+ dh_state = &handler[2];
+ if (mp->retain_hwhandler != RETAIN_HWHANDLER_OFF) {
+ vector_foreach_slot(mp->paths, pp, i) {
+ if (get_dh_state(pp, dh_state, sizeof(handler) - 2) > 0
+ && strcmp(dh_state, "detached")) {
+ memcpy(handler, "1 ", 2);
+ mp->hwhandler = handler;
+ origin = "(setting: retained by kernel driver)";
+ goto out;
+ }
+ }
+ }
mp_set_hwe(hwhandler);
mp_set_conf(hwhandler);
if (pp->tpgs <= 0)
return;
- if (pp->tpgs == 2 && !check_rdac(pp)) {
+ if (pp->tpgs == 2 || !check_rdac(pp)) {
if (sysfs_get_asymmetric_access_state(pp, buff, 512) >= 0)
default_prio = PRIO_SYSFS;
}
char *origin = NULL;
char buff[12];
- if (mp->flush_on_last_del == FLUSH_IN_PROGRESS) {
- condlog(0, "%s: flush_on_last_del in progress", mp->alias);
+ if (mp->disable_queueing) {
+ condlog(0, "%s: queueing disabled", mp->alias);
mp->no_path_retry = NO_PATH_RETRY_FAIL;
return 0;
}
{
char *origin;
- if (mp->flush_on_last_del == FLUSH_IN_PROGRESS)
- return 0;
mp_set_mpe(flush_on_last_del);
mp_set_ovr(flush_on_last_del);
mp_set_hwe(flush_on_last_del);
origin);
return 0;
}
+
+int select_ghost_delay (struct config *conf, struct multipath * mp)
+{
+ char *origin, buff[12];
+
+ mp_set_mpe(ghost_delay);
+ mp_set_ovr(ghost_delay);
+ mp_set_hwe(ghost_delay);
+ mp_set_conf(ghost_delay);
+ mp_set_default(ghost_delay, DEFAULT_GHOST_DELAY);
+out:
+ print_off_int_undef(buff, 12, &mp->ghost_delay);
+ condlog(3, "%s: ghost_delay = %s %s", mp->alias, buff, origin);
+ return 0;
+}
int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp);
int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp);
int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp);
+int select_ghost_delay(struct config *conf, struct multipath * mp);
void reconcile_features_with_options(const char *id, char **features,
int* no_path_retry,
int *retain_hwhandler);
#include "blacklist.h"
#include "prio.h"
#include "prioritizers/alua_spc3.h"
+#include "dm-generic.h"
struct adapter_group *
alloc_adaptergroup(void)
pp->tpgs = TPGS_UNDEF;
pp->priority = PRIO_UNDEF;
checker_clear(&pp->checker);
+ dm_path_to_gen(pp)->ops = &dm_gen_path_ops;
}
return pp;
}
pgp = NULL;
}
+ dm_pathgroup_to_gen(pgp)->ops = &dm_gen_pathgroup_ops;
return pgp;
}
mpp->mpcontext = NULL;
mpp->no_path_retry = NO_PATH_RETRY_UNDEF;
mpp->fast_io_fail = MP_FAST_IO_FAIL_UNSET;
+ dm_multipath_to_gen(mpp)->ops = &dm_gen_multipath_ops;
}
return mpp;
}
return 0;
}
+int add_pathgroup(struct multipath *mpp, struct pathgroup *pgp)
+{
+ int ret = store_pathgroup(mpp->pg, pgp);
+
+ if (ret)
+ return ret;
+ pgp->mpp = mpp;
+ return 0;
+}
+
int
store_hostgroup(vector hostgroupvec, struct host_group * hgp)
{
}
struct multipath *
-find_mp_by_alias (vector mpvec, char * alias)
+find_mp_by_alias (vector mpvec, const char * alias)
{
int i;
int len;
}
struct path *
-find_path_by_dev (vector pathvec, char * dev)
+find_path_by_dev (vector pathvec, const char * dev)
{
int i;
struct path * pp;
}
struct path *
-find_path_by_devt (vector pathvec, char * dev_t)
+find_path_by_devt (vector pathvec, const char * dev_t)
{
int i;
struct path * pp;
return pgp?VECTOR_SLOT(pgp->paths, 0):NULL;
}
-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);
- }
-}
-
int add_feature(char **f, const char *n)
{
int c = 0, d, l;
#include "prio.h"
#include "byteorder.h"
+#include "generic.h"
#define WWID_SIZE 128
#define SERIAL_SIZE 65
#define SCSI_PRODUCT_SIZE 17
#define SCSI_REV_SIZE 5
#define SCSI_STATE_SIZE 19
+#define NVME_MODEL_SIZE 41
+#define NVME_REV_SIZE 9
+
+/* This must be the maximum of SCSI and NVME sizes */
+#define PATH_PRODUCT_SIZE NVME_MODEL_SIZE
+#define PATH_REV_SIZE NVME_REV_SIZE
#define NO_PATH_RETRY_UNDEF 0
#define NO_PATH_RETRY_FAIL -1
FLUSH_UNDEF = YNU_UNDEF,
FLUSH_DISABLED = YNU_NO,
FLUSH_ENABLED = YNU_YES,
- FLUSH_IN_PROGRESS,
};
enum log_checker_err_states {
NU_UNDEF = 0,
};
+enum ghost_delay_states {
+ GHOST_DELAY_OFF = NU_NO,
+ GHOST_DELAY_UNDEF = NU_UNDEF,
+};
+
enum initialized_states {
INIT_FAILED,
INIT_MISSING_UDEV,
struct hd_geometry geom;
char wwid[WWID_SIZE];
char vendor_id[SCSI_VENDOR_SIZE];
- char product_id[SCSI_PRODUCT_SIZE];
- char rev[SCSI_REV_SIZE];
+ char product_id[PATH_PRODUCT_SIZE];
+ char rev[PATH_REV_SIZE];
char serial[SERIAL_SIZE];
char tgt_node_name[NODE_NAME_SIZE];
unsigned long long size;
int io_err_pathfail_starttime;
/* configlet pointers */
struct hwentry * hwe;
+ struct gen_path generic_path;
};
typedef int (pgpolicyfn) (struct multipath *);
int nr_active; /* current available(= not known as failed) paths */
int no_path_retry; /* number of retries after all paths are down */
int retry_tick; /* remaining times for retries */
+ int disable_queueing;
int minio;
int flush_on_last_del;
int attribute_flags;
int max_sectors_kb;
int force_readonly;
int force_udev_reload;
+ int ghost_delay;
+ int ghost_delay_tick;
unsigned int dev_loss;
uid_t uid;
gid_t gid;
int prkey_source;
struct be64 reservation_key;
unsigned char prflag;
+ struct gen_multipath generic_mp;
};
struct pathgroup {
int priority;
int enabled_paths;
vector paths;
- char * selector;
+ struct multipath *mpp;
+ struct gen_pathgroup generic_pg;
};
struct adapter_group {
int store_path (vector pathvec, struct path * pp);
int store_pathgroup (vector pgvec, struct pathgroup * pgp);
+int add_pathgroup(struct multipath*, struct pathgroup *);
-struct multipath * find_mp_by_alias (vector mp, char * alias);
+struct multipath * find_mp_by_alias (vector mp, const char * alias);
struct multipath * find_mp_by_wwid (vector mp, char * wwid);
struct multipath * find_mp_by_str (vector mp, char * wwid);
struct multipath * find_mp_by_minor (vector mp, int minor);
-struct path * find_path_by_devt (vector pathvec, char * devt);
-struct path * find_path_by_dev (vector pathvec, char * dev);
+struct path * find_path_by_devt (vector pathvec, const char * devt);
+struct path * find_path_by_dev (vector pathvec, const char * dev);
struct path * first_path (struct multipath * mpp);
int pathcountgr (struct pathgroup *, int);
int pathcount (struct multipath *, int);
int pathcmp (struct pathgroup *, struct pathgroup *);
-void setup_feature(struct multipath *, char *);
int add_feature (char **, const char *);
int remove_feature (char **, const char *);
#include "propsel.h"
#include "discovery.h"
#include "prio.h"
+#include "configure.h"
+#include "libdevmapper.h"
+#include "io_err_stat.h"
/*
* creates or updates mpp->paths reading mpp->pg
_remove_maps(vecs, STOP_WAITER);
}
-static struct hwentry *
+void
extract_hwe_from_path(struct multipath * mpp)
{
struct path * pp = NULL;
- int pg_num = -1, p_num = -1, i;
- struct pathgroup * pgp = NULL;
-
- condlog(3, "%s: searching paths for valid hwe", mpp->alias);
+ int i;
- if (mpp && mpp->pg) {
- vector_foreach_slot(mpp->pg, pgp, i) {
- if (pgp->status == PGSTATE_ACTIVE ||
- pgp->status == PGSTATE_ENABLED) {
- pg_num = i;
- break;
- }
- }
- if (pg_num >= 0)
- pgp = VECTOR_SLOT(mpp->pg, pg_num);
- }
+ if (mpp->hwe || !mpp->paths)
+ return;
- if (pgp && pgp->paths) {
- vector_foreach_slot(pgp->paths, pp, i) {
- if (pp->dmstate == PSTATE_FAILED)
- continue;
- if (strlen(pp->vendor_id) > 0 &&
- strlen(pp->product_id) > 0 &&
- strlen(pp->rev) > 0) {
- p_num = i;
- break;
- }
+ condlog(3, "%s: searching paths for valid hwe", mpp->alias);
+ /* doing this in two passes seems like paranoia to me */
+ vector_foreach_slot(mpp->paths, pp, i) {
+ if (pp->state != PATH_UP)
+ continue;
+ if (pp->hwe) {
+ mpp->hwe = pp->hwe;
+ return;
}
- if (p_num >= 0)
- pp = VECTOR_SLOT(pgp->paths, i);
}
-
- if (pp) {
- if (!strlen(pp->vendor_id) ||
- !strlen(pp->product_id) ||
- !strlen(pp->rev)) {
- condlog(3, "%s: no device details available", pp->dev);
- return NULL;
- }
- condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
- condlog(3, "%s: product = %s", pp->dev, pp->product_id);
- condlog(3, "%s: rev = %s", pp->dev, pp->rev);
- if (!pp->hwe) {
- struct config *conf = get_multipath_config();
-
- condlog(3, "searching hwtable");
- pp->hwe = find_hwe(conf->hwtable, pp->vendor_id,
- pp->product_id, pp->rev);
- put_multipath_config(conf);
+ vector_foreach_slot(mpp->paths, pp, i) {
+ if (pp->state == PATH_UP)
+ continue;
+ if (pp->hwe) {
+ mpp->hwe = pp->hwe;
+ return;
}
}
-
- return pp?pp->hwe:NULL;
}
-static int
+int
update_multipath_table (struct multipath *mpp, vector pathvec, int is_daemon)
{
char params[PARAMS_SIZE] = {0};
return 0;
}
-static int
+int
update_multipath_status (struct multipath *mpp)
{
char status[PARAMS_SIZE] = {0};
return 0;
}
-void set_no_path_retry(struct config *conf, struct multipath *mpp)
+void enter_recovery_mode(struct multipath *mpp)
+{
+ struct config *conf = get_multipath_config();
+
+ /*
+ * Enter retry mode.
+ * meaning of +1: retry_tick may be decremented in checkerloop before
+ * starting retry.
+ */
+ mpp->stat_queueing_timeouts++;
+ mpp->retry_tick = mpp->no_path_retry * conf->checkint + 1;
+ condlog(1, "%s: Entering recovery mode: max_retries=%d",
+ mpp->alias, mpp->no_path_retry);
+ put_multipath_config(conf);
+}
+
+static void set_no_path_retry(struct multipath *mpp)
{
- mpp->retry_tick = 0;
+ char is_queueing = 0;
+
mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST);
- select_no_path_retry(conf, mpp);
+ if (mpp->features && strstr(mpp->features, "queue_if_no_path"))
+ is_queueing = 1;
switch (mpp->no_path_retry) {
case NO_PATH_RETRY_UNDEF:
break;
case NO_PATH_RETRY_FAIL:
- dm_queue_if_no_path(mpp->alias, 0);
+ if (is_queueing)
+ dm_queue_if_no_path(mpp->alias, 0);
break;
case NO_PATH_RETRY_QUEUE:
- dm_queue_if_no_path(mpp->alias, 1);
+ if (!is_queueing)
+ dm_queue_if_no_path(mpp->alias, 1);
break;
default:
- dm_queue_if_no_path(mpp->alias, 1);
- if (mpp->nr_active == 0) {
- struct config *conf = get_multipath_config();
- /* Enter retry mode */
- mpp->retry_tick = mpp->no_path_retry * conf->checkint;
- condlog(1, "%s: Entering recovery mode: max_retries=%d",
- mpp->alias, mpp->no_path_retry);
- put_multipath_config(conf);
- }
+ if (mpp->nr_active > 0) {
+ mpp->retry_tick = 0;
+ dm_queue_if_no_path(mpp->alias, 1);
+ } else if (is_queueing && mpp->retry_tick == 0)
+ enter_recovery_mode(mpp);
break;
}
}
int __setup_multipath(struct vectors *vecs, struct multipath *mpp,
- int reset, int is_daemon)
+ int reset)
{
- struct config *conf;
-
if (dm_get_info(mpp->alias, &mpp->dmi)) {
/* Error accessing table */
condlog(3, "%s: cannot access table", mpp->alias);
goto out;
}
- if (!dm_map_present(mpp->alias)) {
- /* Table has been removed */
- condlog(3, "%s: table does not exist", mpp->alias);
- goto out;
- }
-
- if (update_multipath_strings(mpp, vecs->pathvec, is_daemon)) {
+ if (update_multipath_strings(mpp, vecs->pathvec, 1)) {
condlog(0, "%s: failed to setup multipath", mpp->alias);
goto out;
}
- set_multipath_wwid(mpp);
- conf = get_multipath_config();
- mpp->mpe = find_mpe(conf->mptable, mpp->wwid);
- put_multipath_config(conf);
- condlog(3, "%s: discover", mpp->alias);
-
- if (!mpp->hwe)
- mpp->hwe = extract_hwe_from_path(mpp);
- if (!mpp->hwe) {
- condlog(3, "%s: no hardware entry found, using defaults",
- mpp->alias);
- }
if (reset) {
- conf = get_multipath_config();
- select_rr_weight(conf, mpp);
- select_pgfailback(conf, mpp);
- set_no_path_retry(conf, mpp);
- select_flush_on_last_del(conf, mpp);
- put_multipath_config(conf);
+ set_no_path_retry(mpp);
if (VECTOR_SIZE(mpp->paths) != 0)
dm_cancel_deferred_remove(mpp);
}
return 1;
}
-struct multipath *add_map_without_path (struct vectors *vecs, char *alias)
+void
+sync_map_state(struct multipath *mpp)
+{
+ struct pathgroup *pgp;
+ struct path *pp;
+ unsigned int i, j;
+
+ if (!mpp->pg)
+ return;
+
+ vector_foreach_slot (mpp->pg, pgp, i){
+ vector_foreach_slot (pgp->paths, pp, j){
+ if (pp->state == PATH_UNCHECKED ||
+ pp->state == PATH_WILD ||
+ pp->state == PATH_DELAYED)
+ continue;
+ if (mpp->ghost_delay_tick > 0)
+ continue;
+ if ((pp->dmstate == PSTATE_FAILED ||
+ pp->dmstate == PSTATE_UNDEF) &&
+ (pp->state == PATH_UP || pp->state == PATH_GHOST))
+ dm_reinstate_path(mpp->alias, pp->dev_t);
+ else if ((pp->dmstate == PSTATE_ACTIVE ||
+ pp->dmstate == PSTATE_UNDEF) &&
+ (pp->state == PATH_DOWN ||
+ pp->state == PATH_SHAKY))
+ dm_fail_path(mpp->alias, pp->dev_t);
+ }
+ }
+}
+
+int
+update_map (struct multipath *mpp, struct vectors *vecs)
+{
+ int retries = 3;
+ char params[PARAMS_SIZE] = {0};
+
+retry:
+ condlog(4, "%s: updating new map", mpp->alias);
+ if (adopt_paths(vecs->pathvec, mpp)) {
+ condlog(0, "%s: failed to adopt paths for new map update",
+ mpp->alias);
+ retries = -1;
+ goto fail;
+ }
+ verify_paths(mpp, vecs);
+ mpp->action = ACT_RELOAD;
+
+ extract_hwe_from_path(mpp);
+ if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+ condlog(0, "%s: failed to setup new map in update", mpp->alias);
+ retries = -1;
+ goto fail;
+ }
+ if (domap(mpp, params, 1) <= 0 && retries-- > 0) {
+ condlog(0, "%s: map_udate sleep", mpp->alias);
+ sleep(1);
+ goto retry;
+ }
+ dm_lib_release();
+
+fail:
+ if (setup_multipath(vecs, mpp))
+ return 1;
+
+ sync_map_state(mpp);
+
+ if (retries < 0)
+ condlog(0, "%s: failed reload in new map update", mpp->alias);
+ return 0;
+}
+
+struct multipath *add_map_without_path (struct vectors *vecs, const char *alias)
{
struct multipath * mpp = alloc_multipath();
+ struct config *conf;
if (!mpp)
return NULL;
mpp->alias = STRDUP(alias);
- if (setup_multipath(vecs, mpp))
- return NULL; /* mpp freed in setup_multipath */
+ if (dm_get_info(mpp->alias, &mpp->dmi)) {
+ condlog(3, "%s: cannot access table", mpp->alias);
+ goto out;
+ }
+ set_multipath_wwid(mpp);
+ conf = get_multipath_config();
+ mpp->mpe = find_mpe(conf->mptable, mpp->wwid);
+ put_multipath_config(conf);
- if (adopt_paths(vecs->pathvec, mpp))
+ if (update_multipath_table(mpp, vecs->pathvec, 1))
+ goto out;
+ if (update_multipath_status(mpp))
goto out;
if (!vector_alloc_slot(vecs->mpvec))
vector_set_slot(vecs->mpvec, mpp);
+ if (update_map(mpp, vecs) != 0) /* map removed */
+ return NULL;
+
if (start_waiter_thread(mpp, vecs))
goto out;
return 2;
}
- if (__setup_multipath(vecs, mpp, reset, 1))
+ if (__setup_multipath(vecs, mpp, reset))
return 1; /* mpp freed in setup_multipath */
/*
void update_queue_mode_del_path(struct multipath *mpp)
{
if (--mpp->nr_active == 0) {
- if (mpp->no_path_retry > 0) {
- struct config *conf = get_multipath_config();
-
- /*
- * Enter retry mode.
- * meaning of +1: retry_tick may be decremented in
- * checkerloop before starting retry.
- */
- mpp->stat_queueing_timeouts++;
- mpp->retry_tick = mpp->no_path_retry *
- conf->checkint + 1;
- condlog(1, "%s: Entering recovery mode: max_retries=%d",
- mpp->alias, mpp->no_path_retry);
- put_multipath_config(conf);
- } else if (mpp->no_path_retry != NO_PATH_RETRY_QUEUE)
+ if (mpp->no_path_retry > 0)
+ enter_recovery_mode(mpp);
+ else if (mpp->no_path_retry != NO_PATH_RETRY_QUEUE)
mpp->stat_map_failures++;
}
condlog(2, "%s: remaining active paths: %d", mpp->alias, mpp->nr_active);
vector mpvec;
};
-void set_no_path_retry(struct config *conf, struct multipath *mpp);
+void enter_recovery_mode(struct multipath *mpp);
int adopt_paths (vector pathvec, struct multipath * mpp);
void orphan_paths (vector pathvec, struct multipath * mpp);
int verify_paths(struct multipath * mpp, struct vectors * vecs);
int update_mpp_paths(struct multipath * mpp, vector pathvec);
int __setup_multipath (struct vectors * vecs, struct multipath * mpp,
- int reset, int is_daemon);
-#define setup_multipath(vecs, mpp) __setup_multipath(vecs, mpp, 1, 1)
+ int reset);
+#define setup_multipath(vecs, mpp) __setup_multipath(vecs, mpp, 1)
int update_multipath_strings (struct multipath *mpp, vector pathvec,
int is_daemon);
+void extract_hwe_from_path(struct multipath * mpp);
void remove_map (struct multipath * mpp, struct vectors * vecs, int purge_vec);
void remove_map_and_stop_waiter (struct multipath * mpp, struct vectors * vecs, int purge_vec);
void remove_maps (struct vectors * vecs);
void remove_maps_and_stop_waiters (struct vectors * vecs);
-struct multipath * add_map_without_path (struct vectors * vecs, char * alias);
+void sync_map_state (struct multipath *);
+int update_map (struct multipath *mpp, struct vectors *vecs);
+struct multipath * add_map_without_path (struct vectors * vecs, const char * alias);
struct multipath * add_map_with_path (struct vectors * vecs,
struct path * pp, int add_vec);
int update_multipath (struct vectors *vecs, char *mapname, int reset);
void update_queue_mode_del_path(struct multipath *mpp);
void update_queue_mode_add_path(struct multipath *mpp);
+int update_multipath_table (struct multipath *mpp, vector pathvec,
+ int is_daemon);
+int update_multipath_status (struct multipath *mpp);
#endif /* _STRUCTS_VEC_H */
#include "util.h"
#include "config.h"
#include "blacklist.h"
+#include "devmapper.h"
#define MAX_ACCUMULATION_COUNT 2048
#define MAX_ACCUMULATION_TIME 30*1000
}
}
+static const char* uevent_get_env_var(const struct uevent *uev,
+ const char *attr)
+{
+ int i, len;
+ const char *p = NULL;
+
+ if (attr == NULL)
+ goto invalid;
+
+ len = strlen(attr);
+ if (len == 0)
+ goto invalid;
+
+ for (i = 0; uev->envp[i] != NULL; i++) {
+ const char *var = uev->envp[i];
+
+ if (strlen(var) > len &&
+ !memcmp(var, attr, len) && var[len] == '=') {
+ p = var + len + 1;
+ break;
+ }
+ }
+
+ condlog(4, "%s: %s -> '%s'", __func__, attr, p);
+ return p;
+
+invalid:
+ condlog(2, "%s: empty variable name", __func__);
+ return NULL;
+}
+
+static int uevent_get_env_positive_int(const struct uevent *uev,
+ const char *attr)
+{
+ const char *p = uevent_get_env_var(uev, attr);
+ char *q;
+ int ret;
+
+ if (p == NULL || *p == '\0')
+ return -1;
+
+ ret = strtoul(p, &q, 10);
+ if (*q != '\0' || ret < 0) {
+ condlog(2, "%s: invalid %s: '%s'", __func__, attr, p);
+ return -1;
+ }
+ return ret;
+}
+
void
uevent_get_wwid(struct uevent *uev)
{
- int i;
- char *uid_attribute;
+ const char *uid_attribute;
+ const char *val;
struct config * conf;
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);
+ val = uevent_get_env_var(uev, uid_attribute);
+ if (val)
+ uev->wwid = val;
+ FREE_CONST(uid_attribute);
}
bool
return err;
}
-int uevent_get_major(struct uevent *uev)
+int uevent_get_major(const struct uevent *uev)
{
- char *p, *q;
- int i, major = -1;
-
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "MAJOR", 5) && strlen(uev->envp[i]) > 6) {
- p = uev->envp[i] + 6;
- major = strtoul(p, &q, 10);
- if (p == q) {
- condlog(2, "invalid major '%s'", p);
- major = -1;
- }
- break;
- }
- }
- return major;
+ return uevent_get_env_positive_int(uev, "MAJOR");
}
-int uevent_get_minor(struct uevent *uev)
+int uevent_get_minor(const struct uevent *uev)
{
- char *p, *q;
- int i, minor = -1;
+ return uevent_get_env_positive_int(uev, "MINOR");
+}
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "MINOR", 5) && strlen(uev->envp[i]) > 6) {
- p = uev->envp[i] + 6;
- minor = strtoul(p, &q, 10);
- if (p == q) {
- condlog(2, "invalid minor '%s'", p);
- minor = -1;
- }
- break;
- }
- }
- return minor;
+int uevent_get_disk_ro(const struct uevent *uev)
+{
+ return uevent_get_env_positive_int(uev, "DISK_RO");
}
-int uevent_get_disk_ro(struct uevent *uev)
+static const char *uevent_get_dm_str(const struct uevent *uev, char *attr)
{
- char *p, *q;
- int i, ro = -1;
+ const char *tmp = uevent_get_env_var(uev, attr);
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "DISK_RO", 7) && strlen(uev->envp[i]) > 8) {
- p = uev->envp[i] + 8;
- ro = strtoul(p, &q, 10);
- if (p == q) {
- condlog(2, "invalid read_only setting '%s'", p);
- ro = -1;
- }
- break;
- }
- }
- return ro;
+ if (tmp == NULL)
+ return NULL;
+ return strdup(tmp);
}
-char *uevent_get_dm_name(struct uevent *uev)
+const char *uevent_get_dm_name(const struct uevent *uev)
{
- char *p = NULL;
- int i;
-
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "DM_NAME", 7) &&
- strlen(uev->envp[i]) > 8) {
- p = MALLOC(strlen(uev->envp[i] + 8) + 1);
- strcpy(p, uev->envp[i] + 8);
- break;
- }
- }
- return p;
+ return uevent_get_dm_str(uev, "DM_NAME");
}
-char *uevent_get_dm_path(struct uevent *uev)
+const char *uevent_get_dm_path(const struct uevent *uev)
{
- char *p = NULL;
- int i;
+ return uevent_get_dm_str(uev, "DM_PATH");
+}
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "DM_PATH", 7) &&
- strlen(uev->envp[i]) > 8) {
- p = MALLOC(strlen(uev->envp[i] + 8) + 1);
- strcpy(p, uev->envp[i] + 8);
- break;
- }
- }
- return p;
+const char *uevent_get_dm_action(const struct uevent *uev)
+{
+ return uevent_get_dm_str(uev, "DM_ACTION");
}
-char *uevent_get_dm_action(struct uevent *uev)
+bool uevent_is_mpath(const struct uevent *uev)
{
- char *p = NULL;
- int i;
+ const char *uuid = uevent_get_env_var(uev, "DM_UUID");
- for (i = 0; uev->envp[i] != NULL; i++) {
- if (!strncmp(uev->envp[i], "DM_ACTION", 9) &&
- strlen(uev->envp[i]) > 10) {
- p = MALLOC(strlen(uev->envp[i] + 10) + 1);
- strcpy(p, uev->envp[i] + 10);
- break;
- }
- }
- return p;
+ if (uuid == NULL)
+ return false;
+ if (strncmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN))
+ return false;
+ return uuid[UUID_PREFIX_LEN] != '\0';
}
char *devpath;
char *action;
char *kernel;
- char *wwid;
+ const char *wwid;
unsigned long seqnum;
char *envp[HOTPLUG_NUM_ENVP];
};
int uevent_listen(struct udev *udev);
int uevent_dispatch(int (*store_uev)(struct uevent *, void * trigger_data),
void * trigger_data);
-int uevent_get_major(struct uevent *uev);
-int uevent_get_minor(struct uevent *uev);
-int uevent_get_disk_ro(struct uevent *uev);
-char *uevent_get_dm_name(struct uevent *uev);
-char *uevent_get_dm_path(struct uevent *uev);
-char *uevent_get_dm_action(struct uevent *uev);
+int uevent_get_major(const struct uevent *uev);
+int uevent_get_minor(const struct uevent *uev);
+int uevent_get_disk_ro(const struct uevent *uev);
+const char *uevent_get_dm_name(const struct uevent *uev);
+const char *uevent_get_dm_path(const struct uevent *uev);
+const char *uevent_get_dm_action(const struct uevent *uev);
+bool uevent_is_mpath(const struct uevent *uev);
#endif /* _UEVENT_H */
int
basenamecpy (const char * str1, char * str2, int str2len)
{
- char *p;
+ const char *p;
if (!str1 || !strlen(str1))
return 0;
if (!str2)
return 0;
- p = (char *)str1 + (strlen(str1) - 1);
+ p = str1 + (strlen(str1) - 1);
while (*--p != '/' && p != str1)
continue;
return -errno;
}
count -= r;
- buf = (char *)buf + r;
+ buf = (const char *)buf + r;
}
return 0;
}
#define vector_foreach_slot_backwards(v,p,i) \
for (i = VECTOR_SIZE(v); i > 0 && ((p) = (v)->slot[i-1]); i--)
+#define identity(x) (x)
+/*
+ * Given a vector vec with elements of given type,
+ * return a newly allocated vector with elements conv(e) for each element
+ * e in vec. "conv" may be a macro or a function.
+ * Use "identity" for a simple copy.
+ */
+#define vector_convert(new, vec, type, conv) \
+ ({ \
+ const struct _vector *__v = (vec); \
+ vector __t = (new); \
+ type *__j; \
+ int __i; \
+ \
+ if (__t == NULL) \
+ __t = vector_alloc(); \
+ if (__t != NULL) { \
+ vector_foreach_slot(__v, __j, __i) { \
+ if (vector_alloc_slot(__t) == NULL) { \
+ vector_free(__t); \
+ __t = NULL; \
+ break; \
+ } \
+ vector_set_slot(__t, conv(__j)); \
+ } \
+ } \
+ __t; \
+ })
+
/* Prototypes */
extern vector vector_alloc(void);
extern void *vector_alloc_slot(vector v);
extern void vector_free(vector v);
+#define vector_free_const(x) vector_free((vector)(long)(x))
extern void free_strvec(vector strvec);
extern void vector_set_slot(vector v, void *value);
extern void vector_del_slot(vector v, int slot);
#ifndef _VERSION_H
#define _VERSION_H
-#define VERSION_CODE 0x000704
-#define DATE_CODE 0x0b0f11
+#define VERSION_CODE 0x000705
+#define DATE_CODE 0x030712
#define PROG "multipath-tools"
$(INSTALL_PROGRAM) -d $(DESTDIR)$(man8dir)
$(INSTALL_PROGRAM) -m 644 $(EXEC).8.gz $(DESTDIR)$(man8dir)
-clean:
+clean: dep_clean
$(RM) core *.o $(EXEC) *.gz
+include $(wildcard $(OBJS:.o=.d))
+
uninstall:
$(RM) $(DESTDIR)$(bindir)/$(EXEC)
$(RM) $(DESTDIR)$(man8dir)/$(EXEC).8.gz
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
TEST=="/usr/lib/udev/kpartx_id", \
IMPORT{program}=="kpartx_id %M %m $env{DM_UUID}"
-ENV{DM_TYPE}=="?*", SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_NAME}"
+ENV{DM_TYPE}=="?*", ENV{DM_SERIAL}=="?*", \
+ SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_SERIAL}"
ENV{DM_WWN}=="?*", SYMLINK+="disk/by-id/wwn-$env{DM_WWN}"
LABEL="mpath_end"
all: $(EXEC)
-$(EXEC): $(OBJS)
+$(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so
$(CC) $(CFLAGS) $(OBJS) -o $(EXEC) $(LDFLAGS) $(LIBDEPS)
$(GZIP) $(EXEC).8 > $(EXEC).8.gz
$(GZIP) $(EXEC).conf.5 > $(EXEC).conf.5.gz
$(RM) $(DESTDIR)$(man8dir)/$(EXEC).8.gz
$(RM) $(DESTDIR)$(man5dir)/$(EXEC).conf.5.gz
-clean:
+clean: dep_clean
$(RM) core *.o $(EXEC) *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
#include "discovery.h"
#include "debug.h"
#include "switchgroup.h"
+#include "dm-generic.h"
#include "print.h"
#include "alias.h"
#include "configure.h"
#include "wwids.h"
#include "uxsock.h"
#include "mpath_cmd.h"
+#include "foreign.h"
int logsink;
struct udev *udev;
}
static int
-update_paths (struct multipath * mpp)
+update_paths (struct multipath * mpp, int quick)
{
int i, j;
struct pathgroup * pgp;
* path is not in sysfs anymore
*/
pp->chkrstate = pp->state = PATH_DOWN;
+ pp->offline = 1;
continue;
}
pp->mpp = mpp;
+ if (quick)
+ continue;
conf = get_multipath_config();
if (pathinfo(pp, conf, DI_ALL))
pp->state = PATH_UNCHECKED;
continue;
}
pp->mpp = mpp;
+ if (quick)
+ continue;
if (pp->state == PATH_UNCHECKED ||
pp->state == PATH_WILD) {
conf = get_multipath_config();
* If not in "fast list mode", we need to fetch information
* about them
*/
- if (cmd != CMD_LIST_SHORT)
- update_paths(mpp);
+ update_paths(mpp, (cmd == CMD_LIST_SHORT));
if (cmd == CMD_LIST_LONG)
mpp->bestpg = select_path_group(mpp);
if (cmd == CMD_CREATE)
reinstate_paths(mpp);
}
+
+ if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) {
+ struct config *conf = get_multipath_config();
+
+ print_foreign_topology(conf->verbosity);
+ put_multipath_config(conf);
+ }
+
return 0;
}
}
}
found:
- condlog(2, "%s:%s usable paths found", devpath, r == 0 ? "" : " no");
+ condlog(r == 0 ? 3 : 2, "%s:%s usable paths found",
+ devpath, r == 0 ? "" : " no");
free:
FREE(mapname);
free_multipath(mpp, FREE_PATHS);
print_all_paths(pathvec, 1);
get_path_layout(pathvec, 0);
+ foreign_path_layout();
if (get_dm_mpvec(cmd, curmp, pathvec, refwwid))
goto out;
return DEV_NONE;
}
+/*
+ * Some multipath commands are dangerous to run while multipathd is running.
+ * For example, "multipath -r" may apply a modified configuration to the kernel,
+ * while multipathd is still using the old configuration, leading to
+ * inconsistent state.
+ *
+ * It is safer to use equivalent multipathd client commands instead.
+ */
+int delegate_to_multipathd(enum mpath_cmds cmd, const char *dev,
+ enum devtypes dev_type, const struct config *conf)
+{
+ int fd;
+ char command[1024], *p, *reply;
+ int n, r = 0;
+
+ fd = mpath_connect();
+ if (fd == -1)
+ return 0;
+
+ p = command;
+ *p = '\0';
+ n = sizeof(command);
+
+ if (cmd == CMD_CREATE && conf->force_reload == FORCE_RELOAD_YES) {
+ p += snprintf(p, n, "reconfigure");
+ }
+ /* Add other translations here */
+
+ if (strlen(command) == 0)
+ /* No command found, no need to delegate */
+ return 0;
+ else if (p >= command + sizeof(command)) {
+ condlog(0, "internal error - command buffer overflow");
+ r = -1;
+ goto out;
+ }
+
+ condlog(3, "delegating command to multipathd");
+ r = mpath_process_cmd(fd, command, &reply, conf->uxsock_timeout);
+
+ if (r == -1) {
+ condlog(1, "error in multipath command %s: %s",
+ command, strerror(errno));
+ goto out;
+ }
+
+ if (reply != NULL && *reply != '\0' && strcmp(reply, "ok\n"))
+ printf("%s", reply);
+ r = 1;
+
+out:
+ FREE(reply);
+ close(fd);
+ if (r < 0)
+ exit(1);
+ return r;
+}
+
int
main (int argc, char *argv[])
{
condlog(0, "failed to initialize prioritizers");
goto out;
}
+ /* Failing here is non-fatal */
+ init_foreign(conf->multipath_dir);
if (cmd == CMD_USABLE_PATHS) {
r = check_usable_paths(conf, dev, dev_type);
goto out;
} else
mpath_disconnect(fd);
}
+
if (cmd == CMD_REMOVE_WWID && !dev) {
condlog(0, "the -w option requires a device");
goto out;
}
+
+ if (delegate_to_multipathd(cmd, dev, dev_type, conf))
+ exit(0);
+
if (cmd == CMD_RESET_WWIDS) {
struct multipath * mpp;
int i;
dm_lib_release();
dm_lib_exit();
+ cleanup_foreign();
cleanup_prio();
cleanup_checkers();
\fImarginal_path_err_sample_time\fR.
If the rate of IO error on a particular path is greater than the
\fImarginal_path_err_rate_threshold\fR, then the path will not reinstate for
-\fImarginal_path_err_rate_threshold\fR seconds unless there is only one
+\fImarginal_path_err_recheck_gap_time\fR seconds unless there is only one
active path. After \fImarginal_path_err_recheck_gap_time\fR expires, the path
will be requeueed for rechecking. If checking result is good enough, the
path will be reinstated.
.RE
.
.
+.TP
+.B ghost_delay
+Sets the number of seconds that multipath will wait after creating a device
+with only ghost paths before marking it ready for use in systemd. This gives
+the active paths time to appear before the multipath runs the hardware handler
+to switch the ghost paths to active ones. Setting this to \fI0\fR or \fIon\fR
+makes multipath immediately mark a device with only ghost paths as ready.
+.RS
+.TP
+The default is \fBno\fR
+.RE
+.
+.
.\" ----------------------------------------------------------------------------
.SH "blacklist section"
.\" ----------------------------------------------------------------------------
.B skip_kpartx
.TP
.B max_sectors_kb
+.TP
+.B ghost_delay
.RE
.PD
.LP
.B skip_kpartx
.TP
.B max_sectors_kb
+.TP
+.B ghost_delay
.RE
.PD
.LP
.B delay_wait_checks
.TP
.B skip_kpartx
+.TP
+.B ghost_delay
.RE
.PD
.LP
# Set DM_MULTIPATH_DEVICE_PATH if the device should be handled by multipath
SUBSYSTEM!="block", GOTO="end_mpath"
ACTION!="add|change", GOTO="end_mpath"
-KERNEL!="sd*|dasd*", GOTO="end_mpath"
+KERNEL!="sd*|dasd*|nvme*", GOTO="end_mpath"
IMPORT{cmdline}="nompath"
ENV{nompath}=="?*", GOTO="end_mpath"
all : $(EXEC)
-$(EXEC): $(OBJS)
+$(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(EXEC) $(LIBDEPS)
$(GZIP) $(EXEC).8 > $(EXEC).8.gz
$(RM) $(DESTDIR)$(unitdir)/$(EXEC).service
$(RM) $(DESTDIR)$(unitdir)/$(EXEC).socket
-clean:
+clean: dep_clean
$(RM) core *.o $(EXEC) *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
/*
* execute handler
*/
- if (clock_gettime(CLOCK_MONOTONIC, &tmo) == 0) {
+ if (clock_gettime(CLOCK_REALTIME, &tmo) == 0) {
tmo.tv_sec += timeout;
} else {
tmo.tv_sec = 0;
+#ifndef _CLI_H_
+#define _CLI_H_
+
#include <stdint.h>
enum {
int cli_init (void);
void cli_exit(void);
char * key_generator (const char * str, int state);
+
+#endif /* _CLI_H_ */
#include "configure.h"
#include "blacklist.h"
#include "debug.h"
+#include "dm-generic.h"
#include "print.h"
#include "sysfs.h"
#include <errno.h>
#include <libudev.h>
#include "util.h"
#include "prkey.h"
-
+#include "propsel.h"
#include "main.h"
#include "cli.h"
#include "uevent.h"
+#include "foreign.h"
int
show_paths (char ** r, int * len, struct vectors * vecs, char * style,
int i;
struct path * pp;
char * c;
- char * reply;
+ char * reply, * header;
unsigned int maxlen = INITIAL_REPLY_LEN;
int again = 1;
get_path_layout(vecs->pathvec, 1);
+ foreign_path_layout();
+
reply = MALLOC(maxlen);
while (again) {
c = reply;
- if (pretty && VECTOR_SIZE(vecs->pathvec) > 0)
+ if (pretty)
c += snprint_path_header(c, reply + maxlen - c,
style);
+ header = c;
vector_foreach_slot(vecs->pathvec, pp, i)
c += snprint_path(c, reply + maxlen - c,
style, pp, pretty);
+ c += snprint_foreign_paths(c, reply + maxlen - c,
+ style, pretty);
+
again = ((c - reply) == (maxlen - 1));
REALLOC_REPLY(reply, again, maxlen);
}
+
+ if (pretty && c == header) {
+ /* No output - clear header */
+ *reply = '\0';
+ c = reply;
+ }
+
*r = reply;
*len = (int)(c - reply + 1);
return 0;
int again = 1;
get_path_layout(vecs->pathvec, 0);
+ foreign_path_layout();
+
reply = MALLOC(maxlen);
while (again) {
c += snprint_multipath_topology(c, reply + maxlen - c,
mpp, 2);
}
+ c += snprint_foreign_topology(c, reply + maxlen - c, 2);
again = ((c - reply) == (maxlen - 1));
REALLOC_REPLY(reply, again, maxlen);
}
+
*r = reply;
*len = (int)(c - reply + 1);
return 0;
{
int i;
struct multipath * mpp;
- char * c;
+ char * c, *header;
char * reply;
unsigned int maxlen = INITIAL_REPLY_LEN;
int again = 1;
get_multipath_layout(vecs->mpvec, 1);
+ foreign_multipath_layout();
+
reply = MALLOC(maxlen);
while (again) {
return 1;
c = reply;
- if (pretty && VECTOR_SIZE(vecs->mpvec) > 0)
+ if (pretty)
c += snprint_multipath_header(c, reply + maxlen - c,
style);
+ header = c;
vector_foreach_slot(vecs->mpvec, mpp, i) {
if (update_multipath(vecs, mpp->alias, 0)) {
}
c += snprint_multipath(c, reply + maxlen - c,
style, mpp, pretty);
- }
+ }
+ c += snprint_foreign_multipaths(c, reply + maxlen - c,
+ style, pretty);
again = ((c - reply) == (maxlen - 1));
REALLOC_REPLY(reply, again, maxlen);
}
+
+ if (pretty && c == header) {
+ /* No output - clear header */
+ *reply = '\0';
+ c = reply;
+ }
*r = reply;
*len = (int)(c - reply + 1);
return 0;
mpp->size = size;
update_mpp_paths(mpp, vecs->pathvec);
- setup_map(mpp, params, PARAMS_SIZE);
+ setup_map(mpp, params, PARAMS_SIZE, vecs);
mpp->action = ACT_RESIZE;
mpp->force_udev_reload = 1;
if (domap(mpp, params, 1) <= 0) {
char * mapname = get_keyparam(v, MAP);
struct multipath *mpp;
int minor;
+ struct config *conf;
mapname = convert_dev(mapname, 0);
condlog(2, "%s: restore map queueing (operator)", mapname);
return 1;
}
+ conf = get_multipath_config();
+ mpp->disable_queueing = 0;
+ select_no_path_retry(conf, mpp);
+ put_multipath_config(conf);
+
if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF &&
mpp->no_path_retry != NO_PATH_RETRY_FAIL) {
dm_queue_if_no_path(mpp->alias, 1);
- if (mpp->nr_active > 0)
- mpp->retry_tick = 0;
- else {
- struct config *conf = get_multipath_config();
- mpp->retry_tick = mpp->no_path_retry * conf->checkint;
- put_multipath_config(conf);
+ if (mpp->no_path_retry > 0) {
+ if (mpp->nr_active > 0)
+ mpp->retry_tick = 0;
+ else
+ enter_recovery_mode(mpp);
}
}
return 0;
condlog(2, "restore queueing (operator)");
vector_foreach_slot(vecs->mpvec, mpp, i) {
+ struct config *conf = get_multipath_config();
+ mpp->disable_queueing = 0;
+ select_no_path_retry(conf, mpp);
+ put_multipath_config(conf);
if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF &&
mpp->no_path_retry != NO_PATH_RETRY_FAIL) {
dm_queue_if_no_path(mpp->alias, 1);
- if (mpp->nr_active > 0)
- mpp->retry_tick = 0;
- else {
- struct config *conf = get_multipath_config();
- mpp->retry_tick = mpp->no_path_retry * conf->checkint;
- put_multipath_config(conf);
+ if (mpp->no_path_retry > 0) {
+ if (mpp->nr_active > 0)
+ mpp->retry_tick = 0;
+ else
+ enter_recovery_mode(mpp);
}
}
}
if (mpp->nr_active == 0)
mpp->stat_map_failures++;
- mpp->retry_tick = -1;
+ mpp->retry_tick = 0;
+ mpp->no_path_retry = NO_PATH_RETRY_FAIL;
+ mpp->disable_queueing = 1;
dm_queue_if_no_path(mpp->alias, 0);
return 0;
}
vector_foreach_slot(vecs->mpvec, mpp, i) {
if (mpp->nr_active == 0)
mpp->stat_map_failures++;
- mpp->retry_tick = -1;
+ mpp->retry_tick = 0;
+ mpp->no_path_retry = NO_PATH_RETRY_FAIL;
+ mpp->disable_queueing = 1;
dm_queue_if_no_path(mpp->alias, 0);
}
return 0;
static int use_watchdog;
#endif
-int uxsock_timeout;
-
/*
* libmultipath
*/
#include "waiter.h"
#include "io_err_stat.h"
#include "wwids.h"
+#include "foreign.h"
#include "../third-party/valgrind/drd.h"
#define FILE_NAME_SIZE 256
};
int logsink;
+int uxsock_timeout;
int verbosity;
int bindings_read_only;
int ignore_new_devs;
struct path * pp;
unsigned int i, j;
struct config *conf;
+ int bestpg;
- if (!mpp || mpp->pgfailback == -FAILBACK_MANUAL)
+ if (!mpp)
return 0;
/*
if (!mpp->pg || VECTOR_SIZE(mpp->paths) == 0)
return 0;
- mpp->bestpg = select_path_group(mpp);
+ bestpg = select_path_group(mpp);
+ if (mpp->pgfailback == -FAILBACK_MANUAL)
+ return 0;
+ mpp->bestpg = bestpg;
if (mpp->bestpg != mpp->nextpg)
return 1;
return 0;
}
-void
-sync_map_state(struct multipath *mpp)
-{
- struct pathgroup *pgp;
- struct path *pp;
- unsigned int i, j;
-
- if (!mpp->pg)
- return;
-
- vector_foreach_slot (mpp->pg, pgp, i){
- vector_foreach_slot (pgp->paths, pp, j){
- if (pp->state == PATH_UNCHECKED ||
- pp->state == PATH_WILD ||
- pp->state == PATH_DELAYED)
- continue;
- if ((pp->dmstate == PSTATE_FAILED ||
- pp->dmstate == PSTATE_UNDEF) &&
- (pp->state == PATH_UP || pp->state == PATH_GHOST))
- dm_reinstate_path(mpp->alias, pp->dev_t);
- else if ((pp->dmstate == PSTATE_ACTIVE ||
- pp->dmstate == PSTATE_UNDEF) &&
- (pp->state == PATH_DOWN ||
- pp->state == PATH_SHAKY))
- dm_fail_path(mpp->alias, pp->dev_t);
- }
- }
-}
-
static void
sync_maps_state(vector mpvec)
{
return 0;
}
-int
-update_map (struct multipath *mpp, struct vectors *vecs)
-{
- int retries = 3;
- char params[PARAMS_SIZE] = {0};
-
-retry:
- condlog(4, "%s: updating new map", mpp->alias);
- if (adopt_paths(vecs->pathvec, mpp)) {
- condlog(0, "%s: failed to adopt paths for new map update",
- mpp->alias);
- retries = -1;
- goto fail;
- }
- verify_paths(mpp, vecs);
- mpp->flush_on_last_del = FLUSH_UNDEF;
- mpp->action = ACT_RELOAD;
-
- if (setup_map(mpp, params, PARAMS_SIZE)) {
- condlog(0, "%s: failed to setup new map in update", mpp->alias);
- retries = -1;
- goto fail;
- }
- if (domap(mpp, params, 1) <= 0 && retries-- > 0) {
- condlog(0, "%s: map_udate sleep", mpp->alias);
- sleep(1);
- goto retry;
- }
- dm_lib_release();
-
-fail:
- if (setup_multipath(vecs, mpp))
- return 1;
-
- sync_map_state(mpp);
-
- if (retries < 0)
- condlog(0, "%s: failed reload in new map update", mpp->alias);
- return 0;
-}
-
static int
uev_add_map (struct uevent * uev, struct vectors * vecs)
{
- char *alias;
+ const char *alias;
int major = -1, minor = -1, rc;
condlog(3, "%s: add map (uevent)", uev->kernel);
pthread_testcancel();
rc = ev_add_map(uev->kernel, alias, vecs);
lock_cleanup_pop(vecs->lock);
- FREE(alias);
+ FREE_CONST(alias);
return rc;
}
+/*
+ * ev_add_map expects that the multipath device already exists in kernel
+ * before it is called. It just adds a device to multipathd or updates an
+ * existing device.
+ */
int
-ev_add_map (char * dev, char * alias, struct vectors * vecs)
+ev_add_map (char * dev, const char * alias, struct vectors * vecs)
{
- char * refwwid;
struct multipath * mpp;
- int map_present;
- int r = 1, delayed_reconfig, reassign_maps;
+ int delayed_reconfig, reassign_maps;
struct config *conf;
- map_present = dm_map_present(alias);
-
- if (map_present && !dm_is_mpath(alias)) {
+ if (!dm_is_mpath(alias)) {
condlog(4, "%s: not a multipath map", alias);
return 0;
}
/*
* now we can register the map
*/
- if (map_present) {
- if ((mpp = add_map_without_path(vecs, alias))) {
- sync_map_state(mpp);
- condlog(2, "%s: devmap %s registered", alias, dev);
- return 0;
- } else {
- condlog(2, "%s: uev_add_map failed", dev);
- return 1;
- }
- }
- r = get_refwwid(CMD_NONE, dev, DEV_DEVMAP, vecs->pathvec, &refwwid);
-
- if (refwwid) {
- r = coalesce_paths(vecs, NULL, refwwid, FORCE_RELOAD_NONE,
- CMD_NONE);
- dm_lib_release();
+ if ((mpp = add_map_without_path(vecs, alias))) {
+ sync_map_state(mpp);
+ condlog(2, "%s: devmap %s registered", alias, dev);
+ return 0;
+ } else {
+ condlog(2, "%s: ev_add_map failed", dev);
+ return 1;
}
-
- if (!r)
- condlog(2, "%s: devmap %s added", alias, dev);
- else if (r == 2)
- condlog(2, "%s: uev_add_map %s blacklisted", alias, dev);
- else
- condlog(0, "%s: uev_add_map %s failed", alias, dev);
-
- FREE(refwwid);
- return r;
}
static int
uev_remove_map (struct uevent * uev, struct vectors * vecs)
{
- char *alias;
+ const char *alias;
int minor;
struct multipath *mpp;
remove_map_and_stop_waiter(mpp, vecs, 1);
out:
lock_cleanup_pop(vecs->lock);
- FREE(alias);
+ FREE_CONST(alias);
return 0;
}
if (pp) {
int r;
- condlog(0, "%s: spurious uevent, path already in pathvec",
+ condlog(2, "%s: spurious uevent, path already in pathvec",
uev->kernel);
if (!pp->mpp && !strlen(pp->wwid)) {
condlog(3, "%s: reinitialize path", uev->kernel);
mpp = find_mp_by_wwid(vecs->mpvec, pp->wwid);
if (mpp && mpp->wait_for_udev &&
(pathcount(mpp, PATH_UP) > 0 ||
- (pathcount(mpp, PATH_GHOST) > 0 && pp->tpgs != TPGS_IMPLICIT))) {
+ (pathcount(mpp, PATH_GHOST) > 0 && pp->tpgs != TPGS_IMPLICIT &&
+ mpp->ghost_delay_tick <= 0))) {
/* 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;
goto fail; /* leave path added to pathvec */
verify_paths(mpp, vecs);
- mpp->flush_on_last_del = FLUSH_UNDEF;
mpp->action = ACT_RELOAD;
+ extract_hwe_from_path(mpp);
} else {
if (!should_multipath(pp, vecs->pathvec)) {
orphan_path(pp, "only one path");
/*
* push the map to the device-mapper
*/
- if (setup_map(mpp, params, PARAMS_SIZE)) {
+ if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
condlog(0, "%s: failed to setup map for addition of new "
"path %s", mpp->alias, pp->dev);
goto fail_map;
int ret;
condlog(2, "%s: remove path (uevent)", uev->kernel);
+ delete_foreign(uev->udev);
+
pthread_cleanup_push(cleanup_lock, &vecs->lock);
lock(&vecs->lock);
pthread_testcancel();
condlog(2, "%s Last path deleted, disabling queueing", mpp->alias);
mpp->retry_tick = 0;
mpp->no_path_retry = NO_PATH_RETRY_FAIL;
- mpp->flush_on_last_del = FLUSH_IN_PROGRESS;
+ mpp->disable_queueing = 1;
mpp->stat_map_failures++;
dm_queue_if_no_path(mpp->alias, 0);
}
*/
}
- if (setup_map(mpp, params, PARAMS_SIZE)) {
+ if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
condlog(0, "%s: failed to setup map for"
" removal of path %s", mpp->alias, pp->dev);
goto fail;
static int
uev_update_path (struct uevent *uev, struct vectors * vecs)
{
- int ro, retval = 0;
+ int ro, retval = 0, rc;
struct path * pp;
struct config *conf;
int disable_changed_wwids;
int needs_reinit = 0;
+ switch ((rc = change_foreign(uev->udev))) {
+ case FOREIGN_OK:
+ /* known foreign path, ignore event */
+ return 0;
+ case FOREIGN_IGNORED:
+ break;
+ case FOREIGN_ERR:
+ condlog(3, "%s: error in change_foreign", __func__);
+ break;
+ default:
+ condlog(1, "%s: return code %d of change_forein is unsupported",
+ __func__, rc);
+ break;
+ }
+
conf = get_multipath_config();
disable_changed_wwids = conf->disable_changed_wwids;
put_multipath_config(conf);
static int
uev_pathfail_check(struct uevent *uev, struct vectors *vecs)
{
- char *action = NULL, *devt = NULL;
+ const char *action = NULL, *devt = NULL;
struct path *pp;
- int r;
+ int r = 1;
action = uevent_get_dm_action(uev);
if (!action)
lock(&vecs->lock);
pthread_testcancel();
pp = find_path_by_devt(vecs->pathvec, devt);
+ if (!pp)
+ goto out_lock;
r = io_err_stat_handle_pathfail(pp);
- lock_cleanup_pop(vecs->lock);
-
if (r)
condlog(3, "io_err_stat: %s: cannot handle pathfail uevent",
pp->dev);
- FREE(devt);
- FREE(action);
- return 0;
+out_lock:
+ lock_cleanup_pop(vecs->lock);
+ FREE_CONST(devt);
+ FREE_CONST(action);
+ return r;
out:
- FREE(action);
+ FREE_CONST(action);
return 1;
}
return 1;
vector_foreach_slot (vecs->mpvec, mpp, i)
- if (setup_multipath(vecs, mpp))
+ if (update_multipath_table(mpp, vecs->pathvec, 1) ||
+ update_multipath_status(mpp)) {
+ remove_map(mpp, vecs, 1);
i--;
+ }
return 0;
}
* are not fully initialised then.
*/
if (!strncmp(uev->kernel, "dm-", 3)) {
+ if (!uevent_is_mpath(uev)) {
+ if (!strncmp(uev->action, "change", 6))
+ (void)add_foreign(uev->udev);
+ else if (!strncmp(uev->action, "remove", 6))
+ (void)delete_foreign(uev->udev);
+ goto out;
+ }
if (!strncmp(uev->action, "change", 6)) {
r = uev_add_map(uev, vecs);
* cess.
*/
uev_pathfail_check(uev, vecs);
- goto out;
- }
- if (!strncmp(uev->action, "remove", 6)) {
+ } else if (!strncmp(uev->action, "remove", 6)) {
r = uev_remove_map(uev, vecs);
- goto out;
}
goto out;
}
}
static void
+ghost_delay_tick(struct vectors *vecs)
+{
+ struct multipath * mpp;
+ unsigned int i;
+
+ vector_foreach_slot (vecs->mpvec, mpp, i) {
+ if (mpp->ghost_delay_tick <= 0)
+ continue;
+ if (--mpp->ghost_delay_tick <= 0) {
+ condlog(0, "%s: timed out waiting for active path",
+ mpp->alias);
+ mpp->force_udev_reload = 1;
+ if (update_map(mpp, vecs) != 0) {
+ /* update_map removed map */
+ i--;
+ continue;
+ }
+ }
+ }
+}
+
+static void
defered_failback_tick (vector mpvec)
{
struct multipath * mpp;
pp->tick = pp->checkint;
}
}
- else if (newstate == PATH_DOWN) {
- int log_checker_err;
+ else if (newstate != PATH_UP && newstate != PATH_GHOST) {
+ if (pp->dmstate == PSTATE_ACTIVE ||
+ pp->dmstate == PSTATE_UNDEF)
+ fail_path(pp, 0);
+ if (newstate == PATH_DOWN) {
+ int log_checker_err;
- conf = get_multipath_config();
- log_checker_err = conf->log_checker_err;
- put_multipath_config(conf);
- if (log_checker_err == LOG_CHKR_ERR_ONCE)
- LOG_MSG(3, checker_message(&pp->checker));
- else
- LOG_MSG(2, checker_message(&pp->checker));
+ conf = get_multipath_config();
+ log_checker_err = conf->log_checker_err;
+ put_multipath_config(conf);
+ if (log_checker_err == LOG_CHKR_ERR_ONCE)
+ LOG_MSG(3, checker_message(&pp->checker));
+ else
+ LOG_MSG(2, checker_message(&pp->checker));
+ }
}
pp->state = newstate;
defered_failback_tick(vecs->mpvec);
retry_count_tick(vecs->mpvec);
missing_uev_wait_tick(vecs);
+ ghost_delay_tick(vecs);
lock_cleanup_pop(vecs->lock);
if (count)
diff_time.tv_sec);
}
}
-
+ check_foreign();
post_config_state(DAEMON_IDLE);
conf = get_multipath_config();
strict_timing = conf->strict_timing;
}
int
-configure (struct vectors * vecs, int start_waiters)
+configure (struct vectors * vecs)
{
struct multipath * mpp;
struct path * pp;
i--;
continue;
}
- if (start_waiters) {
- if (start_waiter_thread(mpp, vecs)) {
- remove_map(mpp, vecs, 1);
- i--;
- }
+ if (start_waiter_thread(mpp, vecs)) {
+ remove_map(mpp, vecs, 1);
+ i--;
}
}
return 0;
free_pathvec(vecs->pathvec, FREE_PATHS);
vecs->pathvec = NULL;
+ delete_all_foreign();
/* Re-read any timezone changes */
tzset();
rcu_assign_pointer(multipath_conf, conf);
call_rcu(&old->rcu, rcu_free_config);
- configure(vecs, 1);
+ configure(vecs);
return 0;
}
void
-handle_signals(void)
+handle_signals(bool nonfatal)
{
if (exit_sig) {
condlog(2, "exit (signal)");
+ exit_sig = 0;
exit_daemon();
}
+ if (!nonfatal)
+ return;
if (reconfig_sig) {
condlog(2, "reconfigure (signal)");
set_config_state(DAEMON_CONFIGURE);
log_reset("multipathd");
pthread_mutex_unlock(&logq_lock);
}
- exit_sig = 0;
reconfig_sig = 0;
log_reset_sig = 0;
}
{
sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGUSR2);
+ /* block all signals */
+ sigfillset(&set);
+ /* SIGPIPE occurs if logging fails */
+ sigdelset(&set, SIGPIPE);
pthread_sigmask(SIG_SETMASK, &set, NULL);
+ /* Other signals will be unblocked in the uxlsnr thread */
signal_set(SIGHUP, sighup);
signal_set(SIGUSR1, sigusr1);
signal_set(SIGUSR2, sigusr2);
setup_thread_attr(&misc_attr, 64 * 1024, 0);
setup_thread_attr(&uevent_attr, DEFAULT_UEVENT_STACKSIZE * 1024, 0);
setup_thread_attr(&waiter_attr, 32 * 1024, 1);
- setup_thread_attr(&io_err_stat_attr, 32 * 1024, 1);
+ setup_thread_attr(&io_err_stat_attr, 32 * 1024, 0);
if (logsink == 1) {
setup_thread_attr(&log_attr, 64 * 1024, 0);
condlog(0, "failed to initialize prioritizers");
goto failed;
}
+ /* Failing this is non-fatal */
+
+ init_foreign(conf->multipath_dir);
setlogmask(LOG_UPTO(conf->verbosity + 3));
/*
* start threads
*/
- rc = start_io_err_stat_thread(vecs);
- if (rc)
- goto failed;
-
if ((rc = pthread_create(&check_thr, &misc_attr, checkerloop, vecs))) {
condlog(0,"failed to create checker loop thread: %d", rc);
goto failed;
remove_maps_and_stop_waiters(vecs);
unlock(&vecs->lock);
- stop_io_err_stat_thread();
-
pthread_cancel(check_thr);
pthread_cancel(uevent_thr);
pthread_cancel(uxlsnr_thr);
pthread_join(uxlsnr_thr, NULL);
pthread_join(uevq_thr, NULL);
+ stop_io_err_stat_thread();
+
lock(&vecs->lock);
free_pathvec(vecs->pathvec, FREE_PATHS);
vecs->pathvec = NULL;
FREE(vecs);
vecs = NULL;
+ cleanup_foreign();
cleanup_checkers();
cleanup_prio();
int reconfigure (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_add_map (char *, const char *, struct vectors *);
int ev_remove_map (char *, char *, int, struct vectors *);
-void sync_map_state (struct multipath *);
int set_config_state(enum daemon_status);
void * mpath_alloc_prin_response(int prin_sa);
int prin_do_scsi_ioctl(char *, int rq_servact, struct prin_resp * resp,
void * mpath_pr_event_handler_fn (void * );
int update_map_pr(struct multipath *mpp);
void * mpath_pr_event_handler_fn (void * pathp );
-void handle_signals(void);
+void handle_signals(bool);
#endif /* MAIN_H */
[Unit]
Description=Device-Mapper Multipath Device Controller
Wants=systemd-udev-trigger.service systemd-udev-settle.service
-Before=iscsi.service iscsid.service lvm2-lvmetad.service lvm2-activation-early.service
+Before=iscsi.service iscsid.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
ExecStartPre=-/sbin/modprobe -a scsi_dh_alua scsi_dh_emc scsi_dh_rdac dm-multipath
ExecStart=/sbin/multipathd -d -s
ExecReload=/sbin/multipathd reconfigure
+TasksMax=infinity
[Install]
WantedBy=sysinit.target
[Socket]
ListenStream=@/org/kernel/linux/storage/multipathd
+
+[Install]
+WantedBy=sockets.target
/*
* kill off a dead client
*/
-static void dead_client(struct client *c)
+static void _dead_client(struct client *c)
{
- pthread_mutex_lock(&client_lock);
+ int fd = c->fd;
list_del_init(&c->node);
- pthread_mutex_unlock(&client_lock);
- close(c->fd);
c->fd = -1;
FREE(c);
+ close(fd);
+}
+
+static void dead_client(struct client *c)
+{
+ pthread_cleanup_push(cleanup_lock, &client_lock);
+ pthread_mutex_lock(&client_lock);
+ _dead_client(c);
+ pthread_cleanup_pop(1);
}
void free_polls (void)
void uxsock_cleanup(void *arg)
{
+ struct client *client_loop;
+ struct client *client_tmp;
+ int ux_sock = (int)arg;
+
+ close(ux_sock);
+
+ pthread_mutex_lock(&client_lock);
+ list_for_each_entry_safe(client_loop, client_tmp, &clients, node) {
+ _dead_client(client_loop);
+ }
+ pthread_mutex_unlock(&client_lock);
+
cli_exit();
free_polls();
}
return NULL;
}
- pthread_cleanup_push(uxsock_cleanup, NULL);
+ pthread_cleanup_push(uxsock_cleanup, (void *)ux_sock);
condlog(3, "uxsock: startup listener");
polls = (struct pollfd *)MALLOC((MIN_POLLS + 1) * sizeof(struct pollfd));
condlog(0, "uxsock: failed to allocate poll fds");
return NULL;
}
- sigemptyset(&mask);
- sigaddset(&mask, SIGINT);
- sigaddset(&mask, SIGTERM);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGUSR1);
+ sigfillset(&mask);
+ sigdelset(&mask, SIGINT);
+ sigdelset(&mask, SIGTERM);
+ sigdelset(&mask, SIGHUP);
+ sigdelset(&mask, SIGUSR1);
while (1) {
struct client *c, *tmp;
int i, poll_count, num_clients;
/* most of our life is spent in this call */
poll_count = ppoll(polls, i, &sleep_time, &mask);
+ handle_signals(false);
if (poll_count == -1) {
if (errno == EINTR) {
- handle_signals();
+ handle_signals(true);
continue;
}
}
if (poll_count == 0) {
- handle_signals();
+ handle_signals(true);
continue;
}
FREE(inbuf);
}
}
+ /* see if we got a non-fatal signal */
+ handle_signals(true);
/* see if we got a new client */
if (polls[0].revents & POLLIN) {
}
pthread_cleanup_pop(1);
- close(ux_sock);
return NULL;
}
--- /dev/null
+include ../Makefile.inc
+
+CFLAGS += $(BIN_CFLAGS) -I$(multipathdir) -I$(mpathcmddir)
+LIBDEPS += -L$(multipathdir) -lmultipath -lcmocka
+
+TESTS := uevent
+
+.SILENT: $(TESTS:%=%.o)
+.PRECIOUS: $(TESTS:%=%-test)
+
+%-test: %.o globals.c $(multipathdir)/libmultipath.so
+ @$(CC) -o $@ $< $(LDFLAGS) $(LIBDEPS)
+
+%.out: %-test
+ @echo == running $< ==
+ @LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) ./$< >$@
+
+all: $(TESTS:%=%.out)
+
+clean: dep_clean
+ rm -f $(TESTS:%=%-test) $(TESTS:%=%.out) $(TESTS:%=%.o)
+
+OBJS = $(TESTS:%=%.o)
+include $(wildcard $(OBJS:.o=.d))
+
+
+
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
--- /dev/null
+#include "structs.h"
+#include "config.h"
+
+/* Required globals */
+struct udev *udev;
+int logsink = 0;
+struct config conf = {
+ .uid_attrs = "sd:ID_BOGUS",
+};
+
+struct config *get_multipath_config(void)
+{
+ return &conf;
+}
+
+void put_multipath_config(struct config* c)
+{}
--- /dev/null
+/*
+ * Copyright (c) 2018 SUSE Linux GmbH
+ *
+ * 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 2
+ * 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, write to the Free Software
+ *
+ */
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <cmocka.h>
+#include "list.h"
+#include "uevent.h"
+
+#include "globals.c"
+
+/* Private prototypes missing in uevent.h */
+struct uevent * alloc_uevent(void);
+void uevent_get_wwid(struct uevent *uev);
+
+/* Stringify helpers */
+#define _str_(x) #x
+#define str(x) _str_(x)
+
+#define MAJOR 17
+#define MINOR 217
+#define DISK_RO 0
+#define DM_NAME "spam"
+#define WWID "foo"
+
+static int setup_uev(void **state)
+{
+ struct uevent *uev = alloc_uevent();
+
+ if (uev == NULL)
+ return -1;
+
+ *state = uev;
+ uev->kernel = "sdo";
+ uev->envp[0] = "MAJOR=" str(MAJOR);
+ uev->envp[1] = "ID_BOGUS=" WWID;
+ uev->envp[2] = "MINOR=" str(MINOR);
+ uev->envp[3] = "DM_NAME=" DM_NAME;
+ uev->envp[4] = "DISK_RO=" str(DISK_RO);
+ uev->envp[5] = NULL;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ free(*state);
+ return 0;
+}
+
+static void test_major_good(void **state)
+{
+ struct uevent *uev = *state;
+
+ assert_int_equal(uevent_get_major(uev), MAJOR);
+}
+
+static void test_minor_good(void **state)
+{
+ struct uevent *uev = *state;
+
+ assert_int_equal(uevent_get_minor(uev), MINOR);
+}
+
+static void test_ro_good(void **state)
+{
+ struct uevent *uev = *state;
+
+ assert_int_equal(uevent_get_disk_ro(uev), DISK_RO);
+}
+
+static void test_wwid(void **state)
+{
+ struct uevent *uev = *state;
+ uevent_get_wwid(uev);
+
+ assert_string_equal(uev->wwid, WWID);
+}
+
+static void test_major_bad_0(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR" str(MAJOR);
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_1(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOr=" str(MAJOR);
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_2(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJORIE=" str(MAJOR);
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_3(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR=max";
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_4(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR=0x10";
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_5(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJO=" str(MAJOR);
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_6(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR=" str(-MAJOR);
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_7(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR=";
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_major_bad_8(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[0] = "MAJOR";
+ assert_int_equal(uevent_get_major(uev), -1);
+}
+
+static void test_dm_name_good(void **state)
+{
+ struct uevent *uev = *state;
+ const char *name = uevent_get_dm_name(uev);
+
+ assert_string_equal(name, DM_NAME);
+ free((void*)name);
+}
+
+static void test_dm_name_bad_0(void **state)
+{
+ struct uevent *uev = *state;
+ const char *name;
+
+ uev->envp[3] = "DM_NAME" DM_NAME;
+ name = uevent_get_dm_name(uev);
+ assert_ptr_equal(name, NULL);
+ free((void*)name);
+}
+
+static void test_dm_name_bad_1(void **state)
+{
+ struct uevent *uev = *state;
+ const char *name;
+
+ uev->envp[3] = "DM_NAMES=" DM_NAME;
+ name = uevent_get_dm_name(uev);
+ assert_ptr_equal(name, NULL);
+ free((void*)name);
+}
+
+static void test_dm_name_good_1(void **state)
+{
+ struct uevent *uev = *state;
+ const char *name;
+
+ /* Note we change index 2 here */
+ uev->envp[2] = "DM_NAME=" DM_NAME;
+ name = uevent_get_dm_name(uev);
+ assert_string_equal(name, DM_NAME);
+ free((void*)name);
+}
+
+static void test_dm_uuid_false_0(void **state)
+{
+ struct uevent *uev = *state;
+
+ assert_false(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_true_0(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UUID=mpath-foo";
+ assert_true(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_false_1(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UUID.mpath-foo";
+ assert_false(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_false_2(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UUID=mpath-";
+ assert_false(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_false_3(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UU=mpath-foo";
+ assert_false(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_false_4(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UUID=mpathfoo";
+ assert_false(uevent_is_mpath(uev));
+}
+
+static void test_dm_uuid_false_5(void **state)
+{
+ struct uevent *uev = *state;
+
+ uev->envp[3] = "DM_UUID=";
+ assert_false(uevent_is_mpath(uev));
+}
+
+int test_uevent_get_XXX(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_major_good),
+ cmocka_unit_test(test_minor_good),
+ cmocka_unit_test(test_ro_good),
+ cmocka_unit_test(test_dm_name_good),
+ cmocka_unit_test(test_wwid),
+ cmocka_unit_test(test_major_bad_0),
+ cmocka_unit_test(test_major_bad_1),
+ cmocka_unit_test(test_major_bad_2),
+ cmocka_unit_test(test_major_bad_3),
+ cmocka_unit_test(test_major_bad_4),
+ cmocka_unit_test(test_major_bad_5),
+ cmocka_unit_test(test_major_bad_6),
+ cmocka_unit_test(test_major_bad_7),
+ cmocka_unit_test(test_major_bad_8),
+ cmocka_unit_test(test_dm_name_bad_0),
+ cmocka_unit_test(test_dm_name_bad_1),
+ cmocka_unit_test(test_dm_name_good_1),
+ cmocka_unit_test(test_dm_uuid_false_0),
+ cmocka_unit_test(test_dm_uuid_true_0),
+ cmocka_unit_test(test_dm_uuid_false_1),
+ cmocka_unit_test(test_dm_uuid_false_2),
+ cmocka_unit_test(test_dm_uuid_false_3),
+ cmocka_unit_test(test_dm_uuid_false_4),
+ cmocka_unit_test(test_dm_uuid_false_5),
+ };
+ return cmocka_run_group_tests(tests, setup_uev, teardown);
+}
+
+int main(void)
+{
+ int ret = 0;
+
+ ret += test_uevent_get_XXX();
+ return ret;
+}