Add phosphor-msl-verify
phosphor-msl-verify is a oneshot application for basic
minimum ship level (MSL) verification.
https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Control/README.msl.md
Change-Id: Ifa036bb0a45255af7c7773bd910e83c64842d868
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/mslverify/.gitignore b/mslverify/.gitignore
new file mode 100644
index 0000000..0bd39bd
--- /dev/null
+++ b/mslverify/.gitignore
@@ -0,0 +1 @@
+phosphor-msl-verify
diff --git a/mslverify/Makefile.am b/mslverify/Makefile.am
new file mode 100644
index 0000000..4d79c0a
--- /dev/null
+++ b/mslverify/Makefile.am
@@ -0,0 +1,15 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+AM_CPPFLAGS = -iquote ${top_srcdir}
+
+sbin_PROGRAMS = phosphor-msl-verify
+
+phosphor_msl_verify_SOURCES = \
+ verify.cpp
+phosphor_msl_verify_LDADD = \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS)
+phosphor_msl_verify_CXXFLAGS = \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
diff --git a/mslverify/README.md b/mslverify/README.md
new file mode 100644
index 0000000..612561e
--- /dev/null
+++ b/mslverify/README.md
@@ -0,0 +1,10 @@
+phosphor-msl-verify
+
+phosphor-msl-verify is a "oneshot" application for basic minimum ship level
+[(MSL)](https://github.com/openbmc/phosphor-dbus-interfaces/xyz/openbmc_project/control/README.msl)
+verification.
+
+The application first determines if MSL validation is disabled and if not,
+searches the D-Bus object namespace for any MeetsMSL interfaces and exits with
+non-zero status if any inventory items implementing the interface are found
+that do not meet the MSL.
diff --git a/mslverify/util.hpp b/mslverify/util.hpp
new file mode 100644
index 0000000..2c26fc2
--- /dev/null
+++ b/mslverify/util.hpp
@@ -0,0 +1,225 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace util
+{
+namespace detail
+{
+namespace errors = sdbusplus::xyz::openbmc_project::Common::Error;
+} // namespace detail
+
+/** @brief Alias for PropertiesChanged signal callbacks. */
+template <typename ...T>
+using Properties = std::map<std::string, sdbusplus::message::variant<T...>>;
+
+namespace sdbusplus
+{
+
+/** @brief Get the bus connection. */
+static auto& getBus() __attribute__((pure));
+static auto& getBus()
+{
+ static auto bus = ::sdbusplus::bus::new_default();
+ return bus;
+}
+
+/** @brief Invoke a method. */
+template <typename ...Args>
+static auto callMethod(
+ ::sdbusplus::bus::bus& bus,
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& method,
+ Args&& ... args)
+{
+ auto reqMsg = bus.new_method_call(
+ busName.c_str(),
+ path.c_str(),
+ interface.c_str(),
+ method.c_str());
+ reqMsg.append(std::forward<Args>(args)...);
+ auto respMsg = bus.call(reqMsg);
+
+ if (respMsg.is_method_error())
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Failed to invoke DBus method.",
+ phosphor::logging::entry("PATH=%s", path.c_str()),
+ phosphor::logging::entry(
+ "INTERFACE=%s", interface.c_str()),
+ phosphor::logging::entry("METHOD=%s", method.c_str()));
+ phosphor::logging::elog<detail::errors::InternalFailure>();
+ }
+
+ return respMsg;
+}
+
+/** @brief Invoke a method. */
+template <typename ...Args>
+static auto callMethod(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& method,
+ Args&& ... args)
+{
+ return callMethod(
+ getBus(),
+ busName,
+ path,
+ interface,
+ method,
+ std::forward<Args>(args)...);
+}
+
+/** @brief Invoke a method and read the response. */
+template <typename Ret, typename ...Args>
+static auto callMethodAndRead(
+ ::sdbusplus::bus::bus& bus,
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& method,
+ Args&& ... args)
+{
+ ::sdbusplus::message::message respMsg =
+ callMethod<Args...>(
+ bus,
+ busName,
+ path,
+ interface,
+ method,
+ std::forward<Args>(args)...);
+ Ret resp;
+ respMsg.read(resp);
+ return resp;
+}
+
+/** @brief Invoke a method and read the response. */
+ template <typename Ret, typename ...Args>
+static auto callMethodAndRead(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& method,
+ Args&& ... args)
+{
+ return callMethodAndRead<Ret>(
+ getBus(),
+ busName,
+ path,
+ interface,
+ method,
+ std::forward<Args>(args)...);
+}
+
+
+/** @brief Get service from the mapper. */
+static auto getService(
+ ::sdbusplus::bus::bus& bus,
+ const std::string& path,
+ const std::string& interface)
+{
+ using namespace std::literals::string_literals;
+ using GetObject = std::map<std::string, std::vector<std::string>>;
+
+ auto mapperResp = callMethodAndRead<GetObject>(
+ bus,
+ "xyz.openbmc_project.ObjectMapper"s,
+ "/xyz/openbmc_project/object_mapper"s,
+ "xyz.openbmc_project.ObjectMapper"s,
+ "GetObject"s,
+ path,
+ GetObject::mapped_type{interface});
+
+ if (mapperResp.empty())
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Object not found.",
+ phosphor::logging::entry("PATH=%s", path.c_str()),
+ phosphor::logging::entry(
+ "INTERFACE=%s", interface.c_str()));
+ phosphor::logging::elog<detail::errors::InternalFailure>();
+ }
+ return mapperResp.begin()->first;
+}
+
+/** @brief Get a property without mapper lookup. */
+template <typename Property>
+static auto getProperty(
+ ::sdbusplus::bus::bus& bus,
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& property)
+{
+ using namespace std::literals::string_literals;
+
+ auto msg = callMethod(
+ bus,
+ busName,
+ path,
+ "org.freedesktop.DBus.Properties"s,
+ "Get"s,
+ interface,
+ property);
+ ::sdbusplus::message::variant<Property> value;
+ msg.read(value);
+ return value.template get<Property>();
+}
+
+/** @brief Get a property without mapper lookup. */
+template <typename Property>
+static auto getProperty(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& property)
+{
+ return getProperty<Property>(
+ getBus(),
+ busName,
+ path,
+ interface,
+ property);
+}
+
+/** @brief Get a property with mapper lookup. */
+template <typename Property>
+static auto getProperty(
+ ::sdbusplus::bus::bus& bus,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& property)
+{
+ return getProperty<Property>(
+ bus,
+ getService(bus, path, interface),
+ path,
+ interface,
+ property);
+}
+
+/** @brief Get a property with mapper lookup. */
+template <typename Property>
+static auto getProperty(
+ const std::string& path,
+ const std::string& interface,
+ const std::string& property)
+{
+ return getProperty<Property>(
+ getBus(),
+ path,
+ interface,
+ property);
+}
+
+} // namespace sdbusplus
+} // namespace util
diff --git a/mslverify/verify.cpp b/mslverify/verify.cpp
new file mode 100644
index 0000000..1595166
--- /dev/null
+++ b/mslverify/verify.cpp
@@ -0,0 +1,114 @@
+/**
+ * Copyright © 2017 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include "util.hpp"
+
+using namespace std::literals::string_literals;
+
+template <typename T>
+struct BusMeetsMSL
+{
+ std::string path;
+
+ BusMeetsMSL(const std::string& p)
+ :path(p) {}
+
+ auto operator()(const T& arg)
+ {
+ // Query each service hosting
+ // xyz.openbmc_project.Inventory.Decorator.MeetsMinimumShipLevel.
+
+ const auto& busName = arg.first;
+ return util::sdbusplus::getProperty<bool>(
+ busName,
+ path,
+ "xyz.openbmc_project.Inventory."
+ "Decorator.MeetsMinimumShipLevel"s,
+ "MeetsMinimumShipLevel"s);
+ }
+};
+
+template <typename T>
+struct PathMeetsMSL
+{
+ auto operator()(const T& arg)
+ {
+ // A given path in the mapper response is composed of
+ // a map of services/interfaces. Validate each service
+ // that hosts the MSL interface meets the MSL.
+
+ const auto& path = arg.first;
+ return std::all_of(
+ arg.second.begin(),
+ arg.second.end(),
+ BusMeetsMSL<typename decltype(arg.second)::value_type>(path));
+ }
+};
+
+int main(void)
+{
+ auto mslVerificationRequired =
+ util::sdbusplus::getProperty<bool>(
+ "/xyz/openbmc_project/control/minimum_ship_level_required"s,
+ "xyz.openbmc_project.Control.MinimumShipLevel"s,
+ "MinimumShipLevelRequired"s);
+
+ if (!mslVerificationRequired)
+ {
+ return 0;
+ }
+
+ // Obtain references to all objects hosting
+ // xyz.openbmc_project.Inventory.Decorator.MeetsMinimumShipLevel
+ // with a mapper subtree query. For each object, validate that
+ // the minimum ship level has been met.
+
+ using SubTreeType =
+ std::map<
+ std::string,
+ std::map<std::string, std::vector<std::string>>>;
+
+ auto subtree =
+ util::sdbusplus::callMethodAndRead<SubTreeType>(
+ "xyz.openbmc_project.ObjectMapper"s,
+ "/xyz/openbmc_project/object_mapper"s,
+ "xyz.openbmc_project.ObjectMapper"s,
+ "GetSubTree"s,
+ "/"s,
+ 0,
+ std::vector<std::string>{
+ "xyz.openbmc_project.Inventory"
+ ".Decorator.MeetsMinimumShipLevel"s});
+
+ auto result = std::all_of(
+ subtree.begin(),
+ subtree.end(),
+ PathMeetsMSL<SubTreeType::value_type>());
+
+ if (!result)
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "The physical system configuration does not "
+ "satisfy the minimum ship level.");
+
+ return 1;
+ }
+
+ return 0;
+}