2 * Copyright (C) 2010 Patrick Ohly
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 #include <syncevo/LocalTransportAgent.h>
21 #include <syncevo/SyncContext.h>
22 #include <syncevo/SyncML.h>
23 #include <syncevo/LogRedirect.h>
24 #include <syncevo/StringDataBlob.h>
25 #include <syncevo/IniConfigNode.h>
26 #include <syncevo/GLibSupport.h>
27 #include <syncevo/DBusTraits.h>
28 #include <syncevo/SuspendFlags.h>
29 #include <syncevo/LogRedirect.h>
30 #include <syncevo/BoostHelper.h>
32 #include <synthesis/syerror.h>
35 #include <sys/socket.h>
36 #include <sys/types.h>
43 #include <syncevo/declarations.h>
46 class NoopAgentDestructor
49 void operator () (TransportAgent *agent) throw() {}
52 LocalTransportAgent::LocalTransportAgent(SyncContext *server,
53 const std::string &clientContext,
56 m_clientContext(SyncConfig::normalizeConfigString(clientContext)),
59 GMainLoopCXX(static_cast<GMainLoop *>(loop), ADD_REF) :
60 GMainLoopCXX(g_main_loop_new(NULL, false), TRANSFER_REF))
64 boost::shared_ptr<LocalTransportAgent> LocalTransportAgent::create(SyncContext *server,
65 const std::string &clientContext,
68 boost::shared_ptr<LocalTransportAgent> self(new LocalTransportAgent(server, clientContext, loop));
73 LocalTransportAgent::~LocalTransportAgent()
77 void LocalTransportAgent::start()
79 // compare normalized context names to detect forbidden sync
80 // within the same context; they could be set up, but are more
81 // likely configuration mistakes
83 SyncConfig::splitConfigString(m_clientContext, peer, context);
85 SE_THROW(StringPrintf("invalid local sync URL: '%s' references a peer config, should point to a context like @%s instead",
86 m_clientContext.c_str(),
89 // TODO (?): check that there are no conflicts between the active
90 // sources. The old "contexts must be different" check achieved that
91 // via brute force (because by definition, databases from different
92 // contexts are meant to be independent), but it was too coarse
93 // and ruled out valid configurations.
94 // if (m_clientContext == m_server->getContextName()) {
95 // SE_THROW(StringPrintf("invalid local sync inside context '%s', need second context with different databases", context.c_str()));
99 SE_THROW("local transport already started");
102 m_forkexec = ForkExecParent::create("syncevo-local-sync");
103 m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgent::onChildConnect, this, _1));
104 // fatal problems, including quitting child with non-zero status
105 m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgent::onFailure, this, _2));
106 // watch onQuit and remember whether the child is still running,
107 // because it might quit prematurely with a zero return code (for
108 // example, when an unexpected slow sync is detected)
109 m_forkexec->m_onQuit.connect(boost::bind(&LocalTransportAgent::onChildQuit, this, _1));
114 * Uses the D-Bus API provided by LocalTransportParent.
116 class LocalTransportParent : private GDBusCXX::DBusRemoteObject
119 static const char *path() { return "/"; }
120 static const char *interface() { return "org.syncevolution.localtransport.parent"; }
121 static const char *destination() { return "local.destination"; }
122 static const char *askPasswordName() { return "AskPassword"; }
123 static const char *storeSyncReportName() { return "StoreSyncReport"; }
125 LocalTransportParent(const GDBusCXX::DBusConnectionPtr &conn) :
126 GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
127 m_askPassword(*this, askPasswordName()),
128 m_storeSyncReport(*this, storeSyncReportName())
131 /** LocalTransportAgent::askPassword() */
132 GDBusCXX::DBusClientCall1<std::string> m_askPassword;
133 /** LocalTransportAgent::storeSyncReport() */
134 GDBusCXX::DBusClientCall0 m_storeSyncReport;
138 * Uses the D-Bus API provided by LocalTransportAgentChild.
140 class LocalTransportChild : public GDBusCXX::DBusRemoteObject
143 static const char *path() { return "/"; }
144 static const char *interface() { return "org.syncevolution.localtransport.child"; }
145 static const char *destination() { return "local.destination"; }
146 static const char *logOutputName() { return "LogOutput"; }
147 static const char *startSyncName() { return "StartSync"; }
148 static const char *sendMsgName() { return "SendMsg"; }
150 LocalTransportChild(const GDBusCXX::DBusConnectionPtr &conn) :
151 GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
152 m_logOutput(*this, logOutputName(), false),
153 m_startSync(*this, startSyncName()),
154 m_sendMsg(*this, sendMsgName())
158 * information from server config about active sources:
159 * mapping is from server source names to child source name + sync mode
160 * (again as set on the server side!)
162 typedef std::map<std::string, StringPair> ActiveSources_t;
163 /** use this to send a message back from child to parent */
164 typedef boost::shared_ptr< GDBusCXX::Result2< std::string, GDBusCXX::DBusArray<uint8_t> > > ReplyPtr;
166 /** log output with level and message; process name will be added by parent */
167 GDBusCXX::SignalWatch2<string, string> m_logOutput;
169 /** LocalTransportAgentChild::startSync() */
170 GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_startSync;
171 /** LocalTransportAgentChild::sendMsg() */
172 GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_sendMsg;
176 void LocalTransportAgent::logChildOutput(const std::string &level, const std::string &message)
178 Logger::MessageOptions options(Logger::strToLevel(level.c_str()));
179 options.m_processName = &m_clientContext;
180 SyncEvo::Logger::instance().messageWithOptions(options, "%s", message.c_str());
183 void LocalTransportAgent::onChildConnect(const GDBusCXX::DBusConnectionPtr &conn)
185 SE_LOG_DEBUG(NULL, "child is ready");
186 m_parent.reset(new GDBusCXX::DBusObjectHelper(conn,
187 LocalTransportParent::path(),
188 LocalTransportParent::interface(),
189 GDBusCXX::DBusObjectHelper::Callback_t(),
191 m_parent->add(this, &LocalTransportAgent::askPassword, LocalTransportParent::askPasswordName());
192 m_parent->add(this, &LocalTransportAgent::storeSyncReport, LocalTransportParent::storeSyncReportName());
193 m_parent->activate();
194 m_child.reset(new LocalTransportChild(conn));
195 m_child->m_logOutput.activate(boost::bind(&LocalTransportAgent::logChildOutput, this, _1, _2));
197 // now tell child what to do
198 LocalTransportChild::ActiveSources_t sources;
199 BOOST_FOREACH(const string &sourceName, m_server->getSyncSources()) {
200 SyncSourceNodes nodes = m_server->getSyncSourceNodesNoTracking(sourceName);
201 SyncSourceConfig source(sourceName, nodes);
202 std::string sync = source.getSync();
203 if (sync != "disabled") {
204 string targetName = source.getURINonEmpty();
205 sources[sourceName] = std::make_pair(targetName, sync);
208 m_child->m_startSync.start(m_clientContext,
209 StringPair(m_server->getConfigName(),
210 m_server->isEphemeral() ?
212 m_server->getRootPath()),
213 static_cast<std::string>(m_server->getLogDir()),
214 m_server->getDoLogging(),
215 StringPair(m_server->getSyncUsername(),
216 m_server->getSyncPassword()),
217 m_server->getConfigProps(),
219 boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
222 void LocalTransportAgent::onFailure(const std::string &error)
225 g_main_loop_quit(m_loop.get());
227 SE_LOG_ERROR(NULL, "local transport failed: %s", error.c_str());
232 void LocalTransportAgent::onChildQuit(int status)
234 SE_LOG_DEBUG(NULL, "child process has quit with status %d", status);
235 g_main_loop_quit(m_loop.get());
238 static void GotPassword(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply,
239 const std::string &password)
241 reply->done(password);
244 static void PasswordException(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply)
246 // TODO: refactor, this is the same as dbusErrorCallback
248 // If there is no pending exception, the process will abort
249 // with "terminate called without an active exception";
250 // dbusErrorCallback() should only be called when there is
251 // a pending exception.
252 // TODO: catch this misuse in a better way
255 // let D-Bus parent log the error
256 std::string explanation;
257 Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
258 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", explanation));
262 void LocalTransportAgent::askPassword(const std::string &passwordName,
263 const std::string &descr,
264 const ConfigPasswordKey &key,
265 const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply)
267 // pass that work to our own SyncContext and its UI - currently blocks
268 SE_LOG_DEBUG(NULL, "local sync parent: asked for password %s, %s",
269 passwordName.c_str(),
273 m_server->getUserInterfaceNonNull().askPasswordAsync(passwordName, descr, key,
274 // TODO refactor: use dbus-callbacks.h
275 boost::bind(GotPassword,
278 boost::bind(PasswordException,
281 SE_LOG_DEBUG(NULL, "local sync parent: password request failed because no m_server");
282 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
283 "not connected to UI"));
286 PasswordException(reply);
290 void LocalTransportAgent::storeSyncReport(const std::string &report)
292 SE_LOG_DEBUG(NULL, "got child sync report:\n%s",
294 m_clientReport = SyncReport(report);
297 void LocalTransportAgent::getClientSyncReport(SyncReport &report)
299 report = m_clientReport;
302 void LocalTransportAgent::setContentType(const std::string &type)
304 m_contentType = type;
307 // workaround for limitations of bind+signals when used together with plain GMainLoop pointer
308 // (pointer to undefined struct)
309 static void gMainLoopQuit(GMainLoopCXX *loop)
311 g_main_loop_quit(loop->get());
314 void LocalTransportAgent::shutdown()
316 SE_LOG_DEBUG(NULL, "parent is shutting down");
318 // block until child is done
319 boost::signals2::scoped_connection c(m_forkexec->m_onQuit.connect(boost::bind(gMainLoopQuit,
321 // don't kill the child here - we expect it to complete by
322 // itself at some point
323 // TODO: how do we detect a child which gets stuck after its last
324 // communication with the parent?
325 // m_forkexec->stop();
326 while (m_forkexec->getState() != ForkExecParent::TERMINATED) {
327 SE_LOG_DEBUG(NULL, "waiting for child to stop");
328 g_main_loop_run(m_loop.get());
337 void LocalTransportAgent::send(const char *data, size_t len)
341 m_child->m_sendMsg.start(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)),
342 boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
345 SE_THROW_EXCEPTION(TransportException,
346 "cannot send message because child process is gone");
350 void LocalTransportAgent::storeReplyMsg(const std::string &contentType,
351 const GDBusCXX::DBusArray<uint8_t> &reply,
352 const std::string &error)
354 m_replyMsg.assign(reinterpret_cast<const char *>(reply.second),
356 m_replyContentType = contentType;
358 m_status = GOT_REPLY;
360 // Only an error if the client hasn't shut down normally.
361 if (m_clientReport.empty()) {
362 SE_LOG_ERROR(NULL, "sending message to child failed: %s", error.c_str());
366 g_main_loop_quit(m_loop.get());
369 void LocalTransportAgent::cancel()
372 SE_LOG_DEBUG(NULL, "killing local transport child in cancel()");
378 TransportAgent::Status LocalTransportAgent::wait(bool noReply)
380 if (m_status == ACTIVE) {
381 // need next message; for noReply == true we are done
385 while (m_status == ACTIVE) {
386 SE_LOG_DEBUG(NULL, "waiting for child to send message");
388 m_forkexec->getState() == ForkExecParent::TERMINATED) {
390 if (m_clientReport.getStatus() != STATUS_OK &&
391 m_clientReport.getStatus() != STATUS_HTTP_OK) {
392 // Report that status, with an error message which contains the explanation
393 // added to the client's error. We are a bit fuzzy about matching the status:
394 // 10xxx matches xxx and vice versa.
395 int status = m_clientReport.getStatus();
396 if (status >= sysync::LOCAL_STATUS_CODE && status <= sysync::LOCAL_STATUS_CODE_END) {
397 status -= sysync::LOCAL_STATUS_CODE;
399 std::string explanation = StringPrintf("failure on target side %s of local sync",
400 m_clientContext.c_str());
401 static const pcrecpp::RE re("\\((?:local|remote), status (\\d+)\\): (.*)");
403 std::string clientExplanation;
404 if (re.PartialMatch(m_clientReport.getError(), &clientStatus, &clientExplanation) &&
405 (status == clientStatus ||
406 status == clientStatus - sysync::LOCAL_STATUS_CODE)) {
408 explanation += clientExplanation;
410 SE_THROW_EXCEPTION_STATUS(StatusException,
412 m_clientReport.getStatus());
414 SE_THROW_EXCEPTION(TransportException,
415 "child process quit without sending its message");
418 g_main_loop_run(m_loop.get());
425 void LocalTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType)
427 if (m_status != GOT_REPLY) {
428 SE_THROW("internal error, no reply available");
430 contentType = m_replyContentType;
431 data = m_replyMsg.c_str();
432 len = m_replyMsg.size();
435 void LocalTransportAgent::setTimeout(int seconds)
437 // setTimeout() was meant for unreliable transports like HTTP
438 // which cannot determine whether the peer is still alive. The
439 // LocalTransportAgent uses sockets and will notice when a peer
440 // dies unexpectedly, so timeouts should never be necessary.
442 // Quite the opposite, because the "client" in a local sync
443 // with WebDAV on the client side can be quite slow, incorrect
444 // timeouts were seen where the client side took longer than
445 // the default timeout of 5 minutes to process a message and
448 // Therefore we ignore the request to set a timeout here and thus
449 // local send/receive operations are allowed to continue for as
450 // long as they like.
452 // m_timeoutSeconds = seconds;
455 class LocalTransportUI : public UserInterface
457 boost::shared_ptr<LocalTransportParent> m_parent;
460 LocalTransportUI(const boost::shared_ptr<LocalTransportParent> &parent) :
464 /** implements password request by asking the parent via D-Bus */
465 virtual string askPassword(const string &passwordName,
467 const ConfigPasswordKey &key)
469 SE_LOG_DEBUG(NULL, "local transport child: requesting password %s, %s via D-Bus",
470 passwordName.c_str(),
472 std::string password;
474 bool havePassword = false;
475 m_parent->m_askPassword.start(passwordName, descr, key,
476 boost::bind(&LocalTransportUI::storePassword, this,
477 boost::ref(password), boost::ref(error),
478 boost::ref(havePassword),
480 SuspendFlags &s = SuspendFlags::getSuspendFlags();
481 while (!havePassword) {
482 if (s.getState() != SuspendFlags::NORMAL) {
483 SE_THROW_EXCEPTION_STATUS(StatusException,
484 StringPrintf("User did not provide the '%s' password.",
485 passwordName.c_str()),
486 SyncMLStatus(sysync::LOCERR_USERABORT));
488 g_main_context_iteration(NULL, true);
490 if (!error.empty()) {
491 Exception::tryRethrowDBus(error);
492 SE_THROW(StringPrintf("retrieving password failed: %s", error.c_str()));
497 virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { SE_THROW("not implemented"); return false; }
498 virtual void readStdin(std::string &content) { SE_THROW("not implemented"); }
501 void storePassword(std::string &res, std::string &errorRes, bool &haveRes, const std::string &password, const std::string &error)
503 if (!error.empty()) {
504 SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request failed: %s",
508 SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request succeeded");
515 static void abortLocalSync(int sigterm)
517 // logging anything here is not safe (our own logging system might
518 // have been interrupted by the SIGTERM and thus be in an inconsistent
519 // state), but let's try it anyway
520 SE_LOG_INFO(NULL, "local sync child shutting down due to SIGTERM");
521 // raise the signal again after disabling the handler, to ensure that
522 // the exit status is "killed by signal xxx" - good because then
523 // the whoever killed used gets the information that we didn't die for
525 signal(sigterm, SIG_DFL);
530 * Provides the "LogOutput" signal.
531 * LocalTransportAgentChild adds the method implementations
532 * before activating it.
534 class LocalTransportChildImpl : public GDBusCXX::DBusObjectHelper
537 LocalTransportChildImpl(const GDBusCXX::DBusConnectionPtr &conn) :
538 GDBusCXX::DBusObjectHelper(conn,
539 LocalTransportChild::path(),
540 LocalTransportChild::interface(),
541 GDBusCXX::DBusObjectHelper::Callback_t(),
543 m_logOutput(*this, LocalTransportChild::logOutputName())
548 GDBusCXX::EmitSignal2<std::string,
550 true /* ignore transmission failures */> m_logOutput;
553 class ChildLogger : public Logger
555 std::auto_ptr<LogRedirect> m_parentLogger;
556 boost::weak_ptr<LocalTransportChildImpl> m_child;
559 ChildLogger(const boost::shared_ptr<LocalTransportChildImpl> &child) :
560 m_parentLogger(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)),
565 m_parentLogger.reset();
569 * Write message into our own log and send to parent.
571 virtual void messagev(const MessageOptions &options,
575 if (options.m_level <= m_parentLogger->getLevel()) {
576 m_parentLogger->process();
577 boost::shared_ptr<LocalTransportChildImpl> child = m_child.lock();
579 // prefix is used to set session path
580 // for general server output, the object path field is dbus server
581 // the object path can't be empty for object paths prevent using empty string.
582 string strLevel = Logger::levelToStr(options.m_level);
583 string log = StringPrintfV(format, args);
584 child->m_logOutput(strLevel, log);
590 class LocalTransportAgentChild : public TransportAgent
592 /** final return code of our main(): non-zero indicates that we need to shut down */
596 * sync report for client side of the local sync
598 SyncReport m_clientReport;
600 /** used to capture libneon output */
601 boost::scoped_ptr<LogRedirect> m_parentLogger;
604 * provides connection to parent, created in constructor
606 boost::shared_ptr<ForkExecChild> m_forkexec;
609 * proxy for the parent's D-Bus API in onConnect()
611 boost::shared_ptr<LocalTransportParent> m_parent;
614 * our D-Bus interface, created in onConnect()
616 boost::shared_ptr<LocalTransportChildImpl> m_child;
619 * sync context, created in Sync() D-Bus call
621 boost::scoped_ptr<SyncContext> m_client;
624 * use this D-Bus result handle to send a message from child to parent
625 * in response to sync() or (later) sendMsg()
627 LocalTransportChild::ReplyPtr m_msgToParent;
628 void setMsgToParent(const LocalTransportChild::ReplyPtr &reply,
629 const std::string &reason)
632 m_msgToParent->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
633 "cancelling message: " + reason));
635 m_msgToParent = reply;
638 /** content type for message to parent */
639 std::string m_contentType;
642 * message from parent
644 std::string m_message;
647 * content type of message from parent
649 std::string m_messageType;
652 * true after parent has received sync report, or sending failed
657 * INACTIVE when idle,
658 * ACTIVE after having sent and while waiting for next message,
659 * GOT_REPLY when we have a message to be processed,
660 * FAILED when permanently broken
665 * one loop run + error checking
667 void step(const std::string &status)
669 SE_LOG_DEBUG(NULL, "local transport: %s", status.c_str());
671 m_forkexec->getState() == ForkExecChild::DISCONNECTED) {
672 SE_THROW("local transport child no longer has a parent, terminating");
674 g_main_context_iteration(NULL, true);
676 SE_THROW("local transport child encountered a problem, terminating");
680 static void onParentQuit()
682 // Never free this state blocker. We can only abort and
684 static boost::shared_ptr<SuspendFlags::StateBlocker> abortGuard;
685 SE_LOG_ERROR(NULL, "sync parent quit unexpectedly");
686 abortGuard = SuspendFlags::getSuspendFlags().abort();
689 void onConnect(const GDBusCXX::DBusConnectionPtr &conn)
691 SE_LOG_DEBUG(NULL, "child connected to parent");
693 // provide our own API
694 m_child.reset(new LocalTransportChildImpl(conn));
695 m_child->add(this, &LocalTransportAgentChild::startSync, LocalTransportChild::startSyncName());
696 m_child->add(this, &LocalTransportAgentChild::sendMsg, LocalTransportChild::sendMsgName());
699 // set up connection to parent
700 m_parent.reset(new LocalTransportParent(conn));
703 void onFailure(SyncMLStatus status, const std::string &reason)
705 SE_LOG_DEBUG(NULL, "child fork/exec failed: %s", reason.c_str());
707 // record failure for parent
708 if (!m_clientReport.getStatus()) {
709 m_clientReport.setStatus(status);
711 if (!reason.empty() &&
712 m_clientReport.getError().empty()) {
713 m_clientReport.setError(reason);
720 // D-Bus API, see LocalTransportChild;
721 // must keep number of parameters < 9, the maximum supported by
723 void startSync(const std::string &clientContext,
724 const StringPair &serverConfig, // config name + root path
725 const std::string &serverLogDir,
726 bool serverDoLogging,
727 const StringPair &serverSyncCredentials,
728 const FullProps &serverConfigProps,
729 const LocalTransportChild::ActiveSources_t &sources,
730 const LocalTransportChild::ReplyPtr &reply)
732 setMsgToParent(reply, "sync() was called");
733 Logger::setProcessName(clientContext);
734 SE_LOG_DEBUG(NULL, "Sync() called, starting the sync");
735 const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY2");
740 // initialize sync context
741 m_client.reset(new SyncContext(std::string("target-config") + clientContext,
743 serverConfig.second == "ephemeral" ?
744 serverConfig.second :
745 serverConfig.second + "/." + clientContext,
746 boost::shared_ptr<TransportAgent>(this, NoopAgentDestructor()),
748 if (serverConfig.second == "ephemeral") {
749 m_client->makeEphemeral();
751 boost::shared_ptr<UserInterface> ui(new LocalTransportUI(m_parent));
752 m_client->setUserInterface(ui);
754 // allow proceeding with sync even if no "target-config" was created,
755 // because information about username/password (for WebDAV) or the
756 // sources (for file backends) might be enough
757 m_client->setConfigNeeded(false);
759 // apply temporary config filters
760 m_client->setConfigFilter(true, "", serverConfigProps.createSyncFilter(m_client->getConfigName()));
761 BOOST_FOREACH(const string &sourceName, m_client->getSyncSources()) {
762 m_client->setConfigFilter(false, sourceName, serverConfigProps.createSourceFilter(m_client->getConfigName(), sourceName));
765 // Copy non-empty credentials from main config, because
766 // that is where the GUI knows how to store them. A better
767 // solution would be to require that credentials are in the
768 // "target-config" config.
770 // Interactive password requests later in SyncContext::sync()
771 // will end up in our LocalTransportContext::askPassword()
772 // implementation above, which will pass the question to
773 // the local sync parent.
774 if (!serverSyncCredentials.first.empty()) {
775 m_client->setSyncUsername(serverSyncCredentials.first, true);
777 if (!serverSyncCredentials.second.empty()) {
778 m_client->setSyncPassword(serverSyncCredentials.second, true);
781 // debugging mode: write logs inside sub-directory of parent,
782 // otherwise use normal log settings
783 if (!serverDoLogging) {
784 m_client->setLogDir(std::string(serverLogDir) + "/child", true);
787 // disable all sources temporarily, will be enabled by next loop
788 BOOST_FOREACH(const string &targetName, m_client->getSyncSources()) {
789 SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName);
790 SyncSourceConfig targetSource(targetName, targetNodes);
791 targetSource.setSync("disabled", true);
794 // activate all sources in client targeted by main config,
796 BOOST_FOREACH(const LocalTransportChild::ActiveSources_t::value_type &entry, sources) {
797 // mapping is from server (source) to child (target)
798 const std::string &sourceName = entry.first;
799 const std::string &targetName = entry.second.first;
800 std::string sync = entry.second.second;
801 SyncMode mode = StringToSyncMode(sync);
802 if (mode != SYNC_NONE) {
803 SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName);
804 SyncSourceConfig targetSource(targetName, targetNodes);
805 string fullTargetName = clientContext + "/" + targetName;
807 if (!targetNodes.dataConfigExists()) {
808 if (targetName.empty()) {
809 m_client->throwError("missing URI for one of the sources");
811 m_client->throwError(StringPrintf("%s: source not configured",
812 fullTargetName.c_str()));
816 // All of the config setting is done as volatile,
817 // so none of the regular config nodes have to
818 // be written. If a sync mode was set, it must have been
819 // done before in this loop => error in original config.
820 if (!targetSource.isDisabled()) {
821 m_client->throwError(StringPrintf("%s: source targetted twice by %s",
822 fullTargetName.c_str(),
823 serverConfig.first.c_str()));
825 // invert data direction
826 if (mode == SYNC_REFRESH_FROM_LOCAL) {
827 mode = SYNC_REFRESH_FROM_REMOTE;
828 } else if (mode == SYNC_REFRESH_FROM_REMOTE) {
829 mode = SYNC_REFRESH_FROM_LOCAL;
830 } else if (mode == SYNC_ONE_WAY_FROM_LOCAL) {
831 mode = SYNC_ONE_WAY_FROM_REMOTE;
832 } else if (mode == SYNC_ONE_WAY_FROM_REMOTE) {
833 mode = SYNC_ONE_WAY_FROM_LOCAL;
834 } else if (mode == SYNC_LOCAL_CACHE_SLOW) {
835 // Remote side is running in caching mode and
836 // asking for refresh. Send all our data.
838 } else if (mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
839 // Remote side is running in caching mode and
840 // asking for an update. Use two-way mode although
841 // nothing is going to come back (simpler that way
842 // than using one-way, which has special code
843 // paths in libsynthesis).
846 targetSource.setSync(PrettyPrintSyncMode(mode, true), true);
847 targetSource.setURI(sourceName, true);
852 // ready for m_client->sync()
856 void sendMsg(const std::string &contentType,
857 const GDBusCXX::DBusArray<uint8_t> &data,
858 const LocalTransportChild::ReplyPtr &reply)
860 SE_LOG_DEBUG(NULL, "child got message of %ld bytes", (long)data.first);
861 setMsgToParent(LocalTransportChild::ReplyPtr(), "sendMsg() was called");
862 if (m_status == ACTIVE) {
863 m_msgToParent = reply;
864 m_message.assign(reinterpret_cast<const char *>(data.second),
866 m_messageType = contentType;
867 m_status = GOT_REPLY;
869 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
870 "child not expecting any message"));
875 LocalTransportAgentChild() :
877 m_forkexec(SyncEvo::ForkExecChild::create()),
881 m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgentChild::onConnect, this, _1));
882 m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgentChild::onFailure, this, _1, _2));
883 // When parent quits, we need to abort whatever we do and shut
884 // down. There's no way how we can complete our work without it.
886 // Note that another way how this process can detect the
887 // death of the parent is when it currently is waiting for
888 // completion of a method call to the parent, like a request
889 // for a password. However, that does not cover failures
890 // like the parent not asking us to sync in the first place
891 // and also does not work with libdbus (https://bugs.freedesktop.org/show_bug.cgi?id=49728).
892 m_forkexec->m_onQuit.connect(&onParentQuit);
894 m_forkexec->connect();
897 boost::shared_ptr<ChildLogger> createLogger()
899 return boost::shared_ptr<ChildLogger>(new ChildLogger(m_child));
904 SuspendFlags &s = SuspendFlags::getSuspendFlags();
907 if (s.getState() != SuspendFlags::NORMAL) {
908 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for parent");
911 step("waiting for parent");
914 if (s.getState() != SuspendFlags::NORMAL) {
915 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for Sync() call from parent");
917 step("waiting for Sync() call from parent");
920 // ignore SIGINT signal in local sync helper from now on:
921 // the parent process will handle those and tell us when
922 // we are expected to abort by sending a SIGTERM
923 struct sigaction new_action;
924 memset(&new_action, 0, sizeof(new_action));
925 new_action.sa_handler = SIG_IGN;
926 sigemptyset(&new_action.sa_mask);
927 sigaction(SIGINT, &new_action, NULL);
929 // SIGTERM would be caught by SuspendFlags and set the "abort"
930 // state. But a lot of code running in this process cannot
931 // check that flag in a timely manner (blocking calls in
932 // libneon, activesync client libraries, ...). Therefore
933 // it is better to abort inside the signal handler.
934 new_action.sa_handler = abortLocalSync;
935 sigaction(SIGTERM, &new_action, NULL);
937 SE_LOG_DEBUG(NULL, "LocalTransportChild: ignore SIGINT, die in SIGTERM");
938 SE_LOG_INFO(NULL, "target side of local sync ready");
939 m_client->sync(&m_clientReport);
942 SyncMLStatus status = Exception::handle(explanation);
943 m_clientReport.setStatus(status);
944 if (!explanation.empty() &&
945 m_clientReport.getError().empty()) {
946 m_clientReport.setError(explanation);
949 std::string report = m_clientReport.toString();
950 SE_LOG_DEBUG(NULL, "child sending sync report after failure:\n%s", report.c_str());
951 m_parent->m_storeSyncReport.start(report,
952 boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1));
953 // wait for acknowledgement for report once:
954 // we are in some kind of error state, better
955 // do not wait too long
957 SE_LOG_DEBUG(NULL, "waiting for parent's ACK for sync report");
958 g_main_context_iteration(NULL, true);
965 // send final report, ignore result
966 std::string report = m_clientReport.toString();
967 SE_LOG_DEBUG(NULL, "child sending sync report:\n%s", report.c_str());
968 m_parent->m_storeSyncReport.start(report,
969 boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1));
970 while (!m_reportSent && m_parent &&
971 s.getState() == SuspendFlags::NORMAL) {
972 step("waiting for parent's ACK for sync report");
977 void syncReportReceived(const std::string &error)
979 SE_LOG_DEBUG(NULL, "sending sync report to parent: %s",
980 error.empty() ? "done" : error.c_str());
984 int getReturnCode() const { return m_ret; }
987 * set transport specific URL of next message
989 virtual void setURL(const std::string &url) {}
992 * define content type for post, see content type constants
994 virtual void setContentType(const std::string &type)
996 m_contentType = type;
1000 * Requests an normal shutdown of the transport. This can take a
1001 * while, for example if communication is still pending.
1002 * Therefore wait() has to be called to ensure that the
1003 * shutdown is complete and that no error occurred.
1005 * Simply deleting the transport is an *unnormal* shutdown that
1006 * does not communicate with the peer.
1008 virtual void shutdown()
1010 SE_LOG_DEBUG(NULL, "child local transport shutting down");
1011 if (m_msgToParent) {
1012 // Must send non-zero message, empty messages cause an
1013 // error during D-Bus message decoding on the receiving
1014 // side. Content doesn't matter, ignored by parent.
1015 m_msgToParent->done("shutdown-message", GDBusCXX::makeDBusArray(1, (uint8_t *)""));
1016 m_msgToParent.reset();
1018 if (m_status != FAILED) {
1024 * start sending message
1026 * Memory must remain valid until reply is received or
1027 * message transmission is canceled.
1029 * @param data start address of data to send
1030 * @param len number of bytes
1032 virtual void send(const char *data, size_t len)
1034 SE_LOG_DEBUG(NULL, "child local transport sending %ld bytes", (long)len);
1035 if (m_msgToParent) {
1037 m_msgToParent->done(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)));
1038 m_msgToParent.reset();
1041 SE_THROW("cannot send data to parent because parent is not waiting for message");
1046 * cancel an active message transmission
1048 * Blocks until send buffer is no longer in use.
1049 * Returns immediately if nothing pending.
1051 virtual void cancel() {}
1055 * Wait for completion of an operation initiated earlier.
1056 * The operation can be a send with optional reply or
1059 * Returns immediately if no operations is pending.
1061 * @param noReply true if no reply is required for a running send;
1062 * only relevant for transports used by a SyncML server
1064 virtual Status wait(bool noReply = false)
1066 SuspendFlags &s = SuspendFlags::getSuspendFlags();
1067 while (m_status == ACTIVE &&
1068 s.getState() == SuspendFlags::NORMAL) {
1069 step("waiting for next message");
1075 * Tells the transport agent to stop the transmission the given
1076 * amount of seconds after send() was called. The transport agent
1077 * will then stop the message transmission and return a TIME_OUT
1080 * @param seconds number of seconds to wait before timing out, zero for no timeout
1082 virtual void setTimeout(int seconds) {}
1085 * provides access to reply data
1087 * Memory pointer remains valid as long as
1088 * transport agent is not deleted and no other
1091 virtual void getReply(const char *&data, size_t &len, std::string &contentType)
1093 SE_LOG_DEBUG(NULL, "processing %ld bytes in child", (long)m_message.size());
1094 if (m_status != GOT_REPLY) {
1095 SE_THROW("getReply() called in child when no reply available");
1097 data = m_message.c_str();
1098 len = m_message.size();
1099 contentType = m_messageType;
1103 int LocalTransportMain(int argc, char **argv)
1105 // delay the client for debugging purposes
1106 const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY");
1111 SyncContext::initMain("syncevo-local-sync");
1113 // Our stderr is either connected to the original stderr (when
1114 // SYNCEVOLUTION_DEBUG is set) or the local sync's parent
1115 // LogRedirect. However, that stderr is not normally used.
1116 // Instead we install our own LogRedirect for both stdout (for
1117 // Execute() and synccompare, which then knows that it needs to
1118 // capture the output) and stderr (to get output like the one from
1119 // libneon into the child log) in LocalTransportAgentChild and
1120 // send all logging output to the local sync parent via D-Bus, to
1121 // be forwarded to the user as part of the normal message stream
1122 // of the sync session.
1123 setvbuf(stderr, NULL, _IONBF, 0);
1124 setvbuf(stdout, NULL, _IONBF, 0);
1126 // SIGPIPE must be ignored, some system libs (glib GIO?) trigger
1127 // it. SIGINT/TERM will be handled via SuspendFlags once the sync
1129 struct sigaction sa;
1130 memset(&sa, 0, sizeof(sa));
1131 sa.sa_handler = SIG_IGN;
1132 sigaction(SIGPIPE, &sa, NULL);
1135 if (getenv("SYNCEVOLUTION_DEBUG")) {
1136 Logger::instance().setLevel(Logger::DEBUG);
1138 // process name will be set to target config name once it is known
1139 Logger::setProcessName("syncevo-local-sync");
1141 boost::shared_ptr<LocalTransportAgentChild> child(new LocalTransportAgentChild);
1142 PushLogger<Logger> logger;
1143 // Temporary handle is necessary to avoid compiler issue with
1144 // clang (ambiguous brackets).
1146 Logger::Handle handle(child->createLogger());
1147 logger.reset(handle);
1150 int ret = child->getReturnCode();
1154 } catch ( const std::exception &ex ) {
1155 SE_LOG_ERROR(NULL, "%s", ex.what());
1157 SE_LOG_ERROR(NULL, "unknown error");