From: Petr Tesarik <ptesarik@suse.cz>
Subject: Add the 'multipath' command
References: bnc#738865

This is the low-level part of the multipath support improvements.
In the next step, this command should be actually used from the
mkinitrd snippet.

Signed-off-by: Petr Tesarik <ptesarik@suse.cz>

diff -r b22c79ef4138 -r 5e1e087a4614 doc/man/kdumptool.8.txt.in
--- a/doc/man/kdumptool.8.txt.in	Wed Mar 06 10:16:25 2013 +0100
+++ b/doc/man/kdumptool.8.txt.in	Fri Mar 08 09:44:44 2013 +0100
@@ -336,6 +336,30 @@
     *all*;;
       Show all variables. (default)
 
+MODIFY MULTIPATH CONFIGURATION
+------------------------------
+
+If multipath devices are used on a machine, then they should be also
+configured in the dump kernel. However, each multipath device needs
+some amount of RAM at run time, increasing the necessary size of the
+crash reserved area. That way it is even possible to exceed the maximum
+size of that area on machines with a very large number of multipath
+devices, 
+
+This subcommand reads an existing multipath configuration on standard
+input, blacklists all devices except those specified on the command line,
+and sends the modified configuration to standard output. This allows to
+let the dump kernel initialize only multipath devices that are necessary
+for saving the dump.
+
+Syntax
+~~~~~~
+
+*kdumptool* [_globals_] *multipath* _device_[...]
+
+_device_ is a multipath device specification (e.g. "wwid
+360060e801531f800000131f80000a001"). See *multipath.conf(5)* for details.
+
 
 RETURN VALUE
 ------------
diff -r b22c79ef4138 -r 5e1e087a4614 kdumptool/CMakeLists.txt
--- a/kdumptool/CMakeLists.txt	Wed Mar 06 10:16:25 2013 +0100
+++ b/kdumptool/CMakeLists.txt	Fri Mar 08 09:44:44 2013 +0100
@@ -82,6 +82,8 @@
     dumpconfig.h
     quotedstring.cc
     quotedstring.h
+    multipath.cc
+    multipath.h
     main.cc
 )
 
diff -r b22c79ef4138 -r 5e1e087a4614 kdumptool/multipath.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kdumptool/multipath.cc	Fri Mar 08 09:44:44 2013 +0100
@@ -0,0 +1,264 @@
+/*
+ * (c) 2013, Petr Tesarik <ptesarik@suse.cz>, SUSE LINUX Products 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 <iostream>
+#include <string>
+
+#include "subcommand.h"
+#include "debug.h"
+#include "multipath.h"
+#include "configuration.h"
+
+using std::string;
+using std::cin;
+using std::cout;
+
+//{{{ MultipathConf -----------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+MultipathConf::MultipathConf(std::istream &input)
+    throw ()
+    : m_input(input)
+{ }
+
+// -----------------------------------------------------------------------------
+void MultipathConf::process(class Handler &handler)
+{
+    while (readline()) {
+	StringVector tokens;
+	string::const_iterator it = m_line.begin();
+	bool stringmode = false;
+	while (1) {
+
+	    // skip whitespace
+	    while (it != m_line.end() && *it &&
+		   (isspace(*it) || *it < 0))
+		++it;
+
+	    // end of line or comment?
+	    if (it == m_line.end() || *it == '\0' || *it == '#' || *it == '!')
+		break;
+
+	    // get a token
+	    if (*it == '"') {
+		// double quotes toggle string mode
+		stringmode = !stringmode;
+		tokens.push_back(string(1, *it));
+		++it;
+	    } else if (!stringmode && (*it == '{' || *it == '}')) {
+		// group start/end
+		tokens.push_back(string(1, *it));
+		++it;
+	    } else {
+		string::const_iterator itr(it);
+		while (itr != m_line.end() && *itr != '\0' &&
+		       // quote terminates a token
+		       *itr != '"' &&
+		       // any special character is accepted in string mode
+		       (stringmode ||
+			// outside of string mode, tokens are terminated at:
+			// * white space or non-ASCII
+			!(isspace(*itr) || *itr < 0 ||
+			  // * comment
+			  *itr == '#' || *itr == '!' ||
+			  // * group start/end
+			  *itr == '{' || *itr == '}')))
+		    ++itr;
+		tokens.push_back(string(it, itr));
+		it = itr;
+	    }
+	}
+
+	handler.process(m_line, tokens);
+    }
+
+    StringVector empty;
+    handler.process(m_line, empty);
+}
+
+// -----------------------------------------------------------------------------
+bool MultipathConf::readline(void)
+{
+    char c;
+    m_line.clear();
+    do {
+	if (!m_input.get(c))
+	    break;
+	m_line += c;
+    } while (c != '\n' && c != '\r');
+    return !m_input.fail();
+}
+
+//{{{ AddBlacklistHandler -----------------------------------------------------
+
+/**
+ * This class implements a multipath.conf handler that copies the config file
+ * to an output stream, adding blacklist and blacklist_exceptions sections.
+ *
+ * If the respective sections are already found in the input, then the
+ * appropriate keywords are added there, because multipath doesn't accept
+ * multiple root sections.
+ *
+ * If the sections are not found, they are added to the beginning of the file.
+ * It is not added to the end of the file, because closing braces are optional
+ * at the end of the file, and opening braces are optional after a keyword
+ * which is known (to multipath!) to start a subsection. As a result, the
+ * state of the multipath parser at the end of the file cannot be reliably
+ * known. Unlike the state at the beginning of the file.
+ */
+class AddBlacklistHandler : public MultipathConf::Handler
+{
+    public:
+        AddBlacklistHandler(std::ostream &output,
+			    const StringVector &exceptions)
+        throw ();
+        ~AddBlacklistHandler();
+
+        virtual void process(const std::string &raw, StringVector &tokens);
+
+    protected:
+	std::ostream &m_output;
+        const StringVector &m_exceptions;
+
+    private:
+        void addLine(const std::string &raw);
+        void outputLine(const std::string &raw);
+        typedef void (AddBlacklistHandler::*lineHandler)(const std::string&);
+        void doBlacklist(lineHandler handler);
+        void doExceptions(lineHandler handler);
+
+        /**
+	 * Raw lines read from the input config file
+	 */
+        StringVector m_lines;
+
+        bool m_blacklist_done;
+        bool m_exceptions_done;
+};
+
+// -----------------------------------------------------------------------------
+AddBlacklistHandler::AddBlacklistHandler(std::ostream &output,
+                                         const StringVector &exceptions)
+    throw ()
+    : m_output(output), m_exceptions(exceptions),
+      m_blacklist_done(false), m_exceptions_done(false)
+{ }
+
+// -----------------------------------------------------------------------------
+AddBlacklistHandler::~AddBlacklistHandler()
+{
+    StringVector::const_iterator it;
+
+    if (!m_blacklist_done) {
+        m_output << "blacklist {\n";
+        doBlacklist(&AddBlacklistHandler::outputLine);
+        m_output << "}\n";
+    }
+    if (!m_exceptions_done) {
+        m_output << "blacklist_exceptions {\n";
+        doExceptions(&AddBlacklistHandler::outputLine);
+        m_output << "}\n";
+    }
+
+    for (it = m_lines.begin(); it != m_lines.end(); ++it)
+        m_output << *it;
+}
+
+// -----------------------------------------------------------------------------
+void AddBlacklistHandler::process(const string &raw, StringVector &tokens)
+{
+    m_lines.push_back(raw);
+    if (tokens.size() > 0) {
+        if (tokens[0] == "blacklist")
+            doBlacklist(&AddBlacklistHandler::addLine);
+        else if (tokens[0] == "blacklist_exceptions")
+            doExceptions(&AddBlacklistHandler::addLine);
+    }
+}
+
+// -----------------------------------------------------------------------------
+void AddBlacklistHandler::addLine(const string &raw)
+{
+    m_lines.push_back(raw);
+}
+
+// -----------------------------------------------------------------------------
+void AddBlacklistHandler::outputLine(const string &raw)
+{
+    m_output << raw;
+}
+
+// -----------------------------------------------------------------------------
+void AddBlacklistHandler::doBlacklist(lineHandler handler)
+{
+    (this->*handler)("\twwid \"*\"\n");
+    m_blacklist_done = true;
+}
+
+void AddBlacklistHandler::doExceptions(lineHandler handler)
+{
+    StringVector::const_iterator it;
+    for (it = m_exceptions.begin(); it != m_exceptions.end(); ++it)
+	(this->*handler)("\t" + *it + "\n");
+    m_exceptions_done = true;
+}
+
+//{{{ Multipath ---------------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+Multipath::Multipath()
+    throw ()
+    : m_parser(cin)
+{ }
+
+// -----------------------------------------------------------------------------
+const char *Multipath::getName() const
+    throw ()
+{
+    return "multipath";
+}
+
+// -----------------------------------------------------------------------------
+bool Multipath::needsConfigfile() const
+    throw ()
+{
+    return false;
+}
+
+// -----------------------------------------------------------------------------
+void Multipath::parseCommandline(OptionParser *optionparser)
+    throw (KError)
+{
+    Debug::debug()->trace("Multipath::parseCommandline(%p)", optionparser);
+
+    m_exceptions = optionparser->getArgs();
+    m_exceptions.erase(m_exceptions.begin());
+}
+
+// -----------------------------------------------------------------------------
+void Multipath::execute()
+    throw (KError)
+{
+    Debug::debug()->trace("Multipath::execute()");
+    AddBlacklistHandler handler(cout, m_exceptions);
+    m_parser.process(handler);
+}
+
+//}}}
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
diff -r b22c79ef4138 -r 5e1e087a4614 kdumptool/multipath.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kdumptool/multipath.h	Fri Mar 08 09:44:44 2013 +0100
@@ -0,0 +1,126 @@
+/*
+ * (c) 2013, Petr Tesarik <ptesarik@suse.cz>, SUSE LINUX Products 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 MULTIPATH_H
+#define MULTIPATH_H
+
+#include "subcommand.h"
+
+//{{{ MultipathConf -----------------------------------------------------------
+
+// This class implements a parser of multipath.conf
+// It is bug-for-bug compatible with the parser from multipath-tools 0.4.9.
+// Known quirks
+// * Last line is ignored if it is not terminated by CR or LF.
+// * White space at the beginning of a quoted string is swallowed.
+// * If a quoted string starts with '!' or '#', it is actually treated as
+//   a comment (so it is ignored and the rest of the line too).
+// * Keywords which are recognized as group-openers don't need an opening
+//   '{' character. However, if it is present, it must be on the same line.
+// * Closing brace may be followed by any garbage
+
+class MultipathConf {
+    public:
+        class Handler {
+	    public:
+	        /**
+		 * Process a parsed input line.
+		 *
+		 * @param[in] raw    raw line data, including the end-of-line
+		 *                   marker (CR or LF)
+		 * @param[in] tokens parsed tokens (one string per token)
+		 */
+	        virtual void process(const std::string &raw,
+				     StringVector &tokens)
+		= 0;
+        };
+
+    public:
+        MultipathConf(std::istream &input)
+        throw ();
+
+        /**
+         * Process input with a given handler
+         */
+        void process(class Handler &handler);
+
+    private:
+        std::istream &m_input;
+        std::string m_line;
+
+        bool readline(void);
+};
+
+//{{{ Multipath ---------------------------------------------------------------
+
+/**
+ * Subcommand to modify multipath.conf
+ */
+class Multipath : public Subcommand {
+
+    public:
+        /**
+         * Creates a new Multipath object.
+         */
+        Multipath()
+        throw ();
+
+    public:
+        /**
+         * Returns the name of the subcommand (multipath).
+         */
+        const char *getName() const
+        throw ();
+
+        /**
+         * Parses the command line options.
+         */
+        void parseCommandline(OptionParser *optionparser)
+        throw (KError);
+
+        /**
+         * That command does not need a config file.
+         */
+        bool needsConfigfile() const
+        throw ();
+
+        /**
+         * Executes the function.
+         *
+         * @throw KError on any error. No exception indicates success.
+         */
+        void execute()
+        throw (KError);
+
+    private:
+	/**
+	 * Lines to be added to blacklist_exceptions
+	 */
+	StringVector m_exceptions;
+
+	/**
+	 * Parser of multipath.conf
+	 */
+	MultipathConf m_parser;
+};
+
+//}}}
+
+#endif /* MULTIPATH_H */
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
diff -r b22c79ef4138 -r 5e1e087a4614 kdumptool/subcommand.cc
--- a/kdumptool/subcommand.cc	Wed Mar 06 10:16:25 2013 +0100
+++ b/kdumptool/subcommand.cc	Fri Mar 08 09:44:44 2013 +0100
@@ -31,6 +31,7 @@
 #include "read_ikconfig.h"
 #include "findkernel.h"
 #include "dumpconfig.h"
+#include "multipath.h"
 
 using std::map;
 using std::list;
@@ -129,6 +130,7 @@
     addSubcommand(new ReadIKConfig());
     addSubcommand(new FindKernel());
     addSubcommand(new DumpConfig());
+    addSubcommand(new Multipath());
 }
 
 // -----------------------------------------------------------------------------
