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