Add TimeManager daemon to openbmc
Time Manager daemon supporting NTP and MANUAL modes and below policy
*) BMC owns the time
*) HOST owns the time
*) SPLIT clock and HOST's time is maintained as an offset to BMC
*) BOTH the BMC and HOST own the clock
Change-Id: I81701b67731aa4b37d6926d5b93d397fea96e086
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
diff --git a/time-manager.cpp b/time-manager.cpp
new file mode 100644
index 0000000..bde2e0d
--- /dev/null
+++ b/time-manager.cpp
@@ -0,0 +1,912 @@
+#define _XOPEN_SOURCE
+#include <chrono>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <array>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/timerfd.h>
+#include <systemd/sd-event.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-daemon.h>
+#include "time-register.hpp"
+#include "time-manager.hpp"
+
+// Neeed to do this since its not exported outside of the kernel.
+// Refer : https://gist.github.com/lethean/446cea944b7441228298
+#ifndef TFD_TIMER_CANCEL_ON_SET
+#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
+#endif
+
+// Needed to make sure timerfd does not misfire eventhough we set CANCEL_ON_SET
+#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
+
+// Used in time-register.c
+extern sd_bus_vtable timeServicesVtable[];
+
+Time::Time(const TimeConfig& timeConfig) : config(timeConfig)
+{
+ // Nothing to do here
+}
+
+BmcTime::BmcTime(const TimeConfig& timeConfig) : Time(timeConfig)
+{
+ // Nothing to do here
+}
+
+HostTime::HostTime(const TimeConfig& timeConfig,
+ const std::chrono::microseconds& hostOffset)
+ : Time(timeConfig),
+ iv_Offset(hostOffset)
+{
+ // Nothing to do here
+}
+
+TimeManager::TimeManager() :
+ iv_HostOffset(std::chrono::microseconds(0)),
+ iv_UptimeUsec(std::chrono::microseconds(0)),
+ iv_EventSource(nullptr),
+ iv_Event(nullptr)
+{
+ assert(setupTimeManager() >= 0);
+}
+
+// Needed to be standalone extern "C" to register
+// as a callback routine with sd_bus_vtable
+int GetTime(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return tmgr->getTime(m, userdata, retError);
+}
+
+int SetTime(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return tmgr->setTime(m, userdata, retError);
+}
+
+// Property reader
+int getCurrTimeModeProperty(sd_bus* bus, const char* path,
+ const char* interface, const char* property,
+ sd_bus_message* m, void* userdata,
+ sd_bus_error* error)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return sd_bus_message_append(m, "s",
+ TimeConfig::modeStr(tmgr->config.getCurrTimeMode()));
+}
+
+int getCurrTimeOwnerProperty(sd_bus* bus, const char* path,
+ const char* interface, const char* property,
+ sd_bus_message* m, void* userdata,
+ sd_bus_error* error)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return sd_bus_message_append(m, "s",
+ TimeConfig::ownerStr(tmgr->config.getCurrTimeOwner()));
+}
+
+int getReqTimeModeProperty(sd_bus* bus, const char* path,
+ const char* interface, const char* property,
+ sd_bus_message* m, void* userdata,
+ sd_bus_error* error)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return sd_bus_message_append(m, "s",
+ TimeConfig::modeStr(tmgr->config.getRequestedTimeMode()));
+}
+
+int getReqTimeOwnerProperty(sd_bus* bus, const char* path,
+ const char* interface, const char* property,
+ sd_bus_message* m, void* userdata,
+ sd_bus_error* error)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ return sd_bus_message_append(m, "s",
+ TimeConfig::ownerStr(tmgr->config.getRequestedTimeOwner()));
+}
+
+int TimeManager::getTime(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ const char* target = nullptr;
+
+ // Extract the target and call respective GetTime
+ auto r = sd_bus_message_read(m, "s", &target);
+ if (r < 0)
+ {
+ std::cerr << "Error:" << strerror(-r)
+ <<" reading user time" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_IO_ERROR, "Error reading input");
+
+ return sd_bus_reply_method_error(m, retError);
+ }
+
+ if (!strcasecmp(target, "bmc"))
+ {
+ auto time = BmcTime(config);
+ return time.getTime(m, retError);
+ }
+ else if (!strcasecmp(target, "host"))
+ {
+ auto time = HostTime(config, iv_HostOffset);
+ return time.getTime(m, retError);
+ }
+ else
+ {
+ std::cerr << "Error:" << strerror(-r)
+ <<" Invalid Target" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_IO_ERROR, "Valid targets are BMC or HOST");
+
+ return sd_bus_reply_method_error(m, retError);
+ }
+}
+
+int TimeManager::setTime(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ long long int timeInUsec {};
+
+ const char* target = nullptr;
+ auto r = sd_bus_message_read(m, "s", &target);
+ if (r < 0)
+ {
+ std::cerr << "Error:" << strerror(-r)
+ <<" reading user time" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_IO_ERROR, "Error reading input");
+
+ return sd_bus_reply_method_error(m, retError);
+ }
+
+ if (!strcasecmp(target, "bmc"))
+ {
+ auto time = BmcTime(config);
+ auto r = time.setTime(m, retError);
+ if (r < 0)
+ {
+ // This would have the error populated
+ return sd_bus_reply_method_error(m, retError);
+ }
+ }
+ else if (!strcasecmp(target, "host"))
+ {
+ auto time = HostTime(config, iv_HostOffset);
+
+ auto r = time.setTime(m, retError);
+ if (r < 0)
+ {
+ // This would have the error populated
+ return sd_bus_reply_method_error(m, retError);
+ }
+
+ if (config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT)
+ {
+ iv_HostOffset = time.getChangedOffset();
+ r = config.writeData<decltype(iv_HostOffset.count())>
+ (cv_HostOffsetFile,
+ iv_HostOffset.count());
+ if (r < 0)
+ {
+ // probably does not make sense to crash on these..
+ // The next NTP sync will set things right.
+ std::cerr << "Error saving host_offset: "
+ << iv_HostOffset.count() << std::endl;
+ }
+ }
+ }
+ return sd_bus_reply_method_return(m, "i", 0);
+}
+
+int Time::setTimeOfDay(const std::chrono::microseconds& timeOfDayUsec)
+{
+ // These 2 are for bypassing some policy
+ // checking in the timedate1 service
+ auto relative = false;
+ auto interactive = false;
+
+ return sd_bus_call_method(config.getDbus(),
+ "org.freedesktop.timedate1",
+ "/org/freedesktop/timedate1",
+ "org.freedesktop.timedate1",
+ "SetTime",
+ nullptr,
+ nullptr, // timedate1 does not return response
+ "xbb",
+ (int64_t)timeOfDayUsec.count(), //newTimeUsec,
+ relative, // Time in absolute seconds since epoch
+ interactive); // bypass polkit checks
+}
+
+// Common routine for BMC and HOST Get Time operations
+std::chrono::microseconds Time::getBaseTime()
+{
+ auto currBmcTime = std::chrono::system_clock::now();
+ return std::chrono::duration_cast<std::chrono::microseconds>
+ (currBmcTime.time_since_epoch());
+}
+
+// Accepts the time in microseconds and converts to Human readable format.
+std::string Time::convertToStr(const std::chrono::microseconds& timeInUsec)
+{
+ using namespace std::chrono;
+
+ // Convert this to number of seconds;
+ auto timeInSec = duration_cast<seconds>(microseconds(timeInUsec));
+ auto time_T = static_cast<std::time_t>(timeInSec.count());
+
+ std::ostringstream timeFormat {};
+ timeFormat << std::put_time(std::gmtime(&time_T), "%c %Z");
+
+ auto timeStr = timeFormat.str();
+ std::cout << timeStr.c_str() << std::endl;
+ return timeStr;
+}
+
+// Reads timeofday and returns time string
+// and also number of microseconds.
+// Ex : Tue Aug 16 22:49:43 2016
+int BmcTime::getTime(sd_bus_message *m, sd_bus_error *retError)
+{
+ std::cout << "Request to get BMC time: ";
+
+ // Get BMC time
+ auto timeInUsec = getBaseTime();
+ auto timeStr = convertToStr(timeInUsec);
+ return sd_bus_reply_method_return(
+ m, "sx", timeStr.c_str(), (uint64_t)timeInUsec.count());
+}
+
+// Designated to be called by IPMI_GET_SEL time from host
+int HostTime::getTime(sd_bus_message *m, sd_bus_error *retError)
+{
+ using namespace std::chrono;
+
+ std::cout << "Request to get HOST time" << std::endl;
+
+ // Get BMC time and add Host's offset
+ // Referencing the iv_hostOffset of TimeManager object
+ auto timeInUsec = getBaseTime();
+ auto hostTime = timeInUsec.count() + iv_Offset.count();
+
+ auto timeStr = convertToStr(duration_cast<microseconds>
+ (microseconds(hostTime)));
+
+ std::cout << " Host_time_str: [ " << timeStr
+ << " ] host_time usec: [ " << hostTime
+ << " ] host_offset: [ " << iv_Offset.count()
+ << " ] " << std::endl;
+
+ return sd_bus_reply_method_return(m, "sx", timeStr.c_str(),
+ (uint64_t)hostTime);
+ return 0;
+}
+
+// Gets the time string and verifies if it conforms to format %Y-%m-%d %H:%M:%S
+// and then sets the BMC time. If the input time string does not conform to the
+// format, an error message is returned.
+int BmcTime::setTime(sd_bus_message *m, sd_bus_error *retError)
+{
+ tm userTm {};
+ const char* userTimeStr = nullptr;
+
+ std::cout << "Request to set BMC time" << std::endl;
+
+ std::cout << "Curr_Mode: " << TimeConfig::modeStr(config.getCurrTimeMode())
+ << " Curr_Owner: " << TimeConfig::ownerStr(config.getCurrTimeOwner())
+ << std::endl;
+
+ if (config.getCurrTimeMode() == TimeConfig::timeModes::NTP)
+ {
+ std::cerr << "Can not set time. Mode is NTP" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Current Mode is NTP");
+
+ return -1;
+ }
+
+ if(config.getCurrTimeOwner() == TimeConfig::timeOwners::HOST)
+ {
+ std::cerr << "Can not set time. Owner is HOST" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Current owner is HOST");
+
+ return -1;
+ }
+
+ auto r = sd_bus_message_read(m, "s", &userTimeStr);
+ if (r < 0)
+ {
+ std::cerr << "Error:" << strerror(-r)
+ <<" reading user time" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_IO_ERROR, "Error reading input");
+ return r;
+ }
+
+ std::cout <<" BMC TIME : " << userTimeStr << std::endl;
+
+ // Convert the time string into tm structure
+ std::istringstream timeString {};
+ timeString.str(userTimeStr);
+ timeString >> std::get_time(&userTm, "%Y-%m-%d %H:%M:%S");
+ if (timeString.fail())
+ {
+ std::cerr << "Error: Incorrect time format" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_INVALID_ARGS, "Incorrect time format");
+ return -1;
+ }
+
+ // Convert the time structure into number of
+ // seconds maintained in GMT. Followed the same that is in
+ // systemd/timedate1
+ auto timeOfDay = timegm(&userTm);
+ if (timeOfDay < 0)
+ {
+ std::cerr <<"Error converting tm to seconds" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Error converting tm to seconds");
+ return -1;
+ }
+
+ // Set REALTIME and also update hwclock
+ auto timeInUsec = std::chrono::microseconds(
+ std::chrono::seconds(timeOfDay));
+ r = setTimeOfDay(timeInUsec);
+ if (r < 0)
+ {
+ std::cerr <<"Error: " << strerror(-r)
+ << "setting time on BMC" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Error setting time on BMC");
+ }
+ return r < 0 ? r : 0;
+}
+
+// Gets the time string from IPMI ( which is currently in seconds since epoch )
+// and then sets the BMC time / adjusts the offset depending on current owner
+// policy.
+int HostTime::setTime(sd_bus_message *m, sd_bus_error *retError)
+{
+ using namespace std::chrono;
+
+ std::cout << "Request to SET Host time" << std::endl;
+
+ std::cout << "Curr_Mode: " << TimeConfig::modeStr(config.getCurrTimeMode())
+ << "Curr_Owner: " << TimeConfig::ownerStr(config.getCurrTimeOwner())
+ << "host_offset: " << iv_Offset.count() << std::endl;
+
+ if (config.getCurrTimeOwner() == TimeConfig::timeOwners::BMC)
+ {
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Current Owner is BMC");
+ return -1;
+ }
+
+ const char* newHostTime = nullptr;
+ auto r = sd_bus_message_read(m, "s", &newHostTime);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "reading host time" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_IO_ERROR, "Error reading input");
+ return -1;
+ }
+
+ // We need to convert the string input to decimal.
+ auto hostTimeSec = std::stol(std::string(newHostTime));
+
+ // And then to microseconds
+ auto hostTimeUsec = duration_cast<microseconds>(seconds(hostTimeSec));
+ std::cout << "setHostTime: HostTimeInUSec: "
+ << hostTimeUsec.count() << std::endl;
+
+ if (config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT)
+ {
+ // Adjust the host offset
+ auto bmcTimeUsec = duration_cast<microseconds>
+ (system_clock::now().time_since_epoch());
+
+ // We are not doing any time settings in BMC
+ std::cout << "Updated: host_time: [ " << hostTimeUsec.count()
+ << " ] host_offset: [ " << iv_Offset.count()
+ << " ] " << std::endl;
+
+ // Communicate the offset back to manager to update needed.
+ changedOffset = hostTimeUsec - bmcTimeUsec;
+
+ return 0;
+ }
+
+ // We are okay to update time in as long as BMC is not the owner
+ r = setTimeOfDay(hostTimeUsec);
+ if (r < 0)
+ {
+ std::cerr <<"Error: " << strerror(-r)
+ << "setting HOST time" << std::endl;
+ *retError = SD_BUS_ERROR_MAKE_CONST(
+ SD_BUS_ERROR_FAILED, "Error setting time");
+ }
+
+ return r < 0 ? r : 0;
+}
+
+// Gets called into by sd_event on an activity seen on sd_bus
+int TimeManager::processSdBusMessage(sd_event_source* es, int fd,
+ uint32_t revents, void* userdata)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ auto r = sd_bus_process(tmgr->getTimeBus(), nullptr);
+ if (r < 0)
+ {
+ std::cerr <<"Error: " << strerror(-r)
+ <<" processing sd_bus message:" << std::endl;
+ }
+ return r;
+}
+
+// Gets called into by sd_event on any time SET event
+int TimeManager::processTimeChange(sd_event_source* es, int fd,
+ uint32_t revents, void* userdata)
+{
+ using namespace std::chrono;
+ std::cout << "BMC time changed" << std::endl;
+
+ auto tmgr = static_cast<TimeManager*>(userdata);
+
+ std::array<char, 64> time {};
+
+ // We are not interested in the data here. Need to read time again .
+ // So read until there is something here in the FD
+ while (read(fd, time.data(), time.max_size()) > 0);
+
+ std::cout <<" Curr_Mode: " << TimeConfig::modeStr(tmgr->config.getCurrTimeMode())
+ << " Curr_Owner : " << TimeConfig::ownerStr(tmgr->config.getCurrTimeOwner())
+ << " Host_Offset: " << tmgr->getHostOffset().count() << std::endl;
+
+ // Read the current BMC time and adjust the
+ // host time offset if the mode is SPLIT
+ if (tmgr->config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT)
+ {
+ // Delta between REAL and Monotonic.
+ auto uptimeUsec = duration_cast<microseconds>
+ (system_clock::now().time_since_epoch() -
+ steady_clock::now().time_since_epoch());
+
+ auto deltaTimeUsec = uptimeUsec - tmgr->getUptimeUsec();
+
+ // If the BMC time goes backwards, then - of - will handle that.
+ auto newHostOffset = tmgr->getHostOffset() - deltaTimeUsec;
+ tmgr->updateHostOffset(newHostOffset);
+
+ std::cout << " UPDATED HOST_OFFSET: "
+ << tmgr->getHostOffset().count() << std::endl;
+ tmgr->updateUptimeUsec(uptimeUsec);
+
+ // Persist this
+ auto r = tmgr->config.writeData<decltype(tmgr->getHostOffset().count())>
+ (TimeManager::cv_HostOffsetFile,
+ tmgr->getHostOffset().count());
+ if (r < 0)
+ {
+ std::cerr << "Error saving host_offset: "
+ << tmgr->getHostOffset().count() << std::endl;
+ return r;
+ }
+ std::cout << " Updated: Host_Offset: "
+ << tmgr->getHostOffset().count() << std::endl;
+ }
+ return 0;
+}
+
+// Resets iv_HostOffset. Needed when we move away from SPLIT.
+int TimeManager::resetHostOffset()
+{
+ iv_HostOffset = std::chrono::microseconds(0);
+ auto r = config.writeData<decltype(iv_HostOffset.count())>
+ (cv_HostOffsetFile,
+ iv_HostOffset.count());
+ config.updateSplitModeFlag(false);
+ return r;
+}
+
+// Called by sd_event when Pgood is changed in Power
+int TimeManager::processPgoodChange(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ const char* key = nullptr;
+ const char* value = nullptr;
+
+ auto newPgood = -1;
+ auto r = 0;
+
+ std::cout <<" PGOOD has changed.." << std::endl;
+
+ // input data is "sa{sv}as" and we are just interested in a{sv}
+ r = sd_bus_message_skip(m, "s");
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r) <<
+ "skipping interface name in data" << std::endl;
+ return r;
+ }
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<"entering the dictionary" << std::endl;
+ return r;
+ }
+
+ while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY,
+ "sv")) > 0)
+ {
+ r = sd_bus_message_read(m, "s", &key);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<" reading the key from dict" << std::endl;
+ // Can not continue here since the next
+ // enter would result in error anyway
+ return r;
+ }
+
+ if (!strcmp(key, "pgood"))
+ {
+ r = sd_bus_message_read(m, "v", "i", &newPgood);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "reading pgood" << std::endl;
+ return r;
+ }
+ r = tmgr->config.updatePropertyVal(key, std::to_string(newPgood));
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "processing pgood" << std::endl;
+ return r;
+ }
+ }
+ else
+ {
+ sd_bus_message_skip(m, "v");
+ }
+ }
+ return 0;
+}
+
+// Called by sd_event when Properties are changed in settingsd.
+// Interested in changes to 'timeMode', 'timeOwner' and 'use_dhcp_ntp'
+int TimeManager::processPropertyChange(sd_bus_message* m, void* userdata,
+ sd_bus_error* retError)
+{
+ auto tmgr = static_cast<TimeManager*>(userdata);
+ const char* key = nullptr;
+ const char* value = nullptr;
+ auto r = 0;
+
+ std::cout <<" User Settings have changed.." << std::endl;
+
+ // input data is "sa{sv}as" and we are just interested in a{sv}
+ r = sd_bus_message_skip(m, "s");
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r) <<
+ "skipping interface name in data" << std::endl;
+ goto finish;
+ }
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<"entering the dictionary" << std::endl;
+ goto finish;
+ }
+
+ while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY,
+ "sv")) > 0)
+ {
+ r = sd_bus_message_read(m, "s", &key);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<" reading the key from dict" << std::endl;
+ // Can not continue here since the next
+ // enter would result in error anyway
+ goto finish;
+ }
+
+ if (!strcmp(key, "time_mode"))
+ {
+ r = sd_bus_message_read(m, "v", "s", &value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "reading timeMode" << std::endl;
+ goto finish;
+ }
+ r = tmgr->config.updatePropertyVal(key, value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "processing timeMode" << std::endl;
+ goto finish;
+ }
+ }
+ else if (!strcmp(key, "time_owner"))
+ {
+ r = sd_bus_message_read(m, "v", "s", &value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "reading timeOwner" << std::endl;
+ goto finish;
+ }
+ r = tmgr->config.updatePropertyVal(key, value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "processing time_owner" << std::endl;
+ goto finish;
+ }
+ else if (tmgr->config.isSplitModeChanged())
+ {
+ // Must have been a change away from mode SPLIT
+ tmgr->resetHostOffset();
+ }
+ }
+ else if (!strcmp(key, "use_dhcp_ntp"))
+ {
+ r = sd_bus_message_read(m, "v", "s", &value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<"reading use_dhcp_ntp" << std::endl;
+ goto finish;
+ }
+ r = tmgr->config.updatePropertyVal(key, value);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "processing dhcp_ntp" << std::endl;
+ goto finish;
+ }
+ }
+ else
+ {
+ sd_bus_message_skip(m, "v");
+ }
+ }
+finish:
+ return r;
+}
+
+// Sets up callback handlers for activities on :
+// 1) user request on SD_BUS
+// 2) Time change
+// 3) Settings change
+// 4) System state change;
+int TimeManager::registerCallbackHandlers()
+{
+ constexpr auto WATCH_SETTING_CHANGE =
+ "type='signal',interface='org.freedesktop.DBus.Properties',"
+ "path='/org/openbmc/settings/host0',member='PropertiesChanged'";
+
+ constexpr auto WATCH_PGOOD_CHANGE =
+ "type='signal',interface='org.freedesktop.DBus.Properties',"
+ "path='/org/openbmc/control/power0',member='PropertiesChanged'";
+
+ // Extract the descriptor out of sd_bus construct.
+ auto sdBusFd = sd_bus_get_fd(iv_TimeBus);
+
+ auto r = sd_event_add_io(iv_Event, &iv_EventSource, sdBusFd, EPOLLIN,
+ processSdBusMessage, this);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<" adding sd_bus_message handler" << std::endl;
+ return r;
+ }
+
+ // Choose the MAX time that is possible to aviod mis fires.
+ itimerspec maxTime {};
+ maxTime.it_value.tv_sec = TIME_T_MAX;
+
+ auto timeFd = timerfd_create(CLOCK_REALTIME, 0);
+ if (timeFd < 0)
+ {
+ std::cerr << "Errorno: " << errno << " creating timerfd" << std::endl;
+ return -1;
+ }
+
+ r = timerfd_settime(timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+ &maxTime, nullptr);
+ if (r)
+ {
+ std::cerr << "Errorno: " << errno << "Setting timerfd" << std::endl;
+ return -1;
+ }
+
+ // Wake me up *only* if someone SETS the time
+ r = sd_event_add_io(iv_Event, &iv_EventSource, timeFd, EPOLLIN,
+ processTimeChange, this);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "adding time_change handler" << std::endl;
+ return r;
+ }
+
+ // Watch for property changes in settingsd
+ r = sd_bus_add_match(iv_TimeBus, NULL, WATCH_SETTING_CHANGE,
+ processPropertyChange, this);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<" adding property change listener" << std::endl;
+ return r;
+ }
+
+ // Watch for state change. Only reliable one to count on is
+ // state of [pgood]. value of [1] meaning host is powering on / powered
+ // on. [0] means powered off.
+ r = sd_bus_add_match(iv_TimeBus, NULL, WATCH_PGOOD_CHANGE,
+ processPgoodChange, this);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << " adding pgood change listener" << std::endl;
+ }
+ return r;
+}
+
+int TimeManager::setupTimeManager()
+{
+ auto r = sd_bus_default_system(&iv_TimeBus);
+ if (r < 0)
+ {
+ std::cerr << "Error" << strerror(-r)
+ <<" connecting to system bus" << std::endl;
+ goto finish;
+ }
+
+ r = sd_bus_add_object_manager(iv_TimeBus, NULL, cv_ObjPath);
+ if (r < 0)
+ {
+ std::cerr << "Error" << strerror(-r)
+ <<" adding object manager" << std::endl;
+ goto finish;
+ }
+
+ std::cout <<"Registering dbus methods" << std::endl;
+ r = sd_bus_add_object_vtable(iv_TimeBus,
+ NULL,
+ cv_ObjPath,
+ cv_BusName,
+ timeServicesVtable,
+ this);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<" adding timer services vtable" << std::endl;
+ goto finish;
+ }
+
+ // create a sd_event object and add handlers
+ r = sd_event_default(&iv_Event);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ <<"creating an sd_event" << std::endl;
+ goto finish;
+ }
+
+ // Handlers called by sd_event when an activity
+ // is observed in event loop
+ r = registerCallbackHandlers();
+ if (r < 0)
+ {
+ std::cerr << "Error setting up callback handlers" << std::endl;
+ goto finish;
+ }
+
+ // Need to do this here since TimeConfig may update the necessary owners
+ r = config.processInitialSettings(iv_TimeBus);
+ if (r < 0)
+ {
+ std::cerr << "Error setting up configuration params" << std::endl;
+ goto finish;
+ }
+
+ // Read saved values from previous run
+ r = readPersistentData();
+ if (r < 0)
+ {
+ std::cerr << "Error reading persistent data" << std::endl;
+ goto finish;
+ }
+
+ // Claim the bus
+ r = sd_bus_request_name(iv_TimeBus, cv_BusName, 0);
+ if (r < 0)
+ {
+ std::cerr << "Error: " << strerror(-r)
+ << "acquiring service name" << std::endl;
+ goto finish;
+ }
+finish:
+ if (r < 0)
+ {
+ iv_EventSource = sd_event_source_unref(iv_EventSource);
+ iv_Event = sd_event_unref(iv_Event);
+ }
+ return r;
+}
+
+int TimeManager::readPersistentData()
+{
+ using namespace std::chrono;
+
+ // Get current host_offset
+ // When we reach here, TimeConfig would have been populated and would have
+ // applied the owner to SPLIT *if* the system allowed it. So check if we
+ // moved away from SPLIT and if so, make offset:0
+ if (config.isSplitModeChanged())
+ {
+ iv_HostOffset = microseconds(0);
+ auto r = config.writeData<decltype(iv_HostOffset.count())>
+ (cv_HostOffsetFile,
+ iv_HostOffset.count());
+ if (r < 0)
+ {
+ std::cerr <<" Error saving offset to file" << std::endl;
+ return r;
+ }
+ }
+ else
+ {
+ auto hostTimeOffset = config.readData<long long int>(cv_HostOffsetFile);
+ iv_HostOffset = microseconds(hostTimeOffset);
+ std::cout <<"Last known host_offset:" << hostTimeOffset << std::endl;
+ }
+
+ //How long was the FSP up prior to 'this' start
+ iv_UptimeUsec = duration_cast<microseconds>
+ (system_clock::now().time_since_epoch() -
+ steady_clock::now().time_since_epoch());
+ if (iv_UptimeUsec.count() < 0)
+ {
+ std::cerr <<"Error reading uptime" << std::endl;
+ return -1;
+ }
+ std::cout <<"Initial Uptime Usec: "
+ << iv_UptimeUsec.count() << std::endl;
+ return 0;
+}
+
+// Forever loop
+int TimeManager::waitForClientRequest()
+{
+ return sd_event_loop(iv_Event);
+}
+
+int main(int argc, char* argv[])
+{
+ auto tmgr = std::make_unique<TimeManager>();
+
+ // Wait for the work
+ auto r = tmgr->waitForClientRequest();
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}