xref: /openbmc/phosphor-logging/util.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2020 IBM Corporation
3 
4 #include "config.h"
5 
6 #include "util.hpp"
7 
8 #include <poll.h>
9 #include <sys/inotify.h>
10 #include <systemd/sd-bus.h>
11 #include <systemd/sd-journal.h>
12 #include <unistd.h>
13 
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/bus.hpp>
16 
17 #include <chrono>
18 #include <fstream>
19 
20 namespace phosphor::logging::util
21 {
22 
23 std::optional<std::string> getOSReleaseValue(const std::string& key)
24 {
25     std::ifstream versionFile{BMC_VERSION_FILE};
26     std::string line;
27     std::string keyPattern{key + '='};
28 
29     while (std::getline(versionFile, line))
30     {
31         if (line.substr(0, keyPattern.size()).find(keyPattern) !=
32             std::string::npos)
33         {
34             // If the value isn't surrounded by quotes, then pos will be
35             // npos + 1 = 0, and the 2nd arg to substr() will be npos
36             // which means get the rest of the string.
37             auto value = line.substr(keyPattern.size());
38             std::size_t pos = value.find_first_of('"') + 1;
39             return value.substr(pos, value.find_last_of('"') - pos);
40         }
41     }
42 
43     return std::nullopt;
44 }
45 
46 void journalSync()
47 {
48     bool syncRequested = false;
49     auto fd = -1;
50     auto rc = -1;
51     auto wd = -1;
52     auto bus = sdbusplus::bus::new_default();
53 
54     auto start = std::chrono::duration_cast<std::chrono::microseconds>(
55                      std::chrono::steady_clock::now().time_since_epoch())
56                      .count();
57 
58     // Make a request to sync the journal with the SIGRTMIN+1 signal and
59     // block until it finishes, waiting at most 5 seconds.
60     //
61     // Number of loop iterations = 3 for the following reasons:
62     // Iteration #1: Requests a journal sync by killing the journald service.
63     // Iteration #2: Setup an inotify watch to monitor the synced file that
64     //               journald updates with the timestamp the last time the
65     //               journal was flushed.
66     // Iteration #3: Poll to wait until inotify reports an event which blocks
67     //               the error log from being commited until the sync completes.
68     constexpr auto maxRetry = 3;
69     for (int i = 0; i < maxRetry; i++)
70     {
71         // Read timestamp from synced file
72         constexpr auto syncedPath = "/run/systemd/journal/synced";
73         std::ifstream syncedFile(syncedPath);
74         if (syncedFile.fail())
75         {
76             // If the synced file doesn't exist, a sync request will create it.
77             if (errno != ENOENT)
78             {
79                 lg2::error(
80                     "Failed to open journal synced file {FILENAME}: {ERROR}",
81                     "FILENAME", syncedPath, "ERROR", strerror(errno));
82                 return;
83             }
84         }
85         else
86         {
87             // Only read the synced file if it exists.
88             // See if a sync happened by now
89             std::string timestampStr;
90             std::getline(syncedFile, timestampStr);
91             auto timestamp = std::stoll(timestampStr);
92             if (timestamp >= start)
93             {
94                 break;
95             }
96         }
97 
98         // Let's ask for a sync, but only once
99         if (!syncRequested)
100         {
101             syncRequested = true;
102 
103             constexpr auto JOURNAL_UNIT = "systemd-journald.service";
104             auto signal = SIGRTMIN + 1;
105 
106             auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
107                                               SYSTEMD_INTERFACE, "KillUnit");
108             method.append(JOURNAL_UNIT, "main", signal);
109             bus.call(method);
110             if (method.is_method_error())
111             {
112                 lg2::error("Failed to kill journal service");
113                 break;
114             }
115 
116             continue;
117         }
118 
119         // Let's install the inotify watch, if we didn't do that yet. This watch
120         // monitors the syncedFile for when journald updates it with a newer
121         // timestamp. This means the journal has been flushed.
122         if (fd < 0)
123         {
124             fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
125             if (fd < 0)
126             {
127                 lg2::error("Failed to create inotify watch: {ERROR}", "ERROR",
128                            strerror(errno));
129                 return;
130             }
131 
132             constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal";
133             wd = inotify_add_watch(fd, JOURNAL_RUN_PATH,
134                                    IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR);
135             if (wd < 0)
136             {
137                 lg2::error("Failed to watch journal directory: {PATH}: {ERROR}",
138                            "PATH", JOURNAL_RUN_PATH, "ERROR", strerror(errno));
139                 close(fd);
140                 return;
141             }
142             continue;
143         }
144 
145         // Let's wait until inotify reports an event
146         struct pollfd fds = {
147             fd,
148             POLLIN,
149             0,
150         };
151         constexpr auto pollTimeout = 5; // 5 seconds
152         rc = poll(&fds, 1, pollTimeout * 1000);
153         if (rc < 0)
154         {
155             lg2::error("Failed to add event: {ERROR}", "ERROR",
156                        strerror(errno));
157             inotify_rm_watch(fd, wd);
158             close(fd);
159             return;
160         }
161         else if (rc == 0)
162         {
163             lg2::info("Poll timeout ({TIMEOUT}), no new journal synced data",
164                       "TIMEOUT", pollTimeout);
165             break;
166         }
167 
168         // Read from the specified file descriptor until there is no new data,
169         // throwing away everything read since the timestamp will be read at the
170         // beginning of the loop.
171         constexpr auto maxBytes = 64;
172         uint8_t buffer[maxBytes];
173         while (read(fd, buffer, maxBytes) > 0)
174             ;
175     }
176 
177     if (fd != -1)
178     {
179         if (wd != -1)
180         {
181             inotify_rm_watch(fd, wd);
182         }
183         close(fd);
184     }
185 
186     return;
187 }
188 
189 namespace additional_data
190 {
191 auto parse(const std::vector<std::string>& data)
192     -> std::map<std::string, std::string>
193 {
194     std::map<std::string, std::string> metadata{};
195 
196     constexpr auto separator = '=';
197     for (const auto& entryItem : data)
198     {
199         auto pos = entryItem.find(separator);
200         if (std::string::npos != pos)
201         {
202             auto key = entryItem.substr(0, entryItem.find(separator));
203             auto value = entryItem.substr(entryItem.find(separator) + 1);
204             metadata.emplace(std::move(key), std::move(value));
205         }
206     }
207 
208     return metadata;
209 }
210 
211 auto combine(const std::map<std::string, std::string>& data)
212     -> std::vector<std::string>
213 {
214     std::vector<std::string> metadata{};
215 
216     for (const auto& [key, value] : data)
217     {
218         std::string line{key};
219         line += "=" + value;
220         metadata.emplace_back(std::move(line));
221     }
222 
223     return metadata;
224 }
225 } // namespace additional_data
226 
227 } // namespace phosphor::logging::util
228