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 }
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 }
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}
Small class to handle std::abort() calls by external libraries.
Definition: IOIntercept.cc:34
void removeObject(CaptureStream *obj)
Remove a CaptureStream object to no longer guard against SIGABRT.
Definition: IOIntercept.cc:51
void addObject(CaptureStream *obj)
Add a CaptureStream object to guard against SIGABRT.
Definition: IOIntercept.cc:49
static CaptureStreamAbortHandler & getInstance()
Return the singleton instance.
Definition: IOIntercept.cc:43
CaptureStreamAbortHandler(CaptureStreamAbortHandler &&)=delete
Singleton, no move construction.
static void handle(int signal)
signal handler: print all pending redirection buffers and exit
Definition: IOIntercept.cc:65
std::set< CaptureStream * > m_objects
list of all active stream redirections
Definition: IOIntercept.cc:62
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.
Definition: IOIntercept.h:103
CaptureStream(std::ostream &stream, FILE *fileObject)
Create a StreamInterceptor which writes into a pipe.
Definition: IOIntercept.cc:156
std::string m_outputStr
string with the output, only filled after finish()
Definition: IOIntercept.h:119
int m_pipeReadFD
file descriptor of the read end of the pipe
Definition: IOIntercept.h:117
bool start()
Start intercepting the output.
Definition: IOIntercept.cc:176
~CaptureStream()
Close file descriptors.
Definition: IOIntercept.cc:167
bool finish()
Restore the stream and get the output from the pipe.
Definition: IOIntercept.cc:185
DiscardStream(std::ostream &stream, FILE *fileObject)
Create StreamInterceptor which will redirect to /dev/null.
Definition: IOIntercept.cc:145
const std::string & getStdOut() const
Return the captured stdout output if any.
Definition: IOIntercept.h:160
bool finish()
Finish intercepting the output.
Definition: IOIntercept.h:158
const std::string & getStdErr() const
Return the captured stderr output if any.
Definition: IOIntercept.h:166
LogConfig::ELogLevel m_stderrLevel
severity of the log message to be emitted for output on stderr
Definition: IOIntercept.h:268
const std::string m_name
Name of the output producing tool/library.
Definition: IOIntercept.h:262
int m_stderrDebugLevel
debug level for the log message to be emitted for output on stderr if m_stderrLevel is c_Debug
Definition: IOIntercept.h:272
LogConfig::ELogLevel m_stdoutLevel
severity of the log message to be emitted for output on stdout
Definition: IOIntercept.h:266
bool finish()
Finish the capture and emit the message if output has appeard on stdout or stderr.
Definition: IOIntercept.cc:236
int m_stdoutDebugLevel
debug level for the log message to be emitted for output on stdout if m_stdoutLevel is c_Debug
Definition: IOIntercept.h:270
std::string m_indent
Identation to add to the beginning of each line of output.
Definition: IOIntercept.h:264
Base class with all necessary features to intercept output to a file descriptor.
Definition: IOIntercept.h:23
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.
Definition: IOIntercept.cc:102
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.
Definition: IOIntercept.cc:120
~StreamInterceptor()
close file descriptors
Definition: IOIntercept.cc:95
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...
Definition: IOIntercept.cc:88
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:31
Encapsulate all classes needed to intercept stdout and stderr.
Definition: IOIntercept.h:21