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/LogDLT.h>
31 #include <syncevo/BoostHelper.h>
33 #include <synthesis/syerror.h>
36 #include <sys/socket.h>
37 #include <sys/types.h>
44 #include <syncevo/declarations.h>
47 class NoopAgentDestructor
50 void operator () (TransportAgent *agent) throw() {}
53 LocalTransportAgent::LocalTransportAgent(SyncContext *server,
54 const std::string &clientContext,
57 m_clientContext(SyncConfig::normalizeConfigString(clientContext)),
60 GMainLoopCXX(static_cast<GMainLoop *>(loop), ADD_REF) :
61 GMainLoopCXX(g_main_loop_new(NULL, false), TRANSFER_REF))
65 boost::shared_ptr<LocalTransportAgent> LocalTransportAgent::create(SyncContext *server,
66 const std::string &clientContext,
69 boost::shared_ptr<LocalTransportAgent> self(new LocalTransportAgent(server, clientContext, loop));
74 LocalTransportAgent::~LocalTransportAgent()
78 void LocalTransportAgent::start()
80 // compare normalized context names to detect forbidden sync
81 // within the same context; they could be set up, but are more
82 // likely configuration mistakes
84 SyncConfig::splitConfigString(m_clientContext, peer, context);
86 SE_THROW(StringPrintf("invalid local sync URL: '%s' references a peer config, should point to a context like @%s instead",
87 m_clientContext.c_str(),
90 // TODO (?): check that there are no conflicts between the active
91 // sources. The old "contexts must be different" check achieved that
92 // via brute force (because by definition, databases from different
93 // contexts are meant to be independent), but it was too coarse
94 // and ruled out valid configurations.
95 // if (m_clientContext == m_server->getContextName()) {
96 // SE_THROW(StringPrintf("invalid local sync inside context '%s', need second context with different databases", context.c_str()));
100 SE_THROW("local transport already started");
103 m_forkexec = ForkExecParent::create("syncevo-local-sync");
105 if (getenv("SYNCEVOLUTION_USE_DLT")) {
106 m_forkexec->addEnvVar("SYNCEVOLUTION_USE_DLT", StringPrintf("%d", LoggerDLT::getCurrentDLTLogLevel()));
109 m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgent::onChildConnect, this, _1));
110 // fatal problems, including quitting child with non-zero status
111 m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgent::onFailure, this, _2));
112 // watch onQuit and remember whether the child is still running,
113 // because it might quit prematurely with a zero return code (for
114 // example, when an unexpected slow sync is detected)
115 m_forkexec->m_onQuit.connect(boost::bind(&LocalTransportAgent::onChildQuit, this, _1));
120 * Uses the D-Bus API provided by LocalTransportParent.
122 class LocalTransportParent : private GDBusCXX::DBusRemoteObject
125 static const char *path() { return "/"; }
126 static const char *interface() { return "org.syncevolution.localtransport.parent"; }
127 static const char *destination() { return "local.destination"; }
128 static const char *askPasswordName() { return "AskPassword"; }
129 static const char *storeSyncReportName() { return "StoreSyncReport"; }
131 LocalTransportParent(const GDBusCXX::DBusConnectionPtr &conn) :
132 GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
133 m_askPassword(*this, askPasswordName()),
134 m_storeSyncReport(*this, storeSyncReportName())
137 /** LocalTransportAgent::askPassword() */
138 GDBusCXX::DBusClientCall1<std::string> m_askPassword;
139 /** LocalTransportAgent::storeSyncReport() */
140 GDBusCXX::DBusClientCall0 m_storeSyncReport;
144 * Uses the D-Bus API provided by LocalTransportAgentChild.
146 class LocalTransportChild : public GDBusCXX::DBusRemoteObject
149 static const char *path() { return "/"; }
150 static const char *interface() { return "org.syncevolution.localtransport.child"; }
151 static const char *destination() { return "local.destination"; }
152 static const char *logOutputName() { return "LogOutput"; }
153 static const char *startSyncName() { return "StartSync"; }
154 static const char *sendMsgName() { return "SendMsg"; }
156 LocalTransportChild(const GDBusCXX::DBusConnectionPtr &conn) :
157 GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
158 m_logOutput(*this, logOutputName(), false),
159 m_startSync(*this, startSyncName()),
160 m_sendMsg(*this, sendMsgName())
164 * information from server config about active sources:
165 * mapping is from server source names to child source name + sync mode
166 * (again as set on the server side!)
168 typedef std::map<std::string, StringPair> ActiveSources_t;
169 /** use this to send a message back from child to parent */
170 typedef boost::shared_ptr< GDBusCXX::Result2< std::string, GDBusCXX::DBusArray<uint8_t> > > ReplyPtr;
172 /** log output with level and message; process name will be added by parent */
173 GDBusCXX::SignalWatch2<string, string> m_logOutput;
175 /** LocalTransportAgentChild::startSync() */
176 GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_startSync;
177 /** LocalTransportAgentChild::sendMsg() */
178 GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_sendMsg;
182 void LocalTransportAgent::logChildOutput(const std::string &level, const std::string &message)
184 Logger::MessageOptions options(Logger::strToLevel(level.c_str()));
185 options.m_processName = &m_clientContext;
186 // Child should have written this into its own log file and/or syslog/dlt already.
187 // Only pass it on to a user of the command line interface.
188 options.m_flags = Logger::MessageOptions::ALREADY_LOGGED;
189 SyncEvo::Logger::instance().messageWithOptions(options, "%s", message.c_str());
192 void LocalTransportAgent::onChildConnect(const GDBusCXX::DBusConnectionPtr &conn)
194 SE_LOG_DEBUG(NULL, "child is ready");
195 m_parent.reset(new GDBusCXX::DBusObjectHelper(conn,
196 LocalTransportParent::path(),
197 LocalTransportParent::interface(),
198 GDBusCXX::DBusObjectHelper::Callback_t(),
200 m_parent->add(this, &LocalTransportAgent::askPassword, LocalTransportParent::askPasswordName());
201 m_parent->add(this, &LocalTransportAgent::storeSyncReport, LocalTransportParent::storeSyncReportName());
202 m_parent->activate();
203 m_child.reset(new LocalTransportChild(conn));
204 m_child->m_logOutput.activate(boost::bind(&LocalTransportAgent::logChildOutput, this, _1, _2));
206 // now tell child what to do
207 LocalTransportChild::ActiveSources_t sources;
208 BOOST_FOREACH(const string &sourceName, m_server->getSyncSources()) {
209 SyncSourceNodes nodes = m_server->getSyncSourceNodesNoTracking(sourceName);
210 SyncSourceConfig source(sourceName, nodes);
211 std::string sync = source.getSync();
212 if (sync != "disabled") {
213 string targetName = source.getURINonEmpty();
214 sources[sourceName] = std::make_pair(targetName, sync);
217 m_child->m_startSync.start(m_clientContext,
218 StringPair(m_server->getConfigName(),
219 m_server->isEphemeral() ?
221 m_server->getRootPath()),
222 static_cast<std::string>(m_server->getLogDir()),
223 m_server->getDoLogging(),
224 std::make_pair(m_server->getSyncUser(),
225 m_server->getSyncPassword()),
226 m_server->getConfigProps(),
228 boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
231 void LocalTransportAgent::onFailure(const std::string &error)
234 g_main_loop_quit(m_loop.get());
236 SE_LOG_ERROR(NULL, "local transport failed: %s", error.c_str());
241 void LocalTransportAgent::onChildQuit(int status)
243 SE_LOG_DEBUG(NULL, "child process has quit with status %d", status);
244 g_main_loop_quit(m_loop.get());
247 static void GotPassword(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply,
248 const std::string &password)
250 reply->done(password);
253 static void PasswordException(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply)
255 // TODO: refactor, this is the same as dbusErrorCallback
257 // If there is no pending exception, the process will abort
258 // with "terminate called without an active exception";
259 // dbusErrorCallback() should only be called when there is
260 // a pending exception.
261 // TODO: catch this misuse in a better way
264 // let D-Bus parent log the error
265 std::string explanation;
266 Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
267 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", explanation));
271 void LocalTransportAgent::askPassword(const std::string &passwordName,
272 const std::string &descr,
273 const ConfigPasswordKey &key,
274 const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply)
276 // pass that work to our own SyncContext and its UI - currently blocks
277 SE_LOG_DEBUG(NULL, "local sync parent: asked for password %s, %s",
278 passwordName.c_str(),
282 m_server->getUserInterfaceNonNull().askPasswordAsync(passwordName, descr, key,
283 // TODO refactor: use dbus-callbacks.h
284 boost::bind(GotPassword,
287 boost::bind(PasswordException,
290 SE_LOG_DEBUG(NULL, "local sync parent: password request failed because no m_server");
291 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
292 "not connected to UI"));
295 PasswordException(reply);
299 void LocalTransportAgent::storeSyncReport(const std::string &report)
301 SE_LOG_DEBUG(NULL, "got child sync report:\n%s",
303 m_clientReport = SyncReport(report);
306 void LocalTransportAgent::getClientSyncReport(SyncReport &report)
308 report = m_clientReport;
311 void LocalTransportAgent::setContentType(const std::string &type)
313 m_contentType = type;
316 // workaround for limitations of bind+signals when used together with plain GMainLoop pointer
317 // (pointer to undefined struct)
318 static void gMainLoopQuit(GMainLoopCXX *loop)
320 g_main_loop_quit(loop->get());
323 void LocalTransportAgent::shutdown()
325 SE_LOG_DEBUG(NULL, "parent is shutting down");
327 // block until child is done
328 boost::signals2::scoped_connection c(m_forkexec->m_onQuit.connect(boost::bind(gMainLoopQuit,
330 // don't kill the child here - we expect it to complete by
331 // itself at some point
332 // TODO: how do we detect a child which gets stuck after its last
333 // communication with the parent?
334 // m_forkexec->stop();
335 while (m_forkexec->getState() != ForkExecParent::TERMINATED) {
336 SE_LOG_DEBUG(NULL, "waiting for child to stop");
337 g_main_loop_run(m_loop.get());
346 void LocalTransportAgent::send(const char *data, size_t len)
350 m_child->m_sendMsg.start(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)),
351 boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
354 SE_THROW_EXCEPTION(TransportException,
355 "cannot send message because child process is gone");
359 void LocalTransportAgent::storeReplyMsg(const std::string &contentType,
360 const GDBusCXX::DBusArray<uint8_t> &reply,
361 const std::string &error)
363 m_replyMsg.assign(reinterpret_cast<const char *>(reply.second),
365 m_replyContentType = contentType;
367 m_status = GOT_REPLY;
369 // Only an error if the client hasn't shut down normally.
370 if (m_clientReport.empty()) {
371 SE_LOG_ERROR(NULL, "sending message to child failed: %s", error.c_str());
375 g_main_loop_quit(m_loop.get());
378 void LocalTransportAgent::cancel()
381 SE_LOG_DEBUG(NULL, "killing local transport child in cancel()");
387 TransportAgent::Status LocalTransportAgent::wait(bool noReply)
389 if (m_status == ACTIVE) {
390 // need next message; for noReply == true we are done
394 while (m_status == ACTIVE) {
395 SE_LOG_DEBUG(NULL, "waiting for child to send message");
397 m_forkexec->getState() == ForkExecParent::TERMINATED) {
399 if (m_clientReport.getStatus() != STATUS_OK &&
400 m_clientReport.getStatus() != STATUS_HTTP_OK) {
401 // Report that status, with an error message which contains the explanation
402 // added to the client's error. We are a bit fuzzy about matching the status:
403 // 10xxx matches xxx and vice versa.
404 int status = m_clientReport.getStatus();
405 if (status >= sysync::LOCAL_STATUS_CODE && status <= sysync::LOCAL_STATUS_CODE_END) {
406 status -= sysync::LOCAL_STATUS_CODE;
408 std::string explanation = StringPrintf("failure on target side %s of local sync",
409 m_clientContext.c_str());
410 static const pcrecpp::RE re("\\((?:local|remote), status (\\d+)\\): (.*)");
412 std::string clientExplanation;
413 if (re.PartialMatch(m_clientReport.getError(), &clientStatus, &clientExplanation) &&
414 (status == clientStatus ||
415 status == clientStatus - sysync::LOCAL_STATUS_CODE)) {
417 explanation += clientExplanation;
419 SE_THROW_EXCEPTION_STATUS(StatusException,
421 m_clientReport.getStatus());
423 SE_THROW_EXCEPTION(TransportException,
424 "child process quit without sending its message");
427 g_main_loop_run(m_loop.get());
434 void LocalTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType)
436 if (m_status != GOT_REPLY) {
437 SE_THROW("internal error, no reply available");
439 contentType = m_replyContentType;
440 data = m_replyMsg.c_str();
441 len = m_replyMsg.size();
444 void LocalTransportAgent::setTimeout(int seconds)
446 // setTimeout() was meant for unreliable transports like HTTP
447 // which cannot determine whether the peer is still alive. The
448 // LocalTransportAgent uses sockets and will notice when a peer
449 // dies unexpectedly, so timeouts should never be necessary.
451 // Quite the opposite, because the "client" in a local sync
452 // with WebDAV on the client side can be quite slow, incorrect
453 // timeouts were seen where the client side took longer than
454 // the default timeout of 5 minutes to process a message and
457 // Therefore we ignore the request to set a timeout here and thus
458 // local send/receive operations are allowed to continue for as
459 // long as they like.
461 // m_timeoutSeconds = seconds;
464 class LocalTransportUI : public UserInterface
466 boost::shared_ptr<LocalTransportParent> m_parent;
469 LocalTransportUI(const boost::shared_ptr<LocalTransportParent> &parent) :
473 /** implements password request by asking the parent via D-Bus */
474 virtual string askPassword(const string &passwordName,
476 const ConfigPasswordKey &key)
478 SE_LOG_DEBUG(NULL, "local transport child: requesting password %s, %s via D-Bus",
479 passwordName.c_str(),
481 std::string password;
483 bool havePassword = false;
484 m_parent->m_askPassword.start(passwordName, descr, key,
485 boost::bind(&LocalTransportUI::storePassword, this,
486 boost::ref(password), boost::ref(error),
487 boost::ref(havePassword),
489 SuspendFlags &s = SuspendFlags::getSuspendFlags();
490 while (!havePassword) {
491 if (s.getState() != SuspendFlags::NORMAL) {
492 SE_THROW_EXCEPTION_STATUS(StatusException,
493 StringPrintf("User did not provide the '%s' password.",
494 passwordName.c_str()),
495 SyncMLStatus(sysync::LOCERR_USERABORT));
497 g_main_context_iteration(NULL, true);
499 if (!error.empty()) {
500 Exception::tryRethrowDBus(error);
501 SE_THROW(StringPrintf("retrieving password failed: %s", error.c_str()));
506 virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { SE_THROW("not implemented"); return false; }
507 virtual void readStdin(std::string &content) { SE_THROW("not implemented"); }
510 void storePassword(std::string &res, std::string &errorRes, bool &haveRes, const std::string &password, const std::string &error)
512 if (!error.empty()) {
513 SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request failed: %s",
517 SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request succeeded");
524 static void abortLocalSync(int sigterm)
526 // logging anything here is not safe (our own logging system might
527 // have been interrupted by the SIGTERM and thus be in an inconsistent
528 // state), but let's try it anyway
529 SE_LOG_INFO(NULL, "local sync child shutting down due to SIGTERM");
530 // raise the signal again after disabling the handler, to ensure that
531 // the exit status is "killed by signal xxx" - good because then
532 // the whoever killed used gets the information that we didn't die for
534 signal(sigterm, SIG_DFL);
539 * Provides the "LogOutput" signal.
540 * LocalTransportAgentChild adds the method implementations
541 * before activating it.
543 class LocalTransportChildImpl : public GDBusCXX::DBusObjectHelper
546 LocalTransportChildImpl(const GDBusCXX::DBusConnectionPtr &conn) :
547 GDBusCXX::DBusObjectHelper(conn,
548 LocalTransportChild::path(),
549 LocalTransportChild::interface(),
550 GDBusCXX::DBusObjectHelper::Callback_t(),
552 m_logOutput(*this, LocalTransportChild::logOutputName())
557 GDBusCXX::EmitSignal2<std::string,
559 true /* ignore transmission failures */> m_logOutput;
562 class ChildLogger : public Logger
564 std::auto_ptr<LogRedirect> m_parentLogger;
565 boost::weak_ptr<LocalTransportChildImpl> m_child;
568 ChildLogger(const boost::shared_ptr<LocalTransportChildImpl> &child) :
569 m_parentLogger(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)),
574 m_parentLogger.reset();
578 * Write message into our own log and send to parent.
580 virtual void messagev(const MessageOptions &options,
584 if (options.m_level <= m_parentLogger->getLevel()) {
585 m_parentLogger->process();
586 boost::shared_ptr<LocalTransportChildImpl> child = m_child.lock();
588 // prefix is used to set session path
589 // for general server output, the object path field is dbus server
590 // the object path can't be empty for object paths prevent using empty string.
591 string strLevel = Logger::levelToStr(options.m_level);
592 string log = StringPrintfV(format, args);
593 child->m_logOutput(strLevel, log);
599 class LocalTransportAgentChild : public TransportAgent
601 /** final return code of our main(): non-zero indicates that we need to shut down */
605 * sync report for client side of the local sync
607 SyncReport m_clientReport;
609 /** used to capture libneon output */
610 boost::scoped_ptr<LogRedirect> m_parentLogger;
613 * provides connection to parent, created in constructor
615 boost::shared_ptr<ForkExecChild> m_forkexec;
618 * proxy for the parent's D-Bus API in onConnect()
620 boost::shared_ptr<LocalTransportParent> m_parent;
623 * our D-Bus interface, created in onConnect()
625 boost::shared_ptr<LocalTransportChildImpl> m_child;
628 * sync context, created in Sync() D-Bus call
630 boost::scoped_ptr<SyncContext> m_client;
633 * use this D-Bus result handle to send a message from child to parent
634 * in response to sync() or (later) sendMsg()
636 LocalTransportChild::ReplyPtr m_msgToParent;
637 void setMsgToParent(const LocalTransportChild::ReplyPtr &reply,
638 const std::string &reason)
641 m_msgToParent->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
642 "cancelling message: " + reason));
644 m_msgToParent = reply;
647 /** content type for message to parent */
648 std::string m_contentType;
651 * message from parent
653 std::string m_message;
656 * content type of message from parent
658 std::string m_messageType;
661 * true after parent has received sync report, or sending failed
666 * INACTIVE when idle,
667 * ACTIVE after having sent and while waiting for next message,
668 * GOT_REPLY when we have a message to be processed,
669 * FAILED when permanently broken
674 * one loop run + error checking
676 void step(const std::string &status)
678 SE_LOG_DEBUG(NULL, "local transport: %s", status.c_str());
680 m_forkexec->getState() == ForkExecChild::DISCONNECTED) {
681 SE_THROW("local transport child no longer has a parent, terminating");
683 g_main_context_iteration(NULL, true);
685 SE_THROW("local transport child encountered a problem, terminating");
689 static void onParentQuit()
691 // Never free this state blocker. We can only abort and
693 static boost::shared_ptr<SuspendFlags::StateBlocker> abortGuard;
694 SE_LOG_ERROR(NULL, "sync parent quit unexpectedly");
695 abortGuard = SuspendFlags::getSuspendFlags().abort();
698 void onConnect(const GDBusCXX::DBusConnectionPtr &conn)
700 SE_LOG_DEBUG(NULL, "child connected to parent");
702 // provide our own API
703 m_child.reset(new LocalTransportChildImpl(conn));
704 m_child->add(this, &LocalTransportAgentChild::startSync, LocalTransportChild::startSyncName());
705 m_child->add(this, &LocalTransportAgentChild::sendMsg, LocalTransportChild::sendMsgName());
708 // set up connection to parent
709 m_parent.reset(new LocalTransportParent(conn));
712 void onFailure(SyncMLStatus status, const std::string &reason)
714 SE_LOG_DEBUG(NULL, "child fork/exec failed: %s", reason.c_str());
716 // record failure for parent
717 if (!m_clientReport.getStatus()) {
718 m_clientReport.setStatus(status);
720 if (!reason.empty() &&
721 m_clientReport.getError().empty()) {
722 m_clientReport.setError(reason);
729 // D-Bus API, see LocalTransportChild;
730 // must keep number of parameters < 9, the maximum supported by
732 void startSync(const std::string &clientContext,
733 const StringPair &serverConfig, // config name + root path
734 const std::string &serverLogDir,
735 bool serverDoLogging,
736 const std::pair<UserIdentity, InitStateString> &serverSyncCredentials,
737 const FullProps &serverConfigProps,
738 const LocalTransportChild::ActiveSources_t &sources,
739 const LocalTransportChild::ReplyPtr &reply)
741 setMsgToParent(reply, "sync() was called");
742 Logger::setProcessName(clientContext);
743 SE_LOG_DEBUG(NULL, "Sync() called, starting the sync");
744 const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY2");
749 // initialize sync context
750 m_client.reset(new SyncContext(std::string("target-config") + clientContext,
752 serverConfig.second == "ephemeral" ?
753 serverConfig.second :
754 serverConfig.second + "/." + clientContext,
755 boost::shared_ptr<TransportAgent>(this, NoopAgentDestructor()),
757 if (serverConfig.second == "ephemeral") {
758 m_client->makeEphemeral();
760 boost::shared_ptr<UserInterface> ui(new LocalTransportUI(m_parent));
761 m_client->setUserInterface(ui);
763 // allow proceeding with sync even if no "target-config" was created,
764 // because information about username/password (for WebDAV) or the
765 // sources (for file backends) might be enough
766 m_client->setConfigNeeded(false);
768 // apply temporary config filters
769 m_client->setConfigFilter(true, "", serverConfigProps.createSyncFilter(m_client->getConfigName()));
770 BOOST_FOREACH(const string &sourceName, m_client->getSyncSources()) {
771 m_client->setConfigFilter(false, sourceName, serverConfigProps.createSourceFilter(m_client->getConfigName(), sourceName));
774 // Copy non-empty credentials from main config, because
775 // that is where the GUI knows how to store them. A better
776 // solution would be to require that credentials are in the
777 // "target-config" config.
779 // Interactive password requests later in SyncContext::sync()
780 // will end up in our LocalTransportContext::askPassword()
781 // implementation above, which will pass the question to
782 // the local sync parent.
783 if (!serverSyncCredentials.first.toString().empty()) {
784 m_client->setSyncUsername(serverSyncCredentials.first.toString(), true);
786 if (!serverSyncCredentials.second.empty()) {
787 m_client->setSyncPassword(serverSyncCredentials.second, true);
790 // debugging mode: write logs inside sub-directory of parent,
791 // otherwise use normal log settings
792 if (!serverDoLogging) {
793 m_client->setLogDir(std::string(serverLogDir) + "/child", true);
796 // disable all sources temporarily, will be enabled by next loop
797 BOOST_FOREACH(const string &targetName, m_client->getSyncSources()) {
798 SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName);
799 SyncSourceConfig targetSource(targetName, targetNodes);
800 targetSource.setSync("disabled", true);
803 // activate all sources in client targeted by main config,
805 BOOST_FOREACH(const LocalTransportChild::ActiveSources_t::value_type &entry, sources) {
806 // mapping is from server (source) to child (target)
807 const std::string &sourceName = entry.first;
808 const std::string &targetName = entry.second.first;
809 std::string sync = entry.second.second;
810 SyncMode mode = StringToSyncMode(sync);
811 if (mode != SYNC_NONE) {
812 SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName);
813 SyncSourceConfig targetSource(targetName, targetNodes);
814 string fullTargetName = clientContext + "/" + targetName;
816 if (!targetNodes.dataConfigExists()) {
817 if (targetName.empty()) {
818 m_client->throwError("missing URI for one of the sources");
820 m_client->throwError(StringPrintf("%s: source not configured",
821 fullTargetName.c_str()));
825 // All of the config setting is done as volatile,
826 // so none of the regular config nodes have to
827 // be written. If a sync mode was set, it must have been
828 // done before in this loop => error in original config.
829 if (!targetSource.isDisabled()) {
830 m_client->throwError(StringPrintf("%s: source targetted twice by %s",
831 fullTargetName.c_str(),
832 serverConfig.first.c_str()));
834 // invert data direction
835 if (mode == SYNC_REFRESH_FROM_LOCAL) {
836 mode = SYNC_REFRESH_FROM_REMOTE;
837 } else if (mode == SYNC_REFRESH_FROM_REMOTE) {
838 mode = SYNC_REFRESH_FROM_LOCAL;
839 } else if (mode == SYNC_ONE_WAY_FROM_LOCAL) {
840 mode = SYNC_ONE_WAY_FROM_REMOTE;
841 } else if (mode == SYNC_ONE_WAY_FROM_REMOTE) {
842 mode = SYNC_ONE_WAY_FROM_LOCAL;
843 } else if (mode == SYNC_LOCAL_CACHE_SLOW) {
844 // Remote side is running in caching mode and
845 // asking for refresh. Send all our data.
847 } else if (mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
848 // Remote side is running in caching mode and
849 // asking for an update. Use two-way mode although
850 // nothing is going to come back (simpler that way
851 // than using one-way, which has special code
852 // paths in libsynthesis).
855 targetSource.setSync(PrettyPrintSyncMode(mode, true), true);
856 targetSource.setURI(sourceName, true);
861 // ready for m_client->sync()
865 void sendMsg(const std::string &contentType,
866 const GDBusCXX::DBusArray<uint8_t> &data,
867 const LocalTransportChild::ReplyPtr &reply)
869 SE_LOG_DEBUG(NULL, "child got message of %ld bytes", (long)data.first);
870 setMsgToParent(LocalTransportChild::ReplyPtr(), "sendMsg() was called");
871 if (m_status == ACTIVE) {
872 m_msgToParent = reply;
873 m_message.assign(reinterpret_cast<const char *>(data.second),
875 m_messageType = contentType;
876 m_status = GOT_REPLY;
878 reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
879 "child not expecting any message"));
884 LocalTransportAgentChild() :
886 m_forkexec(SyncEvo::ForkExecChild::create()),
890 m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgentChild::onConnect, this, _1));
891 m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgentChild::onFailure, this, _1, _2));
892 // When parent quits, we need to abort whatever we do and shut
893 // down. There's no way how we can complete our work without it.
895 // Note that another way how this process can detect the
896 // death of the parent is when it currently is waiting for
897 // completion of a method call to the parent, like a request
898 // for a password. However, that does not cover failures
899 // like the parent not asking us to sync in the first place
900 // and also does not work with libdbus (https://bugs.freedesktop.org/show_bug.cgi?id=49728).
901 m_forkexec->m_onQuit.connect(&onParentQuit);
903 m_forkexec->connect();
906 boost::shared_ptr<ChildLogger> createLogger()
908 return boost::shared_ptr<ChildLogger>(new ChildLogger(m_child));
913 SuspendFlags &s = SuspendFlags::getSuspendFlags();
916 if (s.getState() != SuspendFlags::NORMAL) {
917 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for parent");
920 step("waiting for parent");
923 if (s.getState() != SuspendFlags::NORMAL) {
924 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for Sync() call from parent");
926 step("waiting for Sync() call from parent");
929 // ignore SIGINT signal in local sync helper from now on:
930 // the parent process will handle those and tell us when
931 // we are expected to abort by sending a SIGTERM
932 struct sigaction new_action;
933 memset(&new_action, 0, sizeof(new_action));
934 new_action.sa_handler = SIG_IGN;
935 sigemptyset(&new_action.sa_mask);
936 sigaction(SIGINT, &new_action, NULL);
938 // SIGTERM would be caught by SuspendFlags and set the "abort"
939 // state. But a lot of code running in this process cannot
940 // check that flag in a timely manner (blocking calls in
941 // libneon, activesync client libraries, ...). Therefore
942 // it is better to abort inside the signal handler.
943 new_action.sa_handler = abortLocalSync;
944 sigaction(SIGTERM, &new_action, NULL);
946 SE_LOG_DEBUG(NULL, "LocalTransportChild: ignore SIGINT, die in SIGTERM");
947 SE_LOG_INFO(NULL, "target side of local sync ready");
948 m_client->sync(&m_clientReport);
951 SyncMLStatus status = Exception::handle(explanation);
952 m_clientReport.setStatus(status);
953 if (!explanation.empty() &&
954 m_clientReport.getError().empty()) {
955 m_clientReport.setError(explanation);
958 std::string report = m_clientReport.toString();
959 SE_LOG_DEBUG(NULL, "child sending sync report after failure:\n%s", report.c_str());
960 m_parent->m_storeSyncReport.start(report,
961 boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1));
962 // wait for acknowledgement for report once:
963 // we are in some kind of error state, better
964 // do not wait too long
966 SE_LOG_DEBUG(NULL, "waiting for parent's ACK for sync report");
967 g_main_context_iteration(NULL, true);
974 // send final report, ignore result
975 std::string report = m_clientReport.toString();
976 SE_LOG_DEBUG(NULL, "child sending sync report:\n%s", report.c_str());
977 m_parent->m_storeSyncReport.start(report,
978 boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1));
979 while (!m_reportSent && m_parent &&
980 s.getState() == SuspendFlags::NORMAL) {
981 step("waiting for parent's ACK for sync report");
986 void syncReportReceived(const std::string &error)
988 SE_LOG_DEBUG(NULL, "sending sync report to parent: %s",
989 error.empty() ? "done" : error.c_str());
993 int getReturnCode() const { return m_ret; }
996 * set transport specific URL of next message
998 virtual void setURL(const std::string &url) {}
1001 * define content type for post, see content type constants
1003 virtual void setContentType(const std::string &type)
1005 m_contentType = type;
1009 * Requests an normal shutdown of the transport. This can take a
1010 * while, for example if communication is still pending.
1011 * Therefore wait() has to be called to ensure that the
1012 * shutdown is complete and that no error occurred.
1014 * Simply deleting the transport is an *unnormal* shutdown that
1015 * does not communicate with the peer.
1017 virtual void shutdown()
1019 SE_LOG_DEBUG(NULL, "child local transport shutting down");
1020 if (m_msgToParent) {
1021 // Must send non-zero message, empty messages cause an
1022 // error during D-Bus message decoding on the receiving
1023 // side. Content doesn't matter, ignored by parent.
1024 m_msgToParent->done("shutdown-message", GDBusCXX::makeDBusArray(1, (uint8_t *)""));
1025 m_msgToParent.reset();
1027 if (m_status != FAILED) {
1033 * start sending message
1035 * Memory must remain valid until reply is received or
1036 * message transmission is canceled.
1038 * @param data start address of data to send
1039 * @param len number of bytes
1041 virtual void send(const char *data, size_t len)
1043 SE_LOG_DEBUG(NULL, "child local transport sending %ld bytes", (long)len);
1044 if (m_msgToParent) {
1046 m_msgToParent->done(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)));
1047 m_msgToParent.reset();
1050 SE_THROW("cannot send data to parent because parent is not waiting for message");
1055 * cancel an active message transmission
1057 * Blocks until send buffer is no longer in use.
1058 * Returns immediately if nothing pending.
1060 virtual void cancel() {}
1064 * Wait for completion of an operation initiated earlier.
1065 * The operation can be a send with optional reply or
1068 * Returns immediately if no operations is pending.
1070 * @param noReply true if no reply is required for a running send;
1071 * only relevant for transports used by a SyncML server
1073 virtual Status wait(bool noReply = false)
1075 SuspendFlags &s = SuspendFlags::getSuspendFlags();
1076 while (m_status == ACTIVE &&
1077 s.getState() == SuspendFlags::NORMAL) {
1078 step("waiting for next message");
1084 * Tells the transport agent to stop the transmission the given
1085 * amount of seconds after send() was called. The transport agent
1086 * will then stop the message transmission and return a TIME_OUT
1089 * @param seconds number of seconds to wait before timing out, zero for no timeout
1091 virtual void setTimeout(int seconds) {}
1094 * provides access to reply data
1096 * Memory pointer remains valid as long as
1097 * transport agent is not deleted and no other
1100 virtual void getReply(const char *&data, size_t &len, std::string &contentType)
1102 SE_LOG_DEBUG(NULL, "processing %ld bytes in child", (long)m_message.size());
1103 if (m_status != GOT_REPLY) {
1104 SE_THROW("getReply() called in child when no reply available");
1106 data = m_message.c_str();
1107 len = m_message.size();
1108 contentType = m_messageType;
1112 int LocalTransportMain(int argc, char **argv)
1114 // delay the client for debugging purposes
1115 const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY");
1120 SyncContext::initMain("syncevo-local-sync");
1122 // Our stderr is either connected to the original stderr (when
1123 // SYNCEVOLUTION_DEBUG is set) or the local sync's parent
1124 // LogRedirect. However, that stderr is not normally used.
1125 // Instead we install our own LogRedirect for both stdout (for
1126 // Execute() and synccompare, which then knows that it needs to
1127 // capture the output) and stderr (to get output like the one from
1128 // libneon into the child log) in LocalTransportAgentChild and
1129 // send all logging output to the local sync parent via D-Bus, to
1130 // be forwarded to the user as part of the normal message stream
1131 // of the sync session.
1132 setvbuf(stderr, NULL, _IONBF, 0);
1133 setvbuf(stdout, NULL, _IONBF, 0);
1135 // SIGPIPE must be ignored, some system libs (glib GIO?) trigger
1136 // it. SIGINT/TERM will be handled via SuspendFlags once the sync
1138 struct sigaction sa;
1139 memset(&sa, 0, sizeof(sa));
1140 sa.sa_handler = SIG_IGN;
1141 sigaction(SIGPIPE, &sa, NULL);
1144 if (getenv("SYNCEVOLUTION_DEBUG")) {
1145 Logger::instance().setLevel(Logger::DEBUG);
1147 // process name will be set to target config name once it is known
1148 Logger::setProcessName("syncevo-local-sync");
1150 boost::shared_ptr<LocalTransportAgentChild> child(new LocalTransportAgentChild);
1151 PushLogger<Logger> logger;
1152 // Temporary handle is necessary to avoid compiler issue with
1153 // clang (ambiguous brackets).
1155 Logger::Handle handle(child->createLogger());
1156 logger.reset(handle);
1160 // Set by syncevo-dbus-server for us.
1161 bool useDLT = getenv("SYNCEVOLUTION_USE_DLT") != NULL;
1162 PushLogger<LoggerDLT> loggerdlt;
1164 loggerdlt.reset(new LoggerDLT(DLT_SYNCEVO_LOCAL_HELPER_ID, "SyncEvolution local sync helper"));
1169 int ret = child->getReturnCode();
1173 } catch ( const std::exception &ex ) {
1174 SE_LOG_ERROR(NULL, "%s", ex.what());
1176 SE_LOG_ERROR(NULL, "unknown error");