#define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include #include #include #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(userdata); return tmgr->getTime(m, userdata, retError); } int SetTime(sd_bus_message* m, void* userdata, sd_bus_error* retError) { auto tmgr = static_cast(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(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(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(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(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 (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(microseconds(timeInUsec)); auto time_T = static_cast(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(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(seconds(hostTimeSec)); std::cout << "setHostTime: HostTimeInUSec: " << hostTimeUsec.count() << std::endl; if (config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT) { // Adjust the host offset auto bmcTimeUsec = duration_cast (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(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(userdata); std::array 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 (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(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(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(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 (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(); // Wait for the work auto r = tmgr->waitForClientRequest(); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }