Belle II Software  release-08-01-10
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 
19 namespace 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  {
178  if (StreamInterceptor::start()) {
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
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.
static CaptureStreamAbortHandler & getInstance()
Return the singleton instance.
Definition: IOIntercept.cc:43
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 & getStdErr() const
Return the captured stderr output if any.
Definition: IOIntercept.h:166
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
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