xref: /openbmc/phosphor-networkd/src/hostname_manager.cpp (revision 6b1b0ea9732f4e1ce33e041062f5b957d6c76f4a)
1 #include "hostname_manager.hpp"
2 
3 #include "network_manager.hpp"
4 
5 #include <phosphor-logging/elog-errors.hpp>
6 #include <phosphor-logging/lg2.hpp>
7 #include <sdbusplus/bus.hpp>
8 #include <stdplus/pinned.hpp>
9 #include <xyz/openbmc_project/Common/error.hpp>
10 
11 #include <filesystem>
12 #include <fstream>
13 #include <string>
14 
15 namespace phosphor
16 {
17 namespace network
18 {
19 
20 using namespace phosphor::logging;
21 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
22 
23 static constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper";
24 static constexpr const char* mapperObjPath =
25     "/xyz/openbmc_project/object_mapper";
26 static constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper";
27 static constexpr const char* inventoryRoot = "/xyz/openbmc_project/inventory";
28 static constexpr const char* bmcItemIntf =
29     "xyz.openbmc_project.Inventory.Item.Bmc";
30 static constexpr const char* assetIntf =
31     "xyz.openbmc_project.Inventory.Decorator.Asset";
32 static constexpr const char* networkItemIntf =
33     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
34 static constexpr const char* propIntf = "org.freedesktop.DBus.Properties";
35 static constexpr const char* hostnamedBusName = "org.freedesktop.hostname1";
36 static constexpr const char* hostnamedObjPath = "/org/freedesktop/hostname1";
37 static constexpr const char* hostnamedIntf = "org.freedesktop.hostname1";
38 
HostnameManager(stdplus::PinnedRef<sdbusplus::bus_t> bus,stdplus::PinnedRef<Manager> manager)39 HostnameManager::HostnameManager(stdplus::PinnedRef<sdbusplus::bus_t> bus,
40                                  stdplus::PinnedRef<Manager> manager) :
41     bus(bus), manager(manager)
42 {}
43 
initialize()44 void HostnameManager::initialize()
45 {
46     if (!isFirstBoot())
47     {
48         lg2::info("Hostname already set on previous boot, skipping");
49         return;
50     }
51 
52     lg2::info("First boot detected, setting unique hostname");
53     setUniqueHostname();
54     markHostnameSet();
55 }
56 
isFirstBoot() const57 bool HostnameManager::isFirstBoot() const
58 {
59     return !std::filesystem::exists(firstBootFile);
60 }
61 
markHostnameSet()62 void HostnameManager::markHostnameSet()
63 {
64     try
65     {
66         // Create parent directories if they don't exist
67         std::filesystem::path firstBootFilePath(firstBootFile);
68         std::filesystem::create_directories(firstBootFilePath.parent_path());
69 
70         std::ofstream file(firstBootFile);
71         if (!file)
72         {
73             lg2::error("Failed to create firstBoot file: {PATH}", "PATH",
74                        firstBootFile);
75         }
76     }
77     catch (const std::exception& e)
78     {
79         lg2::error("Exception creating firstBoot file: {ERROR}", "ERROR", e);
80     }
81 }
82 
getBmcSerialNumber()83 std::string HostnameManager::getBmcSerialNumber()
84 {
85     try
86     {
87         // Get BMC item path from inventory
88         auto method = bus.get().new_method_call(mapperBusName, mapperObjPath,
89                                                 mapperIntf, "GetSubTree");
90         method.append(inventoryRoot, 0, std::vector<std::string>{bmcItemIntf});
91 
92         auto reply = bus.get().call(method);
93 
94         std::map<std::string, std::map<std::string, std::vector<std::string>>>
95             response;
96         reply.read(response);
97 
98         if (response.empty())
99         {
100             lg2::warning("No BMC item found in inventory");
101             return "";
102         }
103 
104         // Get the first BMC item path
105         const auto& bmcPath = response.begin()->first;
106         const auto& serviceMap = response.begin()->second;
107 
108         if (serviceMap.empty())
109         {
110             lg2::warning("No service found for BMC item");
111             return "";
112         }
113 
114         const auto& serviceName = serviceMap.begin()->first;
115 
116         // Get SerialNumber property
117         auto propMethod = bus.get().new_method_call(
118             serviceName.c_str(), bmcPath.c_str(), propIntf, "Get");
119         propMethod.append(assetIntf, "SerialNumber");
120 
121         auto propReply = bus.get().call(propMethod);
122         std::variant<std::string> serialNumber;
123         propReply.read(serialNumber);
124 
125         std::string sn = std::get<std::string>(serialNumber);
126         if (sn.empty())
127         {
128             lg2::warning("BMC Serial Number is empty");
129         }
130         else
131         {
132             lg2::info("Retrieved BMC Serial Number: {SN}", "SN", sn);
133         }
134 
135         return sn;
136     }
137     catch (const std::exception& e)
138     {
139         lg2::error("Failed to get BMC serial number: {ERROR}", "ERROR", e);
140         return "";
141     }
142 }
143 
getMacAddress()144 std::string HostnameManager::getMacAddress()
145 {
146     try
147     {
148         auto method = bus.get().new_method_call(mapperBusName, mapperObjPath,
149                                                 mapperIntf, "GetSubTree");
150         method.append(inventoryRoot, 0,
151                       std::vector<std::string>{networkItemIntf});
152 
153         auto reply = bus.get().call(method);
154 
155         std::map<std::string, std::map<std::string, std::vector<std::string>>>
156             response;
157         reply.read(response);
158 
159         if (response.empty())
160         {
161             lg2::warning("No network interface found in inventory");
162             return "";
163         }
164 
165         // Get the first network interface path
166         const auto& netPath = response.begin()->first;
167         const auto& serviceMap = response.begin()->second;
168 
169         if (serviceMap.empty())
170         {
171             lg2::warning("No service found for network interface");
172             return "";
173         }
174 
175         const auto& serviceName = serviceMap.begin()->first;
176 
177         // Get MACAddress property
178         auto propMethod = bus.get().new_method_call(
179             serviceName.c_str(), netPath.c_str(), propIntf, "Get");
180         propMethod.append(networkItemIntf, "MACAddress");
181 
182         auto propReply = bus.get().call(propMethod);
183         std::variant<std::string> macAddress;
184         propReply.read(macAddress);
185 
186         std::string mac = std::get<std::string>(macAddress);
187         if (mac.empty())
188         {
189             lg2::warning("MAC Address is empty");
190         }
191         else
192         {
193             lg2::info("Retrieved MAC Address: {MAC}", "MAC", mac);
194         }
195 
196         return mac;
197     }
198     catch (const std::exception& e)
199     {
200         lg2::error("Failed to get MAC address: {ERROR}", "ERROR", e);
201         return "";
202     }
203 }
204 
getCurrentHostname()205 std::string HostnameManager::getCurrentHostname()
206 {
207     try
208     {
209         auto method = bus.get().new_method_call(
210             hostnamedBusName, hostnamedObjPath, propIntf, "Get");
211         method.append(hostnamedIntf, "Hostname");
212 
213         auto reply = bus.get().call(method);
214         std::variant<std::string> hostname;
215         reply.read(hostname);
216 
217         return std::get<std::string>(hostname);
218     }
219     catch (const std::exception& e)
220     {
221         lg2::error("Failed to get current hostname: {ERROR}", "ERROR", e);
222         return "localhost";
223     }
224 }
225 
setHostname(const std::string & hostname)226 bool HostnameManager::setHostname(const std::string& hostname)
227 {
228     try
229     {
230         auto method =
231             bus.get().new_method_call(hostnamedBusName, hostnamedObjPath,
232                                       hostnamedIntf, "SetStaticHostname");
233         method.append(hostname, false);
234 
235         bus.get().call(method);
236         lg2::info("Successfully set hostname to: {HOSTNAME}", "HOSTNAME",
237                   hostname);
238         return true;
239     }
240     catch (const std::exception& e)
241     {
242         lg2::error("Failed to set hostname to {HOSTNAME}: {ERROR}", "HOSTNAME",
243                    hostname, "ERROR", e);
244         return false;
245     }
246 }
247 
setUniqueHostname()248 void HostnameManager::setUniqueHostname()
249 {
250     std::string currentHostname = getCurrentHostname();
251     std::string uniqueSuffix;
252 
253     // Try to get BMC serial number first
254     std::string serialNumber = getBmcSerialNumber();
255     if (!serialNumber.empty())
256     {
257         uniqueSuffix = serialNumber;
258         lg2::info("Using BMC Serial Number for unique hostname");
259     }
260     else
261     {
262         // Fallback to MAC address
263         lg2::warning(
264             "BMC Serial Number not available, falling back to MAC address");
265         std::string macAddress = getMacAddress();
266         if (!macAddress.empty())
267         {
268             uniqueSuffix = macAddress;
269             lg2::info("Using MAC Address for unique hostname");
270         }
271         else
272         {
273             lg2::error(
274                 "Neither Serial Number nor MAC Address available, cannot set unique hostname");
275             return;
276         }
277     }
278 
279     // Construct and set unique hostname
280     std::string newHostname = currentHostname + "-" + uniqueSuffix;
281 
282     if (setHostname(newHostname))
283     {
284         lg2::info("Unique hostname set successfully: {HOSTNAME}", "HOSTNAME",
285                   newHostname);
286     }
287     else
288     {
289         lg2::error("Failed to set unique hostname");
290     }
291 }
292 
293 } // namespace network
294 } // namespace phosphor
295