Belle II Software development
IOIntercept.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <framework/utilities/IOIntercept.h>
10#include <framework/logging/Logger.h>
11#include <memory>
12#include <cstring>
13#include <csignal>
14#include <set>
15#include <unistd.h>
16#include <fcntl.h>
17#include <boost/algorithm/string.hpp>
18
19namespace Belle2::IOIntercept {
35 public:
44 {
45 static CaptureStreamAbortHandler instance;
46 return instance;
47 }
48
49 void addObject(CaptureStream* obj) { m_objects.emplace(obj); }
51 void removeObject(CaptureStream* obj) { m_objects.erase(obj); }
52 private:
55 {
56 auto result = std::signal(SIGABRT, &CaptureStreamAbortHandler::handle);
57 if (result == SIG_ERR) B2FATAL("Cannot register abort handler");
58 }
59
60 static void handle(int signal);
62 std::set<CaptureStream*> m_objects;
63 };
64
66 {
67 // move set of registered objects in here so we can call finish when
68 // looping over them without invalidating the iterators
69 std::set<CaptureStream*> objects;
71 // loop over all registered instances and print the content in their
72 // buffer to the original file descriptor
73 for (CaptureStream* stream : objects) {
74 // close the write end of the pipe
75 close(stream->m_replacementFD);
76 // read all content
77 if (stream->finish()) {
78 const std::string& out = stream->getOutput();
79 // and write it to the original file descriptor
80 if (out.size()) write(stream->m_savedFD, out.c_str(), out.size());
81 }
82 }
83 // write error message and signal error
84 write(STDERR_FILENO, "abort() called, exiting\n", 24);
85 std::_Exit(EXIT_FAILURE);
86 }
87
88 StreamInterceptor::StreamInterceptor(std::ostream& stream, FILE* fileObject):
89 m_stream(stream), m_fileObject(fileObject), m_savedFD(dup(fileno(m_fileObject)))
90 {
91 if (m_savedFD < 0) {
92 B2ERROR("Error duplicating file descriptor: " << std::strerror(errno));
93 }
94 }
96 {
97 finish();
98 if (m_savedFD >= 0) close(m_savedFD);
99 if (m_replacementFD >= 0) close(m_replacementFD);
100 }
101
102 void StreamInterceptor::readFD(int fd, std::string& out)
103 {
104 out.clear();
105 if (fd <= 0) return;
106 // need a buffer to read
107 static std::unique_ptr<char[]> buffer(new char[1024]);
108 // so then, read everything
109 while (true) {
110 ssize_t size = read(fd, buffer.get(), 1024);
111 if (size <= 0) {
112 // in case we get interrupted by signal, try again
113 if (size < 0 && errno == EINTR) continue;
114 break;
115 }
116 out.append(buffer.get(), static_cast<size_t>(size));
117 }
118 }
119
120 bool StreamInterceptor::replaceFD(int fileDescriptor)
121 {
122 // obviously we don't want to replace invalid descriptors
123 if (fileDescriptor < 0) return false;
124 // flush existing stream
125 m_stream << std::flush;
126 std::fflush(m_fileObject);
127 // and clear the bad bits we might have gotten when pipe capacity is reached
128 m_stream.clear();
129 // and then replace the file descriptor
130 const int currentFD = fileno(m_fileObject);
131 if (currentFD < 0) {
132 B2ERROR("Error obtaining file descriptor: " << std::strerror(errno));
133 return false;
134 }
135 while (dup2(fileDescriptor, currentFD) < 0) {
136 if (errno != EINTR && errno != EBUSY) {
137 B2ERROR("Error in dup2(), cannot replace file descriptor: " << std::strerror(errno));
138 return false;
139 }
140 // interrupted or busy, let's try again
141 }
142 return true;
143 }
144
145 DiscardStream::DiscardStream(std::ostream& stream, FILE* fileObject): StreamInterceptor(stream, fileObject)
146 {
147 int devNull = open("/dev/null", O_WRONLY);
148 if (devNull < 0) {
149 // warning
150 B2ERROR("Cannot open /dev/null: " << std::strerror(errno));
151 return;
152 }
153 setReplacementFD(devNull);
154 }
155
156 CaptureStream::CaptureStream(std::ostream& stream, FILE* fileObject): StreamInterceptor(stream, fileObject)
157 {
158 int pipeFD[2] {0};
159 if (pipe2(pipeFD, O_NONBLOCK) < 0) {
160 B2ERROR("Error creating pipe: " << std::strerror(errno));
161 return;
162 }
163 m_pipeReadFD = pipeFD[0];
164 setReplacementFD(pipeFD[1]);
165 }
166
168 {
169 // the base destructor is called after this one so we need to make sure
170 // everything is finished before we close the pipe.
171 finish();
172 // no need to close the write part, done by base class
173 if (m_pipeReadFD >= 0) close(m_pipeReadFD);
174 }
175
177 {
180 return true;
181 }
182 return false;
183 }
184
186 {
190 return true;
191 }
192 return false;
193 }
194
195 namespace {
208 void sendLogMessage(LogConfig::ELogLevel logLevel, int debugLevel, const std::string& name, const std::string& indent,
209 std::string message)
210 {
211 // avoid formatting if message will not be shown anyway
212 if (!LogSystem::Instance().isLevelEnabled(logLevel, debugLevel)) return;
213 // remove trailing whitespace and end of lines
214 boost::algorithm::trim_right(message);
215 // remove empty lines but keep white space in non empty first line
216 while (!message.empty()) {
217 std::string new_message = boost::algorithm::trim_left_copy_if(message, boost::algorithm::is_any_of(" \t\r"));
218 if (new_message.empty()) {
219 message = new_message;
220 } else if (new_message[0] == '\n') {
221 message = new_message.substr(1, std::string::npos);
222 continue;
223 }
224 break;
225 }
226 // is the message empty? cppcheck thinks that's always the case
227 // cppcheck-suppress knownConditionTrueFalse
228 if (message.empty()) return;
229 // add indentation
230 boost::algorithm::replace_all(message, "\n", "\n" + indent);
231 // fine, show message
232 B2LOG(logLevel, debugLevel, "Output from " << name << ":\n" << indent << message);
233 }
234 }
235
237 {
238 bool result = CaptureStdOutStdErr::finish();
241 return result;
242 }
243}
void removeObject(CaptureStream *obj)
Remove a CaptureStream object to no longer guard against SIGABRT.
void addObject(CaptureStream *obj)
Add a CaptureStream object to guard against SIGABRT.
static CaptureStreamAbortHandler & getInstance()
Return the singleton instance.
CaptureStreamAbortHandler(CaptureStreamAbortHandler &&)=delete
Singleton, no move construction.
static void handle(int signal)
signal handler: print all pending redirection buffers and exit
std::set< CaptureStream * > m_objects
list of all active stream redirections
CaptureStreamAbortHandler & operator=(const CaptureStreamAbortHandler &)=delete
Singleton, no assignment.
CaptureStreamAbortHandler(const CaptureStreamAbortHandler &)=delete
Singleton, no copy construction.
Class to capture anything written to stream into a string.
CaptureStream(std::ostream &stream, FILE *fileObject)
Create a StreamInterceptor which writes into a pipe.
std::string m_outputStr
string with the output, only filled after finish()
int m_pipeReadFD
file descriptor of the read end of the pipe
bool start()
Start intercepting the output.
~CaptureStream()
Close file descriptors.
bool finish()
Restore the stream and get the output from the pipe.
DiscardStream(std::ostream &stream, FILE *fileObject)
Create StreamInterceptor which will redirect to /dev/null.
LogConfig::ELogLevel m_stderrLevel
severity of the log message to be emitted for output on stderr
const std::string m_name
Name of the output producing tool/library.
int m_stderrDebugLevel
debug level for the log message to be emitted for output on stderr if m_stderrLevel is c_Debug
LogConfig::ELogLevel m_stdoutLevel
severity of the log message to be emitted for output on stdout
bool finish()
Finish the capture and emit the message if output has appeard on stdout or stderr.
int m_stdoutDebugLevel
debug level for the log message to be emitted for output on stdout if m_stdoutLevel is c_Debug
std::string m_indent
Indentation to add to the beginning of each line of output.
static void readFD(int fd, std::string &out)
Read the contents of a file descriptor until there is no more input and place them in out.
std::ostream & m_stream
C++ stream object, only needed to flush before replacement.
Definition IOIntercept.h:65
bool replaceFD(int fileDescriptor)
Replace the file descriptor of m_fileObject with the one passed.
~StreamInterceptor()
close file descriptors
FILE * m_fileObject
File object of the file we want to replace, needed to obtain file descriptor and to flush.
Definition IOIntercept.h:67
void setReplacementFD(int fd)
set the replacement file descriptor, should be called in the constructor of derived classes
Definition IOIntercept.h:54
bool start()
start intercepting the stream.
Definition IOIntercept.h:38
StreamInterceptor(std::ostream &stream, FILE *fileObject)
Construct keeping a reference to the std::ostream and the file descriptor which are associated with t...
int m_replacementFD
Replacement file descriptor to be used while capturing.
Definition IOIntercept.h:71
int m_savedFD
Saved file descriptor: a duplicate of the file descriptor of m_fileObject.
Definition IOIntercept.h:69
bool finish()
stop intercepting the stream.
Definition IOIntercept.h:46
ELogLevel
Definition of the supported log levels.
Definition LogConfig.h:26
static LogSystem & Instance()
Static method to get a reference to the LogSystem instance.
Definition LogSystem.cc:28
Encapsulate all classes needed to intercept stdout and stderr.
Definition IOIntercept.h:21