blob: d0e71134b5c152339da7762d1bf8cb6d593f7503 [file] [log] [blame]
#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)
{
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(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,
sd_bus_error *retError)
{
// 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",
retError,
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.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));
return setTimeOfDay(timeInUsec, retError);
}
// 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
return setTimeOfDay(hostTimeUsec, retError);
}
// 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(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(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;
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, "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(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;
}