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