blob: 416ffc9ed2906c9b4023e23a57aa571dbf74ccd5 [file] [log] [blame]
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <mapper.h>
#include <cassert>
#include <cctype>
#include <algorithm>
#include <phosphor-logging/log.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include "time-manager.hpp"
using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Common::Error;
std::map<std::string, TimeConfig::FUNCTOR> TimeConfig::iv_TimeParams = {
// TODO via openbmc/openbmc#668 - openbmc/openbmc#1770 is still work in
// progress at the time of writing this, so the use_dhcp_ntp is still using
// the old org.openbmc settings interfaces. The whole of time manager is
// anyway being rewritten to use new xyz.openbmc_project interfaces; as part
// of that switching to new setting interfaces is also covered.
{ "use_dhcp_ntp", std::make_tuple(&TimeConfig::getSystemSettings,
&TimeConfig::updateNetworkSettings)
}
};
namespace internal
{
namespace setting
{
/** @brief Convert d-bus enum string to native string. For eg, convert
* "xyz.openbmc_project.Time.Owner.Split" to "SPLIT".
*
* @param[in] value - setting enum string
*
* @return converted string
*/
inline auto dbusToNative(std::string&& value)
{
auto setting = std::move(value);
auto index = setting.find_last_of('.') + 1;
setting = setting.substr(index, setting.length() - index);
std::transform(setting.begin(), setting.end(), setting.begin(),
[](unsigned char c){ return std::toupper(c); });
return setting;
}
} // namespace setting
} // namespace internal
TimeConfig::TimeConfig() :
iv_dbus(nullptr),
iv_CurrTimeMode(timeModes::MANUAL),
iv_RequestedTimeMode(timeModes::MANUAL),
iv_CurrTimeOwner(timeOwners::BOTH),
iv_RequestedTimeOwner(timeOwners::BOTH),
iv_CurrDhcpNtp("yes"),
iv_SettingChangeAllowed(false),
iv_SplitModeChanged(false)
{
// Not really having anything to do here.
}
// Given a mode string, returns it's equivalent mode enum
TimeConfig::timeModes TimeConfig::getTimeMode(const char* timeMode)
{
// We are forcing the values to be in specific case and range
if (!strcmp(timeMode,"NTP"))
{
return timeModes::NTP;
}
else
{
return timeModes::MANUAL;
}
}
// Accepts a timeMode enum and returns it's string value
const char* TimeConfig::modeStr(TimeConfig::timeModes timeMode)
{
switch(timeMode)
{
case timeModes::NTP:
{
return "NTP";
}
case timeModes::MANUAL:
{
return "MANUAL";
}
}
assert(false);
return nullptr;
}
// Given a owner string, returns it's equivalent owner enum
TimeConfig::timeOwners TimeConfig::getTimeOwner(const char* timeOwner)
{
if (!strcmp(timeOwner,"BMC"))
{
return timeOwners::BMC;
}
else if (!strcmp(timeOwner,"HOST"))
{
return timeOwners::HOST;
}
else if (!strcmp(timeOwner,"SPLIT"))
{
return timeOwners::SPLIT;
}
else
{
return timeOwners::BOTH;
}
}
// Accepts a timeOwner enum and returns it's string value
const char* TimeConfig::ownerStr(timeOwners timeOwner)
{
switch(timeOwner)
{
case timeOwners::BMC:
{
return "BMC";
}
case timeOwners::HOST:
{
return "HOST";
}
case timeOwners::SPLIT:
{
return "SPLIT";
}
case timeOwners::BOTH:
{
return "BOTH";
}
}
assert(false);
return nullptr;
}
// Returns the busname that hosts objPath
std::unique_ptr<char> TimeConfig::getProvider(const char* objPath)
{
char *provider = nullptr;
mapper_get_service(iv_dbus, objPath, &provider);
return std::unique_ptr<char>(provider);
}
// Accepts a settings name and returns its value.
// for the variant of type 'string' now.
std::string TimeConfig::getSystemSettings(const char* key)
{
constexpr auto settingsObj = "/org/openbmc/settings/host0";
constexpr auto propertyIntf = "org.freedesktop.DBus.Properties";
constexpr auto hostIntf = "org.openbmc.settings.Host";
const char* value = nullptr;
std::string settingsVal {};
sd_bus_message* reply = nullptr;
std::cout <<"Getting System Settings: " << key << std::endl;
// Get the provider from object mapper
auto settingsProvider = getProvider(settingsObj);
if (!settingsProvider)
{
std::cerr << "Error Getting service for Settings" << std::endl;
return value;
}
auto r = sd_bus_call_method(iv_dbus,
settingsProvider.get(),
settingsObj,
propertyIntf,
"Get",
nullptr,
&reply,
"ss",
hostIntf,
key);
if (r < 0)
{
std::cerr <<"Error" << strerror(-r)
<<" reading system settings" << std::endl;
goto finish;
}
r = sd_bus_message_read(reply, "v", "s", &value);
if (r < 0)
{
std::cerr <<"Error " << strerror(-r)
<<" parsing settings data" << std::endl;
}
finish:
if (value)
{
settingsVal.assign(value);
}
reply = sd_bus_message_unref(reply);
return settingsVal;
}
// Reads value from /org/openbmc/control/power0
// This signature on purpose to plug into time parameter map
std::string TimeConfig::getPowerSetting(const char* key)
{
constexpr auto powerObj = "/org/openbmc/control/power0";
constexpr auto powerIntf = "org.openbmc.control.Power";
constexpr auto propertyIntf = "org.freedesktop.DBus.Properties";
int value = -1;
std::string powerValue {};
sd_bus_message* reply = nullptr;
std::cout <<"Reading Power Control key: " << key << std::endl;
// Get the provider from object mapper
auto powerProvider = getProvider(powerObj);
if (!powerProvider)
{
std::cerr <<" Error getting provider for Power Settings" << std::endl;
return powerValue;
}
auto r = sd_bus_call_method(iv_dbus,
powerProvider.get(),
powerObj,
propertyIntf,
"Get",
nullptr,
&reply,
"ss",
powerIntf,
key);
if (r < 0)
{
std::cerr <<"Error " << strerror(-r)
<< "reading: " << key << std::endl;
goto finish;
}
r = sd_bus_message_read(reply, "v", "i", &value);
if (r < 0)
{
std::cerr <<"Error " << strerror(-r)
<<" parsing " << key << "value" << std::endl;
// For maintenance
goto finish;
}
finish:
if (value != -1)
{
powerValue = std::to_string(value);
}
reply = sd_bus_message_unref(reply);
return powerValue;
}
// Updates .network file with UseNtp=
int TimeConfig::updateNetworkSettings(const std::string& useDhcpNtp)
{
constexpr auto networkObj = "/org/openbmc/NetworkManager/Interface";
constexpr auto networkIntf = "org.openbmc.NetworkManager";
std::cout << "use_dhcp_ntp = " << useDhcpNtp.c_str() << std::endl;
// If what we have already is what it is, then just return.
if (iv_CurrDhcpNtp == useDhcpNtp)
{
return 0;
}
// Get the provider from object mapper
auto networkProvider = getProvider(networkObj);
if (!networkProvider)
{
return -1;
}
auto r = sd_bus_call_method(iv_dbus,
networkProvider.get(),
networkObj,
networkIntf,
"UpdateUseNtpField",
nullptr,
nullptr,
"s",
useDhcpNtp.c_str());
if (r < 0)
{
std::cerr <<"Error " << strerror(-r)
<< " updating UseNtp" << std::endl;
}
else
{
std::cout <<"Successfully updated UseNtp=["
<< useDhcpNtp << "]" << std::endl;
r = writeData(cv_DhcpNtpFile, useDhcpNtp);
}
return 0;
}
// Reads the values from 'settingsd' and applies:
// 1) Time Mode
// 2) time Owner
// 3) UseNTP setting
// 4) Pgood
int TimeConfig::processInitialSettings(sd_bus* dbus)
{
// First call from TimeManager to config manager
iv_dbus = dbus;
auto timeOwnerFunctor = std::make_tuple(&TimeConfig::getTimeOwnerSetting,
&TimeConfig::updateTimeOwner);
iv_TimeParams.emplace(settings.timeOwner, std::move(timeOwnerFunctor));
auto timeSyncFunctor = std::make_tuple(
&TimeConfig::getTimeSyncMethodSetting,
&TimeConfig::updateTimeMode);
iv_TimeParams.emplace(settings.timeSyncMethod, std::move(timeSyncFunctor));
using namespace sdbusplus::bus::match::rules;
sdbusplus::bus::bus bus(iv_dbus);
settingsMatches.emplace_back(
bus,
propertiesChanged(settings.timeOwner, settings::timeOwnerIntf),
std::bind(std::mem_fn(&TimeConfig::settingsChanged),
this, std::placeholders::_1));
settingsMatches.emplace_back(
bus,
propertiesChanged(settings.timeSyncMethod, settings::timeSyncIntf),
std::bind(std::mem_fn(&TimeConfig::settingsChanged),
this, std::placeholders::_1));
// Read saved info like Who was the owner , what was the mode,
// what was the use_dhcp_ntp setting before etc..
auto r = readPersistentData();
if (r < 0)
{
std::cerr << "Error reading the data saved in flash."
<< std::endl;
return r;
}
// Now read whats in settings and apply if allowed.
for (auto& iter : iv_TimeParams)
{
// Get the settings value for various keys.
auto reader = std::get<READER>(iter.second);
auto value = (this->*reader)(iter.first.c_str());
if (!value.empty())
{
// Get the value for the key and validate.
auto updater = std::get<UPDATER>(iter.second);
auto r = (this->*updater)(value);
if (r < 0)
{
std::cerr << "Error setting up initial keys" << std::endl;
return r;
}
}
else
{
std::cerr << "key " << iter.first
<<" has no value: " << std::endl;
return -1;
}
}
// Now that we have taken care of consuming, push this as well
// so that we can use the same map for handling pgood change too.
auto readerUpdater = std::make_tuple(&TimeConfig::getPowerSetting,
&TimeConfig::processPgoodChange);
iv_TimeParams.emplace("pgood", readerUpdater);
return 0;
}
// This is called by Property Change handler on the event of
// receiving notification on property value change.
int TimeConfig::updatePropertyVal(const char* key, const std::string& value)
{
auto iter = iv_TimeParams.find(key);
if (iter != iv_TimeParams.end())
{
auto updater = std::get<UPDATER>(iter->second);
return (this->*updater)(value);
}
// Coming here indicates that we never had a matching key.
return -1;
}
// Called by sd_event when Properties are changed in Control/power0
// Interested in change to 'pgood'
int TimeConfig::processPgoodChange(const std::string& newPgood)
{
// Indicating that we are safe to apply any changes
if (!newPgood.compare("0"))
{
iv_SettingChangeAllowed = true;
std::cout <<"Changing time settings allowed now" << std::endl;
}
else
{
iv_SettingChangeAllowed = false;
std::cout <<"Changing time settings is *deferred* now" << std::endl;
}
// if we have had users that changed the time settings
// when we were not ready yet, do it now.
if (iv_RequestedTimeOwner != iv_CurrTimeOwner)
{
auto r = updateTimeOwner(ownerStr(iv_RequestedTimeOwner));
if (r < 0)
{
std::cerr << "Error updating new time owner" << std::endl;
return r;
}
std::cout << "New Owner is : "
<< ownerStr(iv_RequestedTimeOwner) << std::endl;
}
if (iv_RequestedTimeMode != iv_CurrTimeMode)
{
auto r = updateTimeMode(modeStr(iv_RequestedTimeMode));
if (r < 0)
{
std::cerr << "Error updating new time mode" << std::endl;
return r;
}
std::cout <<"New Mode is : "
<< modeStr(iv_RequestedTimeMode) << std::endl;
}
return 0;
}
// Manipulates time owner if the system setting allows it
int TimeConfig::updateTimeMode(const std::string& newModeStr)
{
auto r = 0;
iv_RequestedTimeMode = getTimeMode(newModeStr.c_str());
std::cout <<"Requested_Mode: " << newModeStr
<< " Current_Mode: " << modeStr(iv_CurrTimeMode)
<< std::endl;
if (iv_RequestedTimeMode == iv_CurrTimeMode)
{
std::cout << "Mode is already set to : "
<< newModeStr << std::endl;
return 0;
}
// Also, if the time owner is HOST, then we should not allow NTP.
// However, it may so happen that there are 2 pending requests, one for
// changing to NTP and other for changing owner to something not HOST.
// So check if there is a pending timeOwner change and if so, allow NTP
// if the current is HOST and requested is non HOST.
if (iv_CurrTimeOwner == timeOwners::HOST &&
iv_RequestedTimeOwner == timeOwners::HOST &&
iv_RequestedTimeMode == timeModes::NTP)
{
std::cout <<"Can not set mode to NTP with HOST as owner"
<< std::endl;
return 0;
}
if (iv_SettingChangeAllowed)
{
r = modifyNtpSettings(iv_RequestedTimeMode);
if (r < 0)
{
std::cerr << "Error changing TimeMode settings"
<< std::endl;
}
else
{
iv_CurrTimeMode = iv_RequestedTimeMode;
}
std::cout << "Current_Mode changed to: "
<< newModeStr << " :: " << modeStr(iv_CurrTimeMode) << std::endl;
// Need this when we either restart or come back from reset
r = writeData(cv_TimeModeFile, modeStr(iv_CurrTimeMode));
}
else
{
std::cout <<"Deferring update until system state allows it"
<< std::endl;
}
return r;
}
// Manipulates time owner if the system setting allows it
int TimeConfig::updateTimeOwner(const std::string& newOwnerStr)
{
int r = 0;
iv_RequestedTimeOwner = getTimeOwner(newOwnerStr.c_str());
// Needed when owner changes to HOST
std::string manualMode = "Manual";
// Needed by time manager to do some house keeping
iv_SplitModeChanged = false;
if (iv_RequestedTimeOwner == iv_CurrTimeOwner)
{
std::cout <<"Owner is already set to : "
<< newOwnerStr << std::endl;
return 0;
}
std::cout <<"Requested_Owner: " << newOwnerStr
<< " Current_Owner: " << ownerStr(iv_CurrTimeOwner)
<< std::endl;
if (iv_SettingChangeAllowed)
{
// If we are transitioning from SPLIT to something else,
// reset the host offset.
if (iv_CurrTimeOwner == timeOwners::SPLIT &&
iv_RequestedTimeOwner != timeOwners::SPLIT)
{
// Needed by time manager to do some house keeping
iv_SplitModeChanged = true;
}
iv_CurrTimeOwner = iv_RequestedTimeOwner;
std::cout << "Current_Owner is now: "
<< newOwnerStr << std::endl;
// HOST and NTP are exclusive
if (iv_CurrTimeOwner == timeOwners::HOST)
{
std::cout <<"Forcing the mode to MANUAL" << std::endl;
r = updateTimeMode(manualMode);
if (r < 0)
{
std::cerr << "Error forcing the mode to MANUAL" << std::endl;
return r;
}
}
// Need this when we either restart or come back from reset
r = writeData(cv_TimeOwnerFile, ownerStr(iv_CurrTimeOwner));
}
else
{
std::cout <<"Deferring update until system state allows it"
<< std::endl;
}
return r;
}
// Accepts the time mode and makes necessary changes to timedate1
int TimeConfig::modifyNtpSettings(const timeModes& newTimeMode)
{
auto ntpChangeOp = 0;
// Pass '1' -or- '0' to SetNTP method indicating Enable/Disable
ntpChangeOp = (newTimeMode == timeModes::NTP) ? 1 : 0;
std::cout <<"Applying NTP setting..." << std::endl;
auto r = sd_bus_call_method(iv_dbus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetNTP",
nullptr,
nullptr, // timedate1 does not return response
"bb",
ntpChangeOp, // '1' means Enable
0); // '0' meaning no policy-kit
if (r < 0)
{
std::cerr <<"Error: " << strerror(-r)
<< "changing time Mode" << std::endl;
}
else
{
std::cout << "SUCCESS. NTP setting is now: " <<
((ntpChangeOp) ? "Enabled" : "Disabled");
// TODO : https://github.com/openbmc/phosphor-time-manager/issues/1
if (ntpChangeOp)
{
r = system("systemctl restart systemd-timesyncd &");
}
else
{
r = system("systemctl stop systemd-timesyncd &");
}
}
return r;
}
// Reads all the saved data from the last run
int TimeConfig::readPersistentData()
{
// If we are coming back from a reset reload, then need to
// read what was the last successful Mode and Owner.
auto savedTimeMode = readData<std::string>(cv_TimeModeFile);
if (!savedTimeMode.empty())
{
iv_CurrTimeMode = getTimeMode(savedTimeMode.c_str());
std::cout <<"Last known time_mode: "
<< savedTimeMode.c_str() << std::endl;
}
auto savedTimeOwner = readData<std::string>(cv_TimeOwnerFile);
if (!savedTimeOwner.empty())
{
iv_CurrTimeOwner = getTimeOwner(savedTimeOwner.c_str());
std::cout <<"Last known time_owner: "
<< savedTimeOwner.c_str() << std::endl;
}
auto savedDhcpNtp = readData<std::string>(cv_DhcpNtpFile);
if (!savedDhcpNtp.empty())
{
iv_CurrDhcpNtp = savedDhcpNtp;
std::cout <<"Last known use_dhcp_ntp: "
<< iv_CurrDhcpNtp.c_str() << std::endl;
}
else
{
// This seems to be the first time.
std::cerr <<"Empty DhcpNtp string" << std::endl;
iv_CurrDhcpNtp = "yes";
}
// Doing this here to make sure 'pgood' is read and handled
// first before anything.
auto pgood = getPowerSetting("pgood");
if (!pgood.compare("0"))
{
std::cout << "Changing settings *allowed* now" << std::endl;
iv_SettingChangeAllowed = true;
}
return 0;
}
std::string TimeConfig::getTimeOwnerSetting(const char* path)
{
sdbusplus::bus::bus bus{iv_dbus};
auto method = bus.new_method_call(
settings.service(settings.timeOwner,
settings::timeOwnerIntf).c_str(),
path,
"org.freedesktop.DBus.Properties",
"Get");
method.append(settings::timeOwnerIntf, "TimeOwner");
auto reply = bus.call(method);
if (reply.is_method_error())
{
log<level::ERR>("Error in TimeOwner Get");
elog<InternalFailure>();
}
sdbusplus::message::variant<std::string> result;
reply.read(result);
// TODO via openbmc/openbmc#668 - because the old org.openbmc settings
// interfaces defined the time settings as strings, the code in this file
// is based around that fact. We use enums in the new settings interfaces,
// so code in this file can be changed to work with enums instead. That's
// being covered by the time manager rework (#668). For now, converting the
// settings to the string format that this object expects it to be.
// For eg, convert "xyz.openbmc_project.Time.Owner.Split" to "SPLIT".
auto setting = result.get<std::string>();
return internal::setting::dbusToNative(std::move(setting));
}
std::string TimeConfig::getTimeSyncMethodSetting(const char* path)
{
sdbusplus::bus::bus bus{iv_dbus};
auto method = bus.new_method_call(
settings.service(settings.timeSyncMethod,
settings::timeSyncIntf).c_str(),
path,
"org.freedesktop.DBus.Properties",
"Get");
method.append(settings::timeSyncIntf, "TimeSyncMethod");
auto reply = bus.call(method);
if (reply.is_method_error())
{
log<level::ERR>("Error in TimeSyncMethod Get");
elog<InternalFailure>();
}
sdbusplus::message::variant<std::string> result;
reply.read(result);
auto setting = result.get<std::string>();
return internal::setting::dbusToNative(std::move(setting));
}
int TimeConfig::settingsChanged(sdbusplus::message::message& msg)
{
using Interface = std::string;
using Property = std::string;
using Value = std::string;
using Properties = std::map<Property, sdbusplus::message::variant<Value>>;
Interface interface;
Properties properties;
msg.read(interface, properties);
auto path = msg.get_path();
for(const auto& p : properties)
{
auto setting = p.second.get<std::string>();
updatePropertyVal(path,
internal::setting::dbusToNative(std::move(setting)));
}
return 0;
}