From: Petr Tesarik <ptesarik@suse.com>
Date: Mon, 1 Mar 2021 12:24:52 +0100
Subject: Mount and device resolution using libmount and lsblk
References: bsc#1180513
Upstream: merged
Git-commit: cdd154fe371bc945b4a06dfbab70c813f2cef3ac

Implement parsing of mount tables and resolution of the underlying
block devices with lsblk, including special handling of /dev/root.

Signed-off-by: Petr Tesarik <ptesarik@suse.com>
References: jsc#SLE-13779
---
 CMakeLists.txt           |    5 
 kdumptool/CMakeLists.txt |    2 
 kdumptool/mounts.cc      |  415 +++++++++++++++++++++++++++++++++++++++++++++++
 kdumptool/mounts.h       |  269 ++++++++++++++++++++++++++++++
 4 files changed, 691 insertions(+)
 create mode 100644 kdumptool/mounts.cc
 create mode 100644 kdumptool/mounts.h

--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -130,6 +130,11 @@ ENDIF(NOT LIBSSL_FOUND)
 # libblkid
 pkg_check_modules(BLKID REQUIRED blkid)
 
+# libmount
+pkg_check_modules(LIBMOUNT REQUIRED mount)
+SET(EXTRA_LIBS ${EXTRA_LIBS} ${LIBMOUNT_LIBRARIES})
+INCLUDE_DIRECTORIES(${LIBMOUNT_INCLUDE_DIRS})
+
 #
 # Check for FADUMP
 #
--- a/kdumptool/CMakeLists.txt
+++ b/kdumptool/CMakeLists.txt
@@ -58,6 +58,8 @@ SET(COMMON_SRC
     socket.h
     ledblink.cc
     ledblink.h
+    mounts.cc
+    mounts.h
     progress.cc
     progress.h
     savedump.cc
--- /dev/null
+++ b/kdumptool/mounts.cc
@@ -0,0 +1,415 @@
+/*
+ * (c) 2014, Petr Tesarik <ptesarik@suse.de>, 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 <cctype>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <unistd.h>
+
+// for makedev() and friends:
+#include <sys/sysmacros.h>
+
+#include "global.h"
+#include "debug.h"
+#include "mounts.h"
+#include "process.h"
+
+using std::string;
+using std::ifstream;
+using std::istringstream;
+using std::ostringstream;
+
+//{{{ MountPoint ---------------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+MountPoint& MountPoint::operator=(MountPoint const& other)
+{
+    m_fs = other.m_fs;
+    m_ctarget = other.m_ctarget;
+    return *this;
+}
+
+// -----------------------------------------------------------------------------
+void MountPoint::setFS(MntFS const& fs)
+{
+    m_fs = fs;
+    m_ctarget.clear();
+}
+
+// -----------------------------------------------------------------------------
+FilePath const& MountPoint::canonicalTarget(void)
+{
+    if (m_ctarget.empty()) {
+        FilePath target = mnt_fs_get_target(m_fs);
+        m_ctarget = target.getCanonicalPath();
+    }
+    return m_ctarget;
+}
+
+//}}}
+//{{{ MountTable ---------------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+MountTable::iterator::iterator(MountTable const& table, int direction)
+    : m_tb(table.m_tb), m_mp(NULL)
+{
+    m_it = mnt_new_iter(MNT_ITER_FORWARD);
+    if (!m_it)
+        throw KError("Failed to initialize libmount iterator");
+}
+
+// -----------------------------------------------------------------------------
+MountTable::iterator::iterator(iterator const& other)
+    : m_tb(other.m_tb), m_mp(other.m_mp)
+{
+    m_it = mnt_new_iter(other.getDirection());
+    if (!m_it)
+        throw KError("Failed to initialize libmount iterator");
+    mnt_table_set_iter(m_tb, m_it, m_mp.fs());
+}
+
+// -----------------------------------------------------------------------------
+MountTable::iterator::~iterator()
+{
+    mnt_free_iter(m_it);
+}
+
+// -----------------------------------------------------------------------------
+MountTable::iterator& MountTable::iterator::operator=(iterator const& other)
+{
+    m_tb = other.m_tb;
+    m_mp = other.m_mp;
+    mnt_table_set_iter(m_tb, m_it, m_mp.fs());
+    return *this;
+}
+
+// -----------------------------------------------------------------------------
+MountTable::iterator& MountTable::iterator::operator++()
+{
+    struct libmnt_fs *fs;
+    if (mnt_table_next_fs(m_tb, m_it, &fs) < 0)
+        throw KError("Failed to iterate mount table");
+    m_mp.setFS(fs);
+    return *this;
+}
+
+// -----------------------------------------------------------------------------
+MountTable::MountTable(void)
+    : m_tb(mnt_new_table())
+{
+    if (!m_tb)
+        throw KError("Failed to initialize libmount table");
+}
+
+// -----------------------------------------------------------------------------
+MountTable::iterator MountTable::find_mount(FilePath const& path)
+{
+    Debug::debug()->trace("MountTable::find_mount(%s)", path.c_str());
+
+    FilePath cpath = path.getCanonicalPath();
+    size_t bestlen = 0;
+    iterator it(*this, MNT_ITER_FORWARD);
+    iterator best = it;
+    while (++it) {
+        if (it->isPseudoFS() || it->isSwapArea())
+            continue;
+
+        FilePath const& ctarget = it->canonicalTarget();
+        if (ctarget.length() >= bestlen &&
+            cpath.compare(0, ctarget.length(), ctarget) == 0) {
+            best = it;
+            bestlen = ctarget.length();
+        }
+    }
+    return best;
+}
+
+//}}}
+//{{{ KernelMountTable ---------------------------------------------------------
+
+const char KernelMountTable::_PATH_PROC_MOUNTS[] = "/proc/mounts";
+const char KernelMountTable::_PATH_PROC_MOUNTINFO[] = "/proc/self/mountinfo";
+
+// -----------------------------------------------------------------------------
+KernelMountTable::KernelMountTable(void)
+    : MountTable()
+{
+    const char *path = access(_PATH_PROC_MOUNTINFO, R_OK) == 0
+        ? _PATH_PROC_MOUNTINFO
+        : _PATH_PROC_MOUNTS;
+
+    if (mnt_table_parse_file(m_tb, path) != 0)
+        throw KError("Can't read kernel mount table");
+}
+
+//}}}
+//{{{ FstabMountTable ----------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+FstabMountTable::FstabMountTable(void)
+    : MountTable()
+{
+    if (mnt_table_parse_fstab(m_tb, NULL) != 0)
+        throw KError("Can't read fstab");
+}
+
+//}}}
+//{{{ PathMountPoint -----------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+PathMountPoint::PathMountPoint(FilePath const& path)
+    : MountPoint(NULL)
+{
+    Debug::debug()->trace("PathMountPoint::PathMountPoint(%s)", path.c_str());
+
+    MountTable::iterator mp_kernel =
+        KernelMountTable().find_mount(path);
+    MountTable::iterator mp_fstab =
+        FstabMountTable().find_mount(path);
+    MountTable::iterator *best;
+
+    if (!mp_fstab) {
+        Debug::debug()->dbg("%s: No fstab entry", path.c_str());
+        best = &mp_kernel;
+    } else if (!mp_kernel) {
+        Debug::debug()->dbg("%s: No kernel entry", path.c_str());
+        best = &mp_fstab;
+    } else {
+        Debug::debug()->dbg("%s: Kernel entry: %s, fstab entry: %s",
+                            path.c_str(),
+                            mp_kernel->canonicalTarget().c_str(),
+                            mp_fstab->canonicalTarget().c_str());
+
+        // both exist: choose the longer one, or fstab if same length
+        if (mp_kernel->canonicalTarget().length() >
+            mp_fstab->canonicalTarget().length()) {
+            best = &mp_kernel;
+        } else {
+            best = &mp_fstab;
+        }
+    }
+    MountPoint::operator=(**best);
+
+    if (*best) {
+        Debug::debug()->dbg("Filesystem on %s mounted at %s",
+                            source(), target());
+    } else {
+        Debug::debug()->dbg("No filesystem found!");
+    }
+}
+
+//}}}
+//{{{ PathResolver -------------------------------------------------------------
+
+const char PathResolver::_PATH_DEV_BLOCK_[] = "/dev/block/";
+const char PathResolver::_PATH_DEV_ROOT[] = "/dev/root";
+const char PathResolver::_PATH_PROC_CMDLINE[] = "/proc/cmdline";
+
+FilePath PathResolver::m_devroot;
+
+// -----------------------------------------------------------------------------
+PathResolver::PathResolver()
+{
+    m_cache = mnt_new_cache();
+    if (!m_cache)
+        throw KError("Cannot allocate file path cache");
+}
+
+// -----------------------------------------------------------------------------
+PathResolver::~PathResolver()
+{
+    mnt_free_cache(m_cache);
+}
+
+// -----------------------------------------------------------------------------
+bool PathResolver::_devroot_maj_min(void)
+{
+    size_t pos = m_devroot.find(':');
+    if (pos == string::npos)
+        return false;
+    KString devmajor(m_devroot, 0, pos);
+    if (!devmajor.isNumber())
+        return false;
+
+    KString devminor(m_devroot, pos + 1);
+    pos = devminor.find(':');
+    if (pos != string::npos) {
+        KString rest(devminor, pos + 1);
+        if (!rest.isNumber())
+            return false;
+        devminor.resize(pos);
+    }
+    if (!devminor.isNumber())
+        return false;
+
+    ostringstream os;
+    os << _PATH_DEV_BLOCK_ << devmajor << ':' << devminor;
+    m_devroot = os.str();
+    return true;
+}
+
+// -----------------------------------------------------------------------------
+bool PathResolver::_devroot_hexhex(void)
+{
+    dev_t dev;
+    if (!m_devroot.isHexNumber())
+        return false;
+
+    istringstream is(m_devroot);
+    is >> std::hex >> dev;
+
+    ostringstream os;
+    os << _PATH_DEV_BLOCK_ << major(dev) << ':' << minor(dev);
+    m_devroot = os.str();
+    return true;
+}
+
+// -----------------------------------------------------------------------------
+FilePath& PathResolver::system_root(void)
+{
+    Debug::debug()->trace("PathResolver::system_root()");
+
+    if (m_devroot.empty()) {
+        ifstream fin(_PATH_PROC_CMDLINE);
+        if (!fin)
+            throw KError(string("Unable to open ") + _PATH_PROC_CMDLINE + ".");
+
+        const string param = "root=";
+        while (fin.good()) {
+            bool in_quote = false;
+            KString s;
+            char c;
+            while (fin.get(c) && !(!in_quote && isspace(c))) {
+                s += c;
+                if (c == '"')
+                    in_quote = !in_quote;
+            }
+
+            // remove surrounding quotes
+            if (!s.empty() && *s.begin() == '"') {
+                s.erase(s.begin());
+                string::iterator last = s.end();
+                if (last != s.begin() && *--last == '"')
+                    s.erase(last);
+            }
+
+            if (s == "--")
+                break;
+
+            if (s.startsWith(param))
+                m_devroot.assign(s, param.length());
+        }
+        fin.close();
+
+        if (!m_devroot.empty()) {
+            Debug::debug()->dbg("Kernel commandline says root=%s",
+                                m_devroot.c_str());
+            if (!_devroot_maj_min() &&
+                !_devroot_hexhex()) {
+                m_devroot = resolve(m_devroot);
+            }
+            Debug::debug()->dbg("System root resolved as %s",
+                                m_devroot.c_str());
+        } else {
+            Debug::debug()->dbg("System root cannot be determined");
+            m_devroot = _PATH_DEV_ROOT;
+        }
+    }
+    return m_devroot;
+}
+
+// -----------------------------------------------------------------------------
+FilePath PathResolver::resolve(string const& spec)
+{
+    Debug::debug()->trace("PathResolver::resolve(%s)", spec.c_str());
+
+    char *path = mnt_resolve_spec(spec.c_str(), m_cache);
+    if (!path)
+        throw KError("Cannot resolve path: " + spec);
+    FilePath ret = path;
+    if (ret == _PATH_DEV_ROOT)
+        return system_root();
+    return ret;
+}
+
+//}}}
+//{{{ FilesystemTypeMap --------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+void FilesystemTypeMap::addPath(FilePath const& path)
+{
+    Debug::debug()->trace("FilesystemTypeMap::addPath(%s)", path.c_str());
+
+    PathMountPoint mnt(path);
+    if (mnt)
+        m_sources.insert(m_resolver.resolve(mnt.source()));
+}
+
+// -----------------------------------------------------------------------------
+StringStringMap& FilesystemTypeMap::devices(void)
+{
+    Debug::debug()->trace("FilesystemTypeMap::devices()");
+
+    ProcessFilter p;
+
+    StringVector args;
+    static const char *const opts[] = {
+        "--raw", "--noheadings", "--inverse",
+        "--output", "FSTYPE,PATH",
+        NULL
+    };
+    for (const char *const *p = opts; *p; ++p)
+        args.push_back(*p);
+    args.insert(args.end(), m_sources.begin(), m_sources.end());
+
+    ostringstream stdoutStream, stderrStream;
+    p.setStdout(&stdoutStream);
+    p.setStderr(&stderrStream);
+    int ret = p.execute("lsblk", args);
+    if (ret != 0) {
+        KString error = stderrStream.str();
+        throw KError("lsblk failed: " + error.trim());
+    }
+
+    KString out = stdoutStream.str();
+    size_t pos = 0;
+    while (pos < out.length()) {
+        size_t end = out.find_first_of("\r\n", pos);
+        size_t sep = out.find(' ', pos);
+        if (sep < end) {
+            string type = out.substr(pos, sep - pos);
+            string path = out.substr(sep + 1, end - sep - 1);
+            m_devices[path] = type;
+
+            Debug::debug()->dbg("Device %s type %s",
+                                path.c_str(), type.c_str());
+        }
+        pos = out.find_first_not_of("\r\n", end);
+    }
+
+    return m_devices;
+}
+
+//}}}
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
--- /dev/null
+++ b/kdumptool/mounts.h
@@ -0,0 +1,269 @@
+/*
+ * (c) 2021, Petr Tesarik <ptesarik@suse.de>, SUSE Linux Software Solutions 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 MOUNTS_H
+#define MOUNTS_H
+
+#include <set>
+
+#include "global.h"
+#include "fileutil.h"
+
+#include <libmount.h>
+
+//{{{ MntFS --------------------------------------------------------------------
+
+/**
+ * Refcounting wrapper for struct libmnt_fs*
+ */
+class MntFS {
+        struct libmnt_fs *m_fs;
+
+    public:
+        MntFS(struct libmnt_fs *fs)
+            : m_fs(fs)
+        { mnt_ref_fs(m_fs); }
+
+        MntFS(MntFS const& other)
+            : m_fs(other.m_fs)
+        { mnt_ref_fs(m_fs); }
+
+        ~MntFS()
+        { mnt_unref_fs(m_fs); }
+
+        /**
+         * Typecast to struct libmnt_fs*, so instances of this class
+         * can be used as arguments to libmount functions.
+         */
+        operator struct libmnt_fs*() const
+        { return m_fs; }
+
+        /**
+         * Assign a new value, taking care of rerence counts.
+         */
+        MntFS& operator=(MntFS const& other)
+        {
+            mnt_unref_fs(m_fs);
+            m_fs = other.m_fs;
+            mnt_ref_fs(m_fs);
+            return *this;
+        }
+};
+
+//}}}
+
+//{{{ MntTable -----------------------------------------------------------------
+
+/*
+ * Refcounting wrapper for struct libmnt_table*
+ */
+class MntTable {
+        struct libmnt_table *m_tb;
+
+    public:
+        MntTable(struct libmnt_table *tb)
+            : m_tb(tb)
+        { mnt_ref_table(m_tb); }
+
+        MntTable(MntTable const& other)
+            : m_tb(other.m_tb)
+        { mnt_ref_table(m_tb); }
+
+        ~MntTable()
+        { mnt_unref_table(m_tb); }
+
+        /**
+         * Typecast to struct libmnt_table*, so instances of this class
+         * can be used as arguments to libmount functions.
+         */
+        operator struct libmnt_table*() const
+        { return m_tb; }
+
+        /**
+         * Assign a new value, taking care of rerence counts.
+         */
+        MntTable& operator=(MntTable const& other)
+        {
+            mnt_unref_table(m_tb);
+            m_tb = other.m_tb;
+            mnt_ref_table(m_tb);
+            return *this;
+        }
+};
+
+//}}}
+
+//{{{ MountPoint ---------------------------------------------------------------
+
+class MountPoint {
+    protected:
+        MntFS m_fs;
+
+    private:
+        FilePath m_ctarget;
+
+    public:
+        MountPoint(const MntFS& fs)
+            : m_fs(fs)
+        { }
+
+        MountPoint(MountPoint const& other)
+            : m_fs(other.m_fs), m_ctarget(other.m_ctarget)
+        { }
+
+        operator bool() const
+        { return (bool)m_fs; }
+
+        MountPoint& operator=(MountPoint const& other);
+
+        MntFS& fs(void)
+        { return m_fs; }
+
+        void setFS(MntFS const& fs);
+
+        FilePath const& canonicalTarget(void);
+
+        bool isNetFS(void)
+        { return mnt_fs_is_netfs(m_fs); }
+
+        bool isPseudoFS(void)
+        { return mnt_fs_is_pseudofs(m_fs); }
+
+        bool isSwapArea(void)
+        { return mnt_fs_is_swaparea(m_fs); }
+
+        const char *source(void)
+        { return mnt_fs_get_source(m_fs); }
+
+        const char *target(void)
+        { return mnt_fs_get_target(m_fs); }
+};
+
+//}}}
+//{{{ MountTable ---------------------------------------------------------------
+
+class MountTable {
+    protected:
+        MntTable m_tb;
+
+    public:
+        class iterator {
+            struct libmnt_iter *m_it;
+            MntTable m_tb;
+            MountPoint m_mp;
+
+        public:
+            iterator(MountTable const& table, int direction);
+            iterator(iterator const& other);
+            ~iterator();
+
+            iterator& operator=(iterator const& other);
+
+            operator bool() const
+            { return (bool)m_mp; }
+
+            iterator& operator++();
+            iterator operator++(int)
+            { iterator temp = *this; ++(*this); return temp; }
+
+            MountPoint const& operator*() const { return m_mp; }
+            MountPoint* operator->() { return &m_mp; }
+
+            int getDirection(void) const
+            { return mnt_iter_get_direction(m_it); }
+        };
+
+        MountTable(void);
+
+        iterator find_mount(FilePath const& path);
+};
+
+//}}}
+//{{{ KernelMountTable ---------------------------------------------------------
+
+class KernelMountTable : public MountTable {
+    public:
+        KernelMountTable(void);
+
+    private:
+        static const char _PATH_PROC_MOUNTS[];
+        static const char _PATH_PROC_MOUNTINFO[];
+};
+
+//}}}
+//{{{ FstabMountTable ----------------------------------------------------------
+
+class FstabMountTable : public MountTable {
+    public:
+        FstabMountTable(void);
+};
+
+//}}}
+//{{{ PathMountPoint -----------------------------------------------------------
+
+class PathMountPoint : public MountPoint {
+    public:
+        PathMountPoint(FilePath const& path);
+        PathMountPoint(PathMountPoint const &other)
+            : MountPoint(other)
+        { }
+};
+
+//}}}
+//{{{ PathResolver -------------------------------------------------------------
+
+class PathResolver {
+    protected:
+        struct libmnt_cache *m_cache;
+
+    public:
+        PathResolver();
+        ~PathResolver();
+
+        FilePath resolve(std::string const& spec);
+        FilePath& system_root(void);
+
+    private:
+        static const char _PATH_DEV_BLOCK_[];
+        static const char _PATH_DEV_ROOT[];
+        static const char _PATH_PROC_CMDLINE[];
+        static FilePath m_devroot;
+
+        bool _devroot_maj_min(void);
+        bool _devroot_hexhex(void);
+};
+
+//}}}
+//{{{ FilesystemTypeMap --------------------------------------------------------
+
+class FilesystemTypeMap {
+    protected:
+        PathResolver m_resolver;
+        std::set<std::string> m_sources;
+        StringStringMap m_devices;
+
+    public:
+        void addPath(FilePath const& path);
+        StringStringMap& devices(void);
+};
+
+//}}}
+
+#endif /* MOUNTS_H */
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
