syncevolution/src/dbus/server/sync-helper.cpp

223 lines
8.3 KiB
C++

/*
* Copyright (C) 2012 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "session-helper.h"
#include <syncevo/Logging.h>
#include <syncevo/ForkExec.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/SyncContext.h>
#include <syncevo/LogRedirect.h>
#include <syncevo/LogDLT.h>
using namespace SyncEvo;
using namespace GDBusCXX;
namespace {
GMainLoop *loop = NULL;
int logLevelDBus = Logger::INFO;
// that one is actually never called. probably a bug in ForkExec - it should
// call m_onFailure instead of throwing an exception
void onFailure(const std::string &error, bool &failed) throw ()
{
SE_LOG_DEBUG(NULL, "failure, quitting now: %s", error.c_str());
failed = true;
}
void onConnect(const DBusConnectionPtr &conn,
const boost::shared_ptr<ForkExecChild> &forkexec,
boost::shared_ptr<SessionHelper> &helper)
{
helper.reset(new SessionHelper(loop, conn, forkexec));
helper->activate();
helper->setDBusLogLevel(Logger::Level(logLevelDBus));
}
void onAbort()
{
g_main_loop_quit(loop);
}
} // anonymous namespace
/**
* This program is a helper of syncevo-dbus-server which provides the
* Connection and Session DBus interfaces and runs individual sync
* sessions. It is only intended to be started by syncevo-dbus-server,
*/
int main(int argc, char **argv, char **envp)
{
// delay the client for debugging purposes
const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY");
if (delay) {
Sleep(atoi(delay));
}
if (getenv("SYNCEVOLUTION_DBUS_HELPER_VGDB")) {
// Trigger an error in valgrind. Use in combination with
// --vgdb-error=1 --vgdb=yes (note the =1!) to attach when
// the process is running.
void *dummy = malloc(1);
free(dummy);
// cppcheck-suppress deallocDealloc
// cppcheck-suppress doubleFree
// cppcheck-suppress uninitvar
free(dummy);
}
SyncContext::initMain("syncevo-dbus-helper");
loop = g_main_loop_new(NULL, FALSE);
// Suspend and abort are signaled via SIGINT/SIGTERM
// respectively. SuspendFlags handle that for us.
// SIGURG is used as acknowledgement from parent to us that we
// can quite.
SuspendFlags &s = SuspendFlags::getSuspendFlags();
s.setLevel(Logger::DEV);
boost::shared_ptr<SuspendFlags::Guard> guard = s.activate((1<<SIGINT)|(1<<SIGTERM)|(1<<SIGURG));
bool debug = getenv("SYNCEVOLUTION_DEBUG");
// Redirect both stdout and stderr. The only code
// writing to it should be third-party libraries
// which are unaware of the SyncEvolution logging system.
// Redirecting is useful to get such output into our
// sync logfile, once we have one.
boost::shared_ptr<LogRedirect> redirect;
PushLogger<LogRedirect> pushRedirect;
if (!debug) {
redirect.reset(new LogRedirect(LogRedirect::STDERR_AND_STDOUT));
pushRedirect.reset(redirect);
}
#ifdef USE_DLT
// Set by syncevo-dbus-server for us.
bool useDLT = getenv("SYNCEVOLUTION_USE_DLT") != NULL;
PushLogger<LoggerDLT> loggerdlt;
if (useDLT) {
loggerdlt.reset(new LoggerDLT(DLT_SYNCEVO_DBUS_HELPER_ID, "SyncEvolution local sync helper"));
}
#endif
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
try {
static GOptionEntry entries[] = {
{ "dbus-verbosity", 'v', 0, G_OPTION_ARG_INT, &logLevelDBus,
"Choose amount of output via D-Bus signals with Logger::Level; default is INFO = 3.",
"level" },
{ NULL }
};
GErrorCXX gerror;
static GOptionContext *context = g_option_context_new("- SyncEvolution D-Bus Helper");
g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE);
bool success = g_option_context_parse(context, &argc, &argv, gerror);
if (!success) {
gerror.throwError(SE_HERE, "parsing command line options");
}
if (debug) {
Logger::instance().setLevel(Logger::DEBUG);
Logger::setProcessName(StringPrintf("syncevo-dbus-helper-%ld", (long)getpid()));
}
// syncevo-dbus-helper produces the output which is of most
// interest to users, and therefore it is allowed to print
// [INFO/ERROR/DEBUG] without including a process name in
// the brackets, like the other processes do.
// Logger::setProcessName("syncevo-dbus-helper");
boost::shared_ptr<ForkExecChild> forkexec = ForkExecChild::create();
boost::shared_ptr<SessionHelper> helper;
bool failed = false;
forkexec->m_onConnect.connect(boost::bind(onConnect, _1,
boost::cref(forkexec),
boost::ref(helper)));
forkexec->m_onFailure.connect(boost::bind(onFailure, _2, boost::ref(failed)));
forkexec->connect();
// Run until we are connected, failed or get interrupted.
boost::signals2::connection c =
s.m_stateChanged.connect(boost::bind(&onAbort));
SE_LOG_DEBUG(NULL, "helper (pid %d) finished setup, waiting for parent connection", getpid());
while (true) {
if (s.getState() != SuspendFlags::NORMAL) {
// not an error, someone wanted us to stop
SE_LOG_DEBUG(NULL, "aborted via signal while starting, terminating");
// tell caller that we aborted by terminating via the SIGTERM signal
return 0;
}
if (failed) {
SE_THROW("parent connection failed");
}
if (helper) {
// done
break;
}
// wait
g_main_loop_run(loop);
}
// Now we no longer care whether the parent connection fails.
// TODO: What if the parent fails to call us and instead closes his
// side of the connection? Will we notice and abort?
c.disconnect();
SE_LOG_DEBUG(NULL, "connected to parent, run helper");
helper->run();
SE_LOG_DEBUG(NULL, "helper operation done");
helper.reset();
SE_LOG_DEBUG(NULL, "helper destroyed");
// Wait for confirmation from parent that we are allowed to
// quit. This is necessary because we might have pending IO
// for the parent, like D-Bus method replies.
while (true) {
if (s.getReceivedSignals() & (1<<SIGURG)) {
// not an error, someone wanted us to stop
SE_LOG_DEBUG(NULL, "aborted via signal after completing operation, terminating");
return 0;
}
if (forkexec->getState() != ForkExecChild::CONNECTED) {
// No point running any longer, parent is gone.
//
// This can occur during normal operations, so don't
// treat it as an error:
// - we send final method response
// - parent signals us and closes the connection
// - our event loop processes these two events such
// that we see the "not connected" one first
SE_LOG_DEBUG(NULL, "parent has quit, terminating");
return 0;
}
g_main_context_iteration(NULL, true);
}
} catch ( const std::exception &ex ) {
SE_LOG_ERROR(NULL, "helper quitting with exception: %s", ex.what());
} catch (...) {
SE_LOG_ERROR(NULL, "helper quitting: unknown error");
}
return 1;
}