1 #include "manager.hpp"
2 
3 #include "utils.hpp"
4 
5 #include <phosphor-logging/elog-errors.hpp>
6 #include <phosphor-logging/elog.hpp>
7 #include <phosphor-logging/log.hpp>
8 #include <xyz/openbmc_project/Common/error.hpp>
9 #include <xyz/openbmc_project/State/Host/server.hpp>
10 
11 namespace rules = sdbusplus::bus::match::rules;
12 
13 namespace // anonymous
14 {
15 constexpr auto HOST_CURRENT_STATE = "CurrentHostState";
16 
17 constexpr auto SYSTEMD_TIME_SERVICE = "org.freedesktop.timedate1";
18 constexpr auto SYSTEMD_TIME_PATH = "/org/freedesktop/timedate1";
19 constexpr auto SYSTEMD_TIME_INTERFACE = "org.freedesktop.timedate1";
20 constexpr auto METHOD_SET_NTP = "SetNTP";
21 } // namespace
22 
23 namespace phosphor
24 {
25 namespace time
26 {
27 
28 using namespace phosphor::logging;
29 
30 const std::set<std::string> Manager::managedProperties = {PROPERTY_TIME_MODE};
31 
32 Manager::Manager(sdbusplus::bus::bus& bus) : bus(bus), settings(bus)
33 {
34     using namespace sdbusplus::bus::match::rules;
35     hostStateChangeMatch =
36         std::make_unique<decltype(hostStateChangeMatch)::element_type>(
37             bus, propertiesChanged(settings.hostState, settings::hostStateIntf),
38             std::bind(std::mem_fn(&Manager::onHostStateChanged), this,
39                       std::placeholders::_1));
40     settingsMatches.emplace_back(
41         bus, propertiesChanged(settings.timeSyncMethod, settings::timeSyncIntf),
42         std::bind(std::mem_fn(&Manager::onSettingsChanged), this,
43                   std::placeholders::_1));
44 
45     checkHostOn();
46 
47     // Restore settings from persistent storage
48     restoreSettings();
49 
50     // Check the settings daemon to process the new settings
51     auto mode = getSetting(settings.timeSyncMethod.c_str(),
52                            settings::timeSyncIntf, PROPERTY_TIME_MODE);
53 
54     onPropertyChanged(PROPERTY_TIME_MODE, mode);
55 }
56 
57 void Manager::addListener(PropertyChangeListner* listener)
58 {
59     // Notify listener about the initial value
60     listener->onModeChanged(timeMode);
61 
62     listeners.insert(listener);
63 }
64 
65 void Manager::restoreSettings()
66 {
67     auto mode = utils::readData<std::string>(modeFile);
68     if (!mode.empty())
69     {
70         timeMode = utils::strToMode(mode);
71     }
72 }
73 
74 void Manager::checkHostOn()
75 {
76     using Host = sdbusplus::xyz::openbmc_project::State::server::Host;
77     auto hostService = utils::getService(bus, settings.hostState.c_str(),
78                                          settings::hostStateIntf);
79     auto stateStr = utils::getProperty<std::string>(
80         bus, hostService.c_str(), settings.hostState.c_str(),
81         settings::hostStateIntf, HOST_CURRENT_STATE);
82     auto state = Host::convertHostStateFromString(stateStr);
83     hostOn = (state == Host::HostState::Running);
84 }
85 
86 void Manager::onPropertyChanged(const std::string& key,
87                                 const std::string& value)
88 {
89     if (hostOn)
90     {
91         // If host is on, set the values as requested time mode.
92         // And when host becomes off, notify the listeners.
93         setPropertyAsRequested(key, value);
94     }
95     else
96     {
97         // If host is off, notify listeners
98         if (key == PROPERTY_TIME_MODE)
99         {
100             setCurrentTimeMode(value);
101             onTimeModeChanged(value);
102         }
103     }
104 }
105 
106 int Manager::onSettingsChanged(sdbusplus::message::message& msg)
107 {
108     using Interface = std::string;
109     using Property = std::string;
110     using Value = std::string;
111     using Properties = std::map<Property, std::variant<Value>>;
112 
113     Interface interface;
114     Properties properties;
115 
116     msg.read(interface, properties);
117 
118     for (const auto& p : properties)
119     {
120         onPropertyChanged(p.first, std::get<std::string>(p.second));
121     }
122 
123     return 0;
124 }
125 
126 void Manager::setPropertyAsRequested(const std::string& key,
127                                      const std::string& value)
128 {
129     if (key == PROPERTY_TIME_MODE)
130     {
131         setRequestedMode(value);
132     }
133     else
134     {
135         // The key shall be already the supported one
136         using InvalidArgumentError =
137             sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
138         using namespace xyz::openbmc_project::Common;
139         elog<InvalidArgumentError>(
140             InvalidArgument::ARGUMENT_NAME(key.c_str()),
141             InvalidArgument::ARGUMENT_VALUE(value.c_str()));
142     }
143 }
144 
145 void Manager::setRequestedMode(const std::string& mode)
146 {
147     requestedMode = mode;
148 }
149 
150 void Manager::updateNtpSetting(const std::string& value)
151 {
152     bool isNtp =
153         (value == "xyz.openbmc_project.Time.Synchronization.Method.NTP");
154     auto method = bus.new_method_call(SYSTEMD_TIME_SERVICE, SYSTEMD_TIME_PATH,
155                                       SYSTEMD_TIME_INTERFACE, METHOD_SET_NTP);
156     method.append(isNtp, false); // isNtp: 'true/false' means Enable/Disable
157                                  // 'false' meaning no policy-kit
158 
159     try
160     {
161         bus.call_noreply(method);
162         log<level::INFO>("Updated NTP setting", entry("ENABLED=%d", isNtp));
163     }
164     catch (const sdbusplus::exception::SdBusError& ex)
165     {
166         log<level::ERR>("Failed to update NTP setting",
167                         entry("ERR=%s", ex.what()));
168     }
169 }
170 
171 void Manager::onHostStateChanged(sdbusplus::message::message& msg)
172 {
173     using Interface = std::string;
174     using Property = std::string;
175     using Value = std::string;
176     using Properties = std::map<Property, std::variant<Value>>;
177     using Host = sdbusplus::xyz::openbmc_project::State::server::Host;
178 
179     Interface interface;
180     Properties properties;
181 
182     msg.read(interface, properties);
183 
184     for (const auto& p : properties)
185     {
186         if (p.first == HOST_CURRENT_STATE)
187         {
188             auto state = Host::convertHostStateFromString(
189                 std::get<std::string>(p.second));
190             onHostState(state == Host::HostState::Running);
191             break;
192         }
193     }
194 }
195 
196 void Manager::onHostState(bool on)
197 {
198     hostOn = on;
199     if (hostOn)
200     {
201         log<level::INFO>("Changing time settings is *deferred* now");
202         return;
203     }
204     log<level::INFO>("Changing time settings allowed now");
205     if (!requestedMode.empty())
206     {
207         if (setCurrentTimeMode(requestedMode))
208         {
209             onTimeModeChanged(requestedMode);
210         }
211         setRequestedMode({}); // Clear requested mode
212     }
213 }
214 
215 bool Manager::setCurrentTimeMode(const std::string& mode)
216 {
217     auto newMode = utils::strToMode(mode);
218     if (newMode != timeMode)
219     {
220         log<level::INFO>("Time mode is changed",
221                          entry("MODE=%s", mode.c_str()));
222         timeMode = newMode;
223         utils::writeData(modeFile, mode);
224         return true;
225     }
226     else
227     {
228         return false;
229     }
230 }
231 
232 void Manager::onTimeModeChanged(const std::string& mode)
233 {
234     for (const auto listener : listeners)
235     {
236         listener->onModeChanged(timeMode);
237     }
238     // When time_mode is updated, update the NTP setting
239     updateNtpSetting(mode);
240 }
241 
242 std::string Manager::getSetting(const char* path, const char* interface,
243                                 const char* setting) const
244 {
245     std::string settingManager = utils::getService(bus, path, interface);
246     return utils::getProperty<std::string>(bus, settingManager.c_str(), path,
247                                            interface, setting);
248 }
249 
250 } // namespace time
251 } // namespace phosphor
252