2 * Copyright (C) 2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) version 3.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include <syncevo/LogRedirect.h>
23 #include <syncevo/Logging.h>
25 #include <syncevo/util.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
36 #include <boost/algorithm/string/find_iterator.hpp>
37 #include <boost/algorithm/string/predicate.hpp>
38 #include <boost/noncopyable.hpp>
39 #include <boost/foreach.hpp>
49 #include <syncevo/declarations.h>
53 LogRedirect *LogRedirect::m_redirect;
54 std::set<std::string> LogRedirect::m_knownErrors;
56 void LogRedirect::abortHandler(int sig) throw()
58 // Don't know state of logging system, don't log here!
59 // SE_LOG_ERROR(NULL, "caught signal %d, shutting down", sig);
61 // Shut down redirection, also flushes to log. This involves
62 // unsafe calls. For example, we may have to allocate new memory,
63 // which deadlocks if glib detected memory corruption and
64 // called abort() (see FDO #76375).
66 // But flushing the log is the whole point of the abortHandler, so
67 // we can't just skip this. To handle cases where the work that we
68 // need to do fails, we set a timeout and let the process be
69 // killed that way. alarm() and sigaction() are async-signal-safe.
71 struct sigaction new_action, old_action;
72 memset(&new_action, 0, sizeof(new_action));
73 new_action.sa_handler = SIG_DFL; // Terminates the process.
74 sigemptyset(&new_action.sa_mask);
75 sigaction(SIGALRM, &new_action, &old_action);
78 RecMutex::Guard guard = lock();
80 m_redirect->restore();
84 // Raise same signal again. Because our handler
85 // is automatically removed, this will abort
90 void LogRedirect::init()
101 m_stderr.m_copy = -1;
102 m_stdout.m_original =
105 m_stdout.m_copy = -1;
107 const char *lines = getenv("SYNCEVOLUTION_SUPPRESS_ERRORS");
109 typedef boost::split_iterator<const char *> string_split_iterator;
110 string_split_iterator it =
111 boost::make_split_iterator(lines, boost::first_finder("\n", boost::is_iequal()));
112 while (it != string_split_iterator()) {
113 m_knownErrors.insert(std::string(it->begin(), it->end()));
118 // CONSOLEPRINTF in libsynthesis.
119 m_knownErrors.insert("SYSYNC Rejected with error:");
120 // libneon 'Request ends, status 207 class 2xx, error line:'
121 m_knownErrors.insert("xx, error line:\n");
122 // some internal Qt warning (?)
123 m_knownErrors.insert("Qt: Session management error: None of the authentication protocols specified are supported");
126 LogRedirect::LogRedirect(Mode mode, const char *filename)
130 if (!getenv("SYNCEVOLUTION_DEBUG")) {
131 redirect(STDERR_FILENO, m_stderr);
132 if (mode == STDERR_AND_STDOUT) {
133 redirect(STDOUT_FILENO, m_stdout);
135 fopen(filename, "w") :
136 fdopen(dup(m_stdout.m_copy), "w");
140 perror(filename ? filename : "LogRedirect fdopen");
142 } else if (filename) {
143 m_out = fopen(filename, "w");
148 // Separate FILE, will write into same file as normal output
149 // if a filename was given (for testing), otherwise to original
151 m_err = fdopen(dup((filename && m_out) ?
153 m_stderr.m_copy), "w");
156 // Modify process state while holding the Logger mutex.
157 RecMutex::Guard guard = lock();
159 SE_LOG_WARNING(NULL, "LogRedirect already instantiated?!");
163 if (!getenv("SYNCEVOLUTION_DEBUG")) {
164 struct sigaction new_action, old_action;
165 memset(&new_action, 0, sizeof(new_action));
166 new_action.sa_handler = abortHandler;
167 sigemptyset(&new_action.sa_mask);
168 // disable handler after it was called once
169 new_action.sa_flags = SA_RESETHAND;
170 // block signals while we handler is active
171 // to prevent recursive calls
172 sigaddset(&new_action.sa_mask, SIGABRT);
173 sigaddset(&new_action.sa_mask, SIGSEGV);
174 sigaddset(&new_action.sa_mask, SIGBUS);
175 sigaction(SIGABRT, &new_action, &old_action);
176 sigaction(SIGSEGV, &new_action, &old_action);
177 sigaction(SIGBUS, &new_action, &old_action);
179 m_processing = false;
182 LogRedirect::LogRedirect(ExecuteFlags flags)
186 // This instance does not modify process state and
187 // doesn't have to be thread-safe.
189 if (!(flags & EXECUTE_NO_STDERR)) {
190 redirect(STDERR_FILENO, m_stderr);
192 if (!(flags & EXECUTE_NO_STDOUT)) {
193 redirect(STDOUT_FILENO, m_stdout);
197 LogRedirect::~LogRedirect() throw()
199 RecMutex::Guard guard;
203 if (m_redirect == this) {
220 void LogRedirect::remove() throw()
225 void LogRedirect::removeRedirect() throw()
228 // We were forked. Ignore mutex (might be held by thread which was not
229 // forked) and restore the forked process' state to the one it was
230 // before setting up redirection.
232 // Do the minimal amount of work possible in restore(), i.e.,
233 // suppress the processing of streams.
234 m_redirect->m_streams = false;
236 m_redirect->restore(m_redirect->m_stdout);
237 m_redirect->restore(m_redirect->m_stderr);
241 void LogRedirect::restore() throw()
243 RecMutex::Guard guard;
256 m_processing = false;
259 void LogRedirect::messagev(const MessageOptions &options,
263 RecMutex::Guard guard = lock();
265 // check for other output first
267 if (!(options.m_flags & MessageOptions::ONLY_GLOBAL_LOG)) {
268 // Choose output channel: SHOW goes to original stdout,
269 // everything else to stderr.
270 LoggerStdout::write(options.m_level == SHOW ?
271 (m_out ? m_out : stdout) :
272 (m_err ? m_err : stderr),
273 options.m_level, getLevel(),
275 options.m_processName,
281 void LogRedirect::redirect(int original, FDs &fds) throw()
283 fds.m_original = original;
284 fds.m_write = fds.m_read = -1;
285 fds.m_copy = dup(fds.m_original);
286 if (fds.m_copy >= 0) {
288 // According to Stevens, Unix Network Programming, "Unix
289 // domain datagram sockets are similar to UDP sockets: the
290 // provide an *unreliable* datagram service that preserves
291 // record boundaries." (14.4 Socket Functions,
292 // p. 378). But unit tests showed that they do block on
293 // Linux and thus seem reliable. Not sure what the official
296 // To avoid the deadlock risk, we must use UDP. But when we
297 // want "reliable" behavior *and* detect that all output was
298 // processed, we have to use streams despite loosing
299 // the write() boundaries, because Unix domain datagram sockets
300 // do not flag "end of data".
302 #define USE_UNIX_DOMAIN_DGRAM 0
303 if (!socketpair(AF_LOCAL,
304 USE_UNIX_DOMAIN_DGRAM ? SOCK_DGRAM : SOCK_STREAM,
307 fds.m_write = sockets[0];
308 fds.m_read = sockets[1];
311 perror("LogRedirect::redirect() socketpair");
314 int write = socket(AF_INET, SOCK_DGRAM, 0);
316 int read = socket(AF_INET, SOCK_DGRAM, 0);
318 struct sockaddr_in addr;
319 memset(&addr, 0, sizeof(addr));
320 addr.sin_family = AF_INET;
321 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
323 for (int port = 1025; !bound && port < 10000; port++) {
324 addr.sin_port = htons(port);
325 if (!bind(read, (struct sockaddr *)&addr, sizeof(addr))) {
331 if (!connect(write, (struct sockaddr *)&addr, sizeof(addr))) {
332 if (dup2(write, fds.m_original) >= 0) {
338 perror("LogRedirect::redirect() dup2");
340 perror("LogRedirect::redirect connect");
350 perror("LogRedirect::redirect() dup");
354 void LogRedirect::restore(FDs &fds) throw()
356 if (!m_streams && fds.m_copy >= 0) {
357 // flush our own redirected output and process what they might have written
358 if (fds.m_original == STDOUT_FILENO) {
360 std::cout << std::flush;
363 std::cerr << std::flush;
367 dup2(fds.m_copy, fds.m_original);
370 if (fds.m_copy >= 0) {
373 if (fds.m_write >= 0) {
376 if (fds.m_read >= 0) {
384 bool LogRedirect::process(FDs &fds) throw()
387 bool data_read = false;
389 if (fds.m_read <= 0) {
393 ssize_t available = 0;
395 have_message = false;
397 // keep peeking at the data with increasing buffer sizes until
398 // we are sure that we don't truncate it
399 size_t newlen = std::max((size_t)1024, m_len);
402 if (newlen > m_len) {
403 void *buffer = realloc(m_buffer, newlen);
407 // We already read some data of a
408 // datagram. Give up on the rest of the data,
409 // process what we have below.
410 if ((size_t)available == m_len) {
411 // Need the byte for nul termination.
418 Exception::throwError(SE_HERE, "out of memory");
422 m_buffer = (char *)buffer;
426 // read, but leave space for nul byte;
427 // when using datagrams, we only peek here and remove the
428 // datagram below, without rereading the data
429 if (!USE_UNIX_DOMAIN_DGRAM && m_streams) {
430 available = recv(fds.m_read, m_buffer, m_len - 1, MSG_DONTWAIT);
431 if (available == 0) {
433 } else if (available == -1) {
434 if (errno == EAGAIN) {
435 // pretend that data was read, so that caller invokes us again
438 Exception::throwError(SE_HERE, "reading output", errno);
442 // data read, process it
447 available = recv(fds.m_read, m_buffer, m_len - 1, MSG_DONTWAIT|MSG_PEEK);
448 have_message = available >= 0;
450 if (available < (ssize_t)m_len - 1) {
453 // try again with twice the buffer
458 if (USE_UNIX_DOMAIN_DGRAM || !m_streams) {
459 // swallow packet, even if empty or we couldn't receive it
460 recv(fds.m_read, NULL, 0, MSG_DONTWAIT);
466 m_buffer[available] = 0;
467 // Now pass it to logger, with a level determined by
468 // the channel. This is the point where we can filter
471 Logger::Level level = Logger::DEV;
472 char *text = m_buffer;
474 if (fds.m_original == STDOUT_FILENO) {
475 // stdout: not sure what this could be, so show it
476 level = Logger::SHOW;
477 char *eol = strchr(text, '\n');
478 if (!m_stdoutData.empty()) {
479 // try to complete previous line, can be done
480 // if text contains a line break
482 m_stdoutData.append(text, eol - text);
484 Logger::instance().message(level, prefix.empty() ? NULL : &prefix,
486 "%s", m_stdoutData.c_str());
487 m_stdoutData.clear();
491 // avoid sending incomplete line at end of text,
492 // must be done when there is no line break or
493 // it is not at the end of the buffer
494 eol = strrchr(text, '\n');
495 if (eol != m_buffer + available - 1) {
497 m_stdoutData.append(eol + 1);
500 m_stdoutData.append(text);
505 // output might have been processed as part of m_stdoutData,
506 // don't log empty string below
510 } else if (fds.m_original == STDERR_FILENO) {
511 // stderr: not normally useful for users, so we
512 // can filter it more aggressively. For example,
513 // swallow extra line breaks, glib inserts those.
514 while (*text == '\n') {
517 const char *glib_debug_prefix = "** ("; // ** (client-test:875): WARNING **:
518 const char *glib_msg_prefix = "** Message:";
520 if ((!strncmp(text, glib_debug_prefix, strlen(glib_debug_prefix)) &&
521 strstr(text, " **:")) ||
522 !strncmp(text, glib_msg_prefix, strlen(glib_msg_prefix))) {
523 level = Logger::DEBUG;
529 // If the text contains the word "error", it probably
530 // is severe enough to show to the user, regardless of
531 // who produced it... except for errors suppressed
533 if (strcasestr(text, "error") &&
534 !ignoreError(text)) {
535 level = Logger::ERROR;
539 // avoid explicit newline at end of output,
540 // logging will add it for each message()
542 size_t len = strlen(text);
543 if (len > 0 && text[len - 1] == '\n') {
546 Logger::instance().message(level, prefix.empty() ? NULL : &prefix,
551 } while(have_message);
556 void LogRedirect::addIgnoreError(const std::string &error)
558 RecMutex::Guard guard = Logger::lock();
559 m_knownErrors.insert(error);
562 bool LogRedirect::ignoreError(const std::string &text)
564 RecMutex::Guard guard = Logger::lock();
565 BOOST_FOREACH(const std::string &entry, m_knownErrors) {
566 if (text.find(entry) != text.npos) {
573 void LogRedirect::process()
575 RecMutex::Guard guard;
578 // iterate until both sockets are closed by peer
585 if (m_stdout.m_read >= 0) {
586 FD_SET(m_stdout.m_read, &readfds);
587 FD_SET(m_stdout.m_read, &errfds);
588 maxfd = m_stdout.m_read;
590 if (m_stderr.m_read >= 0) {
591 FD_SET(m_stderr.m_read, &readfds);
592 FD_SET(m_stderr.m_read, &errfds);
593 if (m_stderr.m_read > maxfd) {
594 maxfd = m_stderr.m_read;
602 int res = select(maxfd + 1, &readfds, NULL, &errfds, NULL);
605 // fatal, cannot continue
606 Exception::throwError(SE_HERE, "waiting for output", errno);
610 // None ready? Try again.
613 if (m_stdout.m_read >= 0 && FD_ISSET(m_stdout.m_read, &readfds)) {
614 if (!process(m_stdout)) {
615 // Exact status of a Unix domain datagram socket upon close
616 // of the remote end is a bit uncertain. For TCP, we would end
617 // up here: marked by select as "ready for read", but no data -> EOF.
618 close(m_stdout.m_read);
619 m_stdout.m_read = -1;
622 if (m_stdout.m_read >= 0 && FD_ISSET(m_stdout.m_read, &errfds)) {
623 // But in practice, Unix domain sockets don't mark the stream
624 // as "closed". This is an attempt to detect that situation
625 // via the FDs exception status, but that also doesn't work.
626 close(m_stdout.m_read);
627 m_stdout.m_read = -1;
629 if (m_stderr.m_read >= 0 && FD_ISSET(m_stderr.m_read, &readfds)) {
630 if (!process(m_stderr)) {
631 close(m_stderr.m_read);
632 m_stderr.m_read = -1;
635 if (m_stderr.m_read >= 0 && FD_ISSET(m_stderr.m_read, &errfds)) {
636 close(m_stderr.m_read);
637 m_stderr.m_read = -1;
654 // avoid hanging onto excessive amounts of memory
655 m_len = std::min((size_t)(4 * 1024), m_len);
656 m_buffer = (char *)realloc(m_buffer, m_len);
661 m_processing = false;
666 void LogRedirect::flush() throw()
668 RecMutex::Guard guard = lock();
671 if (!m_stdoutData.empty()) {
673 std::swap(buffer, m_stdoutData);
674 Logger::instance().message(Logger::SHOW, NULL,
676 "%s", buffer.c_str());
681 #ifdef ENABLE_UNIT_TESTS
683 class LogRedirectTest : public CppUnit::TestFixture {
684 CPPUNIT_TEST_SUITE(LogRedirectTest);
685 CPPUNIT_TEST(simple);
686 CPPUNIT_TEST(largeChunk);
687 CPPUNIT_TEST(streams);
688 CPPUNIT_TEST(overload);
692 CPPUNIT_TEST_SUITE_END();
695 * redirect stdout/stderr, then intercept the log messages and
696 * store them for inspection
698 class LogBuffer : public Logger, private boost::noncopyable
701 std::stringstream m_streams[DEBUG + 1];
702 PushLogger<LogRedirect> m_redirect;
704 LogBuffer(LogRedirect::Mode mode = LogRedirect::STDERR_AND_STDOUT)
706 m_redirect.reset(new LogRedirect(mode));
707 addLogger(boost::shared_ptr<Logger>(this, NopDestructor()));
715 virtual void messagev(const MessageOptions &options,
719 CPPUNIT_ASSERT(options.m_level <= DEBUG && options.m_level >= 0);
720 m_streams[options.m_level] << StringPrintfV(format, args);
729 static const char *simpleMessage = "hello world";
730 CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(simpleMessage), write(STDOUT_FILENO, simpleMessage, strlen(simpleMessage)));
731 buffer.m_redirect->flush();
733 CPPUNIT_ASSERT_EQUAL(buffer.m_streams[Logger::SHOW].str(), std::string(simpleMessage));
741 large.append(60 * 1024, 'h');
742 CPPUNIT_ASSERT_EQUAL((ssize_t)large.size(), write(STDOUT_FILENO, large.c_str(), large.size()));
743 buffer.m_redirect->flush();
745 CPPUNIT_ASSERT_EQUAL(large.size(), buffer.m_streams[Logger::SHOW].str().size());
746 CPPUNIT_ASSERT_EQUAL(large, buffer.m_streams[Logger::SHOW].str());
753 static const char *simpleMessage = "hello world";
754 CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(simpleMessage), write(STDOUT_FILENO, simpleMessage, strlen(simpleMessage)));
755 static const char *errorMessage = "such a cruel place";
756 CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(errorMessage), write(STDERR_FILENO, errorMessage, strlen(errorMessage)));
758 // process() keeps unfinished STDOUT lines buffered
759 buffer.m_redirect->process();
760 CPPUNIT_ASSERT_EQUAL(std::string(errorMessage), buffer.m_streams[Logger::DEV].str());
761 CPPUNIT_ASSERT_EQUAL(string(""), buffer.m_streams[Logger::SHOW].str());
763 // flush() makes them available
764 buffer.m_redirect->flush();
765 CPPUNIT_ASSERT_EQUAL(std::string(errorMessage), buffer.m_streams[Logger::DEV].str());
766 CPPUNIT_ASSERT_EQUAL(std::string(simpleMessage), buffer.m_streams[Logger::SHOW].str());
774 large.append(1024, 'h');
775 for (int i = 0; i < 4000; i++) {
776 CPPUNIT_ASSERT_EQUAL((ssize_t)large.size(), write(STDOUT_FILENO, large.c_str(), large.size()));
778 buffer.m_redirect->flush();
780 CPPUNIT_ASSERT(buffer.m_streams[Logger::SHOW].str().size() > large.size());
789 static const char *filename = "LogRedirectTest_glib.out";
790 int new_stdout = open(filename, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU);
792 // check that intercept all glib message and don't print anything to stdout
793 int orig_stdout = -1;
795 // need to restore the current state below; would be nice
796 // to query it instead of assuming that Logger::glogFunc
797 // is the current log handler
798 g_log_set_default_handler(g_log_default_handler, NULL);
800 orig_stdout = dup(STDOUT_FILENO);
801 dup2(new_stdout, STDOUT_FILENO);
803 LogBuffer buffer(LogRedirect::STDERR);
805 fprintf(stdout, "normal message stdout\n");
808 fprintf(stderr, "normal message stderr\n");
811 // ** (process:13552): WARNING **: test warning
812 g_warning("test warning");
813 // ** Message: test message
814 g_message("test message");
815 // ** (process:13552): CRITICAL **: test critical
816 g_critical("test critical");
819 // ** (process:13552): DEBUG: test debug
820 g_debug("test debug");
822 buffer.m_redirect->process();
824 std::string error = buffer.m_streams[Logger::ERROR].str();
825 std::string warning = buffer.m_streams[Logger::WARNING].str();
826 std::string show = buffer.m_streams[Logger::SHOW].str();
827 std::string info = buffer.m_streams[Logger::INFO].str();
828 std::string dev = buffer.m_streams[Logger::DEV].str();
829 std::string debug = buffer.m_streams[Logger::DEBUG].str();
830 CPPUNIT_ASSERT_EQUAL(string(""), error);
831 CPPUNIT_ASSERT_EQUAL(string(""), warning);
832 CPPUNIT_ASSERT_EQUAL(string(""), show);
833 CPPUNIT_ASSERT_EQUAL(string(""), info);
834 CPPUNIT_ASSERT_EQUAL(string(""), error);
835 CPPUNIT_ASSERT(dev.find("normal message stderr") != dev.npos);
836 CPPUNIT_ASSERT(debug.find("test warning") != debug.npos);
838 g_log_set_default_handler(Logger::glogFunc, NULL);
839 dup2(orig_stdout, STDOUT_FILENO);
842 g_log_set_default_handler(Logger::glogFunc, NULL);
843 dup2(orig_stdout, STDOUT_FILENO);
845 lseek(new_stdout, 0, SEEK_SET);
847 ssize_t l = read(new_stdout, out, sizeof(out) - 1);
848 CPPUNIT_ASSERT(l > 0);
850 CPPUNIT_ASSERT(boost::starts_with(std::string(out), "normal message stdout"));
855 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(LogRedirectTest);
857 #endif // ENABLE_UNIT_TESTS