Imported Upstream version 1.3.99.4
[platform/upstream/syncevolution.git] / src / syncevo / LocalTransportAgent.cpp
1 /*
2  * Copyright (C) 2010 Patrick Ohly
3  *
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.
8  *
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.
13  *
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
17  * 02110-1301  USA
18  */
19
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>
31
32 #include <synthesis/syerror.h>
33
34 #include <stddef.h>
35 #include <sys/socket.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <fcntl.h>
39 #include <pcrecpp.h>
40
41 #include <algorithm>
42
43 #include <syncevo/declarations.h>
44 SE_BEGIN_CXX
45
46 class NoopAgentDestructor
47 {
48 public:
49     void operator () (TransportAgent *agent) throw() {}
50 };
51
52 LocalTransportAgent::LocalTransportAgent(SyncContext *server,
53                                          const std::string &clientContext,
54                                          void *loop) :
55     m_server(server),
56     m_clientContext(SyncConfig::normalizeConfigString(clientContext)),
57     m_status(INACTIVE),
58     m_loop(loop ?
59            GMainLoopCXX(static_cast<GMainLoop *>(loop), ADD_REF) :
60            GMainLoopCXX(g_main_loop_new(NULL, false), TRANSFER_REF))
61 {
62 }
63
64 boost::shared_ptr<LocalTransportAgent> LocalTransportAgent::create(SyncContext *server,
65                                                                    const std::string &clientContext,
66                                                                    void *loop)
67 {
68     boost::shared_ptr<LocalTransportAgent> self(new LocalTransportAgent(server, clientContext, loop));
69     self->m_self = self;
70     return self;
71 }
72
73 LocalTransportAgent::~LocalTransportAgent()
74 {
75 }
76
77 void LocalTransportAgent::start()
78 {
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
82     string peer, context;
83     SyncConfig::splitConfigString(m_clientContext, peer, context);
84     if (!peer.empty()) {
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(),
87                               context.c_str()));
88     }
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()));
96     // }
97
98     if (m_forkexec) {
99         SE_THROW("local transport already started");
100     }
101     m_status = ACTIVE;
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));
110     m_forkexec->start();
111 }
112
113 /**
114  * Uses the D-Bus API provided by LocalTransportParent.
115  */
116 class LocalTransportParent : private GDBusCXX::DBusRemoteObject
117 {
118  public:
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"; }
124
125     LocalTransportParent(const GDBusCXX::DBusConnectionPtr &conn) :
126         GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
127         m_askPassword(*this, askPasswordName()),
128         m_storeSyncReport(*this, storeSyncReportName())
129     {}
130
131     /** LocalTransportAgent::askPassword() */
132     GDBusCXX::DBusClientCall1<std::string> m_askPassword;
133     /** LocalTransportAgent::storeSyncReport() */
134     GDBusCXX::DBusClientCall0 m_storeSyncReport;
135 };
136
137 /**
138  * Uses the D-Bus API provided by LocalTransportAgentChild.
139  */
140 class LocalTransportChild : public GDBusCXX::DBusRemoteObject
141 {
142  public:
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"; }
149
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())
155     {}
156
157     /**
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!)
161      */
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;
165
166     /** log output with level and message; process name will be added by parent */
167     GDBusCXX::SignalWatch2<string, string> m_logOutput;
168
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;
173
174 };
175
176 void LocalTransportAgent::logChildOutput(const std::string &level, const std::string &message)
177 {
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());
181 }
182
183 void LocalTransportAgent::onChildConnect(const GDBusCXX::DBusConnectionPtr &conn)
184 {
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(),
190                                                   true));
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));
196
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);
206         }
207     }
208     m_child->m_startSync.start(m_clientContext,
209                                StringPair(m_server->getConfigName(),
210                                           m_server->isEphemeral() ?
211                                           "ephemeral" :
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(),
218                                sources,
219                                boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
220 }
221
222 void LocalTransportAgent::onFailure(const std::string &error)
223 {
224     m_status = FAILED;
225     g_main_loop_quit(m_loop.get());
226
227     SE_LOG_ERROR(NULL, "local transport failed: %s", error.c_str());
228     m_parent.reset();
229     m_child.reset();
230 }
231
232 void LocalTransportAgent::onChildQuit(int status)
233 {
234     SE_LOG_DEBUG(NULL, "child process has quit with status %d", status);
235     g_main_loop_quit(m_loop.get());
236 }
237
238 static void GotPassword(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply,
239                         const std::string &password)
240 {
241     reply->done(password);
242 }
243
244 static void PasswordException(const boost::shared_ptr< GDBusCXX::Result1<const std::string &> > &reply)
245 {
246     // TODO: refactor, this is the same as dbusErrorCallback
247     try {
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
253         throw;
254     } catch (...) {
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));
259     }
260 }
261
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)
266 {
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(),
270                  descr.c_str());
271     try {
272         if (m_server) {
273             m_server->getUserInterfaceNonNull().askPasswordAsync(passwordName, descr, key,
274                                                                  // TODO refactor: use dbus-callbacks.h
275                                                                  boost::bind(GotPassword,
276                                                                              reply,
277                                                                              _1),
278                                                                  boost::bind(PasswordException,
279                                                                              reply));
280         } else {
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"));
284         }
285     } catch (...) {
286         PasswordException(reply);
287     }
288 }
289
290 void LocalTransportAgent::storeSyncReport(const std::string &report)
291 {
292     SE_LOG_DEBUG(NULL, "got child sync report:\n%s",
293                  report.c_str());
294     m_clientReport = SyncReport(report);
295 }
296
297 void LocalTransportAgent::getClientSyncReport(SyncReport &report)
298 {
299     report = m_clientReport;
300 }
301
302 void LocalTransportAgent::setContentType(const std::string &type)
303 {
304     m_contentType = type;
305 }
306
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)
310 {
311     g_main_loop_quit(loop->get());
312 }
313
314 void LocalTransportAgent::shutdown()
315 {
316     SE_LOG_DEBUG(NULL, "parent is shutting down");
317     if (m_forkexec) {
318         // block until child is done
319         boost::signals2::scoped_connection c(m_forkexec->m_onQuit.connect(boost::bind(gMainLoopQuit,
320                                                                                       &m_loop)));
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());
329         }
330
331         m_forkexec.reset();
332         m_parent.reset();
333         m_child.reset();
334     }
335 }
336
337 void LocalTransportAgent::send(const char *data, size_t len)
338 {
339     if (m_child) {
340         m_status = ACTIVE;
341         m_child->m_sendMsg.start(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)),
342                                  boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3));
343     } else {
344         m_status = FAILED;
345         SE_THROW_EXCEPTION(TransportException,
346                            "cannot send message because child process is gone");
347     }
348 }
349
350 void LocalTransportAgent::storeReplyMsg(const std::string &contentType,
351                                         const GDBusCXX::DBusArray<uint8_t> &reply,
352                                         const std::string &error)
353 {
354     m_replyMsg.assign(reinterpret_cast<const char *>(reply.second),
355                       reply.first);
356     m_replyContentType = contentType;
357     if (error.empty()) {
358         m_status = GOT_REPLY;
359     } else {
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());
363             m_status = FAILED;
364         }
365     }
366     g_main_loop_quit(m_loop.get());
367 }
368
369 void LocalTransportAgent::cancel()
370 {
371     if (m_forkexec) {
372         SE_LOG_DEBUG(NULL, "killing local transport child in cancel()");
373         m_forkexec->stop();
374     }
375     m_status = CANCELED;
376 }
377
378 TransportAgent::Status LocalTransportAgent::wait(bool noReply)
379 {
380     if (m_status == ACTIVE) {
381         // need next message; for noReply == true we are done
382         if (noReply) {
383             m_status = INACTIVE;
384         } else {
385             while (m_status == ACTIVE) {
386                 SE_LOG_DEBUG(NULL, "waiting for child to send message");
387                 if (m_forkexec &&
388                     m_forkexec->getState() == ForkExecParent::TERMINATED) {
389                     m_status = FAILED;
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;
398                         }
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+)\\): (.*)");
402                         int clientStatus;
403                         std::string clientExplanation;
404                         if (re.PartialMatch(m_clientReport.getError(), &clientStatus, &clientExplanation) &&
405                             (status == clientStatus ||
406                              status == clientStatus - sysync::LOCAL_STATUS_CODE)) {
407                             explanation += ": ";
408                             explanation += clientExplanation;
409                         }
410                         SE_THROW_EXCEPTION_STATUS(StatusException,
411                                                   explanation,
412                                                   m_clientReport.getStatus());
413                     } else {
414                         SE_THROW_EXCEPTION(TransportException,
415                                            "child process quit without sending its message");
416                     }
417                 }
418                 g_main_loop_run(m_loop.get());
419             }
420         }
421     }
422     return m_status;
423 }
424
425 void LocalTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType)
426 {
427     if (m_status != GOT_REPLY) {
428         SE_THROW("internal error, no reply available");
429     }
430     contentType = m_replyContentType;
431     data = m_replyMsg.c_str();
432     len = m_replyMsg.size();
433 }
434
435 void LocalTransportAgent::setTimeout(int seconds)
436 {
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.
441     //
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
446     // send a reply.
447     //
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.
451     //
452     // m_timeoutSeconds = seconds;
453 }
454
455 class LocalTransportUI : public UserInterface
456 {
457     boost::shared_ptr<LocalTransportParent> m_parent;
458
459 public:
460     LocalTransportUI(const boost::shared_ptr<LocalTransportParent> &parent) :
461         m_parent(parent)
462     {}
463
464     /** implements password request by asking the parent via D-Bus */
465     virtual string askPassword(const string &passwordName,
466                                const string &descr,
467                                const ConfigPasswordKey &key)
468     {
469         SE_LOG_DEBUG(NULL, "local transport child: requesting password %s, %s via D-Bus",
470                      passwordName.c_str(),
471                      descr.c_str());
472         std::string password;
473         std::string error;
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),
479                                                   _1, _2));
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));
487             }
488             g_main_context_iteration(NULL, true);
489         }
490         if (!error.empty()) {
491             Exception::tryRethrowDBus(error);
492             SE_THROW(StringPrintf("retrieving password failed: %s", error.c_str()));
493         }
494         return password;
495     }
496
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"); }
499
500 private:
501     void storePassword(std::string &res, std::string &errorRes, bool &haveRes, const std::string &password, const std::string &error)
502     {
503         if (!error.empty()) {
504             SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request failed: %s",
505                          error.c_str());
506             errorRes = error;
507         } else {
508             SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request succeeded");
509             res = password;
510         }
511         haveRes = true;
512     }
513 };
514
515 static void abortLocalSync(int sigterm)
516 {
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
524     // some other reason
525     signal(sigterm, SIG_DFL);
526     raise(sigterm);
527 }
528
529 /**
530  * Provides the "LogOutput" signal.
531  * LocalTransportAgentChild adds the method implementations
532  * before activating it.
533  */
534 class LocalTransportChildImpl : public GDBusCXX::DBusObjectHelper
535 {
536 public:
537     LocalTransportChildImpl(const GDBusCXX::DBusConnectionPtr &conn) :
538         GDBusCXX::DBusObjectHelper(conn,
539                                    LocalTransportChild::path(),
540                                    LocalTransportChild::interface(),
541                                    GDBusCXX::DBusObjectHelper::Callback_t(),
542                                    true),
543         m_logOutput(*this, LocalTransportChild::logOutputName())
544     {
545         add(m_logOutput);
546     };
547
548     GDBusCXX::EmitSignal2<std::string,
549                           std::string,
550                           true /* ignore transmission failures */> m_logOutput;
551 };
552
553 class ChildLogger : public Logger
554 {
555     std::auto_ptr<LogRedirect> m_parentLogger;
556     boost::weak_ptr<LocalTransportChildImpl> m_child;
557
558 public:
559     ChildLogger(const boost::shared_ptr<LocalTransportChildImpl> &child) :
560         m_parentLogger(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)),
561         m_child(child)
562     {}
563     ~ChildLogger()
564     {
565         m_parentLogger.reset();
566     }
567
568     /**
569      * Write message into our own log and send to parent.
570      */
571     virtual void messagev(const MessageOptions &options,
572                           const char *format,
573                           va_list args)
574     {
575         if (options.m_level <= m_parentLogger->getLevel()) {
576             m_parentLogger->process();
577             boost::shared_ptr<LocalTransportChildImpl> child = m_child.lock();
578             if (child) {
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);
585             }
586         }
587     }
588 };
589
590 class LocalTransportAgentChild : public TransportAgent
591 {
592     /** final return code of our main(): non-zero indicates that we need to shut down */
593     int m_ret;
594
595     /**
596      * sync report for client side of the local sync
597      */
598     SyncReport m_clientReport;
599
600     /** used to capture libneon output */
601     boost::scoped_ptr<LogRedirect> m_parentLogger;
602
603     /**
604      * provides connection to parent, created in constructor
605      */
606     boost::shared_ptr<ForkExecChild> m_forkexec;
607
608     /**
609      * proxy for the parent's D-Bus API in onConnect()
610      */
611     boost::shared_ptr<LocalTransportParent> m_parent;
612
613     /**
614      * our D-Bus interface, created in onConnect()
615      */
616     boost::shared_ptr<LocalTransportChildImpl> m_child;
617
618     /**
619      * sync context, created in Sync() D-Bus call
620      */
621     boost::scoped_ptr<SyncContext> m_client;
622
623     /**
624      * use this D-Bus result handle to send a message from child to parent
625      * in response to sync() or (later) sendMsg()
626      */
627     LocalTransportChild::ReplyPtr m_msgToParent;
628     void setMsgToParent(const LocalTransportChild::ReplyPtr &reply,
629                         const std::string &reason)
630     {
631         if (m_msgToParent) {
632             m_msgToParent->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
633                                                        "cancelling message: " + reason));
634         }
635         m_msgToParent = reply;
636     }
637
638     /** content type for message to parent */
639     std::string m_contentType;
640
641     /**
642      * message from parent
643      */
644     std::string m_message;
645
646     /**
647      * content type of message from parent
648      */
649     std::string m_messageType;
650
651     /**
652      * true after parent has received sync report, or sending failed
653      */
654     bool m_reportSent;
655
656     /**
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
661      */
662     Status m_status;
663
664     /**
665      * one loop run + error checking
666      */
667     void step(const std::string &status)
668     {
669         SE_LOG_DEBUG(NULL, "local transport: %s", status.c_str());
670         if (!m_forkexec ||
671             m_forkexec->getState() == ForkExecChild::DISCONNECTED) {
672             SE_THROW("local transport child no longer has a parent, terminating");
673         }
674         g_main_context_iteration(NULL, true);
675         if (m_ret) {
676             SE_THROW("local transport child encountered a problem, terminating");
677         }
678     }
679
680     static void onParentQuit()
681     {
682         // Never free this state blocker. We can only abort and
683         // quit from now on.
684         static boost::shared_ptr<SuspendFlags::StateBlocker> abortGuard;
685         SE_LOG_ERROR(NULL, "sync parent quit unexpectedly");
686         abortGuard = SuspendFlags::getSuspendFlags().abort();
687     }
688
689     void onConnect(const GDBusCXX::DBusConnectionPtr &conn)
690     {
691         SE_LOG_DEBUG(NULL, "child connected to parent");
692
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());
697         m_child->activate();
698
699         // set up connection to parent
700         m_parent.reset(new LocalTransportParent(conn));
701     }
702
703     void onFailure(SyncMLStatus status, const std::string &reason)
704     {
705         SE_LOG_DEBUG(NULL, "child fork/exec failed: %s", reason.c_str());
706
707         // record failure for parent
708         if (!m_clientReport.getStatus()) {
709             m_clientReport.setStatus(status);
710         }
711         if (!reason.empty() &&
712             m_clientReport.getError().empty()) {
713             m_clientReport.setError(reason);
714         }
715
716         // return to step()
717         m_ret = 1;
718     }
719
720     // D-Bus API, see LocalTransportChild;
721     // must keep number of parameters < 9, the maximum supported by
722     // our D-Bus binding
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)
731     {
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");
736         if (delay) {
737             Sleep(atoi(delay));
738         }
739
740         // initialize sync context
741         m_client.reset(new SyncContext(std::string("target-config") + clientContext,
742                                        serverConfig.first,
743                                        serverConfig.second == "ephemeral" ?
744                                        serverConfig.second :
745                                        serverConfig.second + "/." + clientContext,
746                                        boost::shared_ptr<TransportAgent>(this, NoopAgentDestructor()),
747                                        serverDoLogging));
748         if (serverConfig.second == "ephemeral") {
749             m_client->makeEphemeral();
750         }
751         boost::shared_ptr<UserInterface> ui(new LocalTransportUI(m_parent));
752         m_client->setUserInterface(ui);
753
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);
758
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));
763         }
764
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.
769         //
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);
776         }
777         if (!serverSyncCredentials.second.empty()) {
778             m_client->setSyncPassword(serverSyncCredentials.second, true);
779         }
780
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);
785         }
786
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);
792         }
793
794         // activate all sources in client targeted by main config,
795         // with right uri
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;
806
807                 if (!targetNodes.dataConfigExists()) {
808                     if (targetName.empty()) {
809                         m_client->throwError("missing URI for one of the sources");
810                     } else {
811                         m_client->throwError(StringPrintf("%s: source not configured",
812                                                           fullTargetName.c_str()));
813                     }
814                 }
815
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()));
824                 }
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.
837                     mode = SYNC_SLOW;
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).
844                     mode = SYNC_TWO_WAY;
845                 }
846                 targetSource.setSync(PrettyPrintSyncMode(mode, true), true);
847                 targetSource.setURI(sourceName, true);
848             }
849         }
850
851
852         // ready for m_client->sync()
853         m_status = ACTIVE;
854     }
855
856     void sendMsg(const std::string &contentType,
857                  const GDBusCXX::DBusArray<uint8_t> &data,
858                  const LocalTransportChild::ReplyPtr &reply)
859     {
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),
865                              data.first);
866             m_messageType = contentType;
867             m_status = GOT_REPLY;
868         } else {
869             reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error",
870                                                "child not expecting any message"));
871         }
872     }
873
874 public:
875     LocalTransportAgentChild() :
876         m_ret(0),
877         m_forkexec(SyncEvo::ForkExecChild::create()),
878         m_reportSent(false),
879         m_status(INACTIVE)
880     {
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.
885         //
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);
893
894         m_forkexec->connect();
895     }
896
897     boost::shared_ptr<ChildLogger> createLogger()
898     {
899         return boost::shared_ptr<ChildLogger>(new ChildLogger(m_child));
900     }
901
902     void run()
903     {
904         SuspendFlags &s = SuspendFlags::getSuspendFlags();
905
906         while (!m_parent) {
907             if (s.getState() != SuspendFlags::NORMAL) {
908                 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for parent");
909                 return;
910             }
911             step("waiting for parent");
912         }
913         while (!m_client) {
914             if (s.getState() != SuspendFlags::NORMAL) {
915                 SE_LOG_DEBUG(NULL, "aborted, returning while waiting for Sync() call from parent");
916             }
917             step("waiting for Sync() call from parent");
918         }
919         try {
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);
928
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);
936
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);
940         } catch (...) {
941             string explanation;
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);
947             }
948             if (m_parent) {
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
956                 if (m_parent) {
957                     SE_LOG_DEBUG(NULL, "waiting for parent's ACK for sync report");
958                     g_main_context_iteration(NULL, true);
959                 }
960             }
961             throw;
962         }
963
964         if (m_parent) {
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");
973             }
974         }
975     }
976
977     void syncReportReceived(const std::string &error)
978     {
979         SE_LOG_DEBUG(NULL, "sending sync report to parent: %s",
980                      error.empty() ? "done" : error.c_str());
981         m_reportSent = true;
982     }
983
984     int getReturnCode() const { return m_ret; }
985
986     /**
987      * set transport specific URL of next message
988      */
989     virtual void setURL(const std::string &url) {}
990
991     /**
992      * define content type for post, see content type constants
993      */
994     virtual void setContentType(const std::string &type)
995     {
996         m_contentType = type;
997     }
998
999     /**
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.
1004      *
1005      * Simply deleting the transport is an *unnormal* shutdown that
1006      * does not communicate with the peer.
1007      */
1008     virtual void shutdown()
1009     {
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();
1017         }
1018         if (m_status != FAILED) {
1019             m_status = CLOSED;
1020         }
1021     }
1022
1023     /**
1024      * start sending message
1025      *
1026      * Memory must remain valid until reply is received or
1027      * message transmission is canceled.
1028      *
1029      * @param data      start address of data to send
1030      * @param len       number of bytes
1031      */
1032     virtual void send(const char *data, size_t len)
1033     {
1034         SE_LOG_DEBUG(NULL, "child local transport sending %ld bytes", (long)len);
1035         if (m_msgToParent) {
1036             m_status = ACTIVE;
1037             m_msgToParent->done(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)));
1038             m_msgToParent.reset();
1039         } else {
1040             m_status = FAILED;
1041             SE_THROW("cannot send data to parent because parent is not waiting for message");
1042         }
1043     }
1044
1045     /**
1046      * cancel an active message transmission
1047      *
1048      * Blocks until send buffer is no longer in use.
1049      * Returns immediately if nothing pending.
1050      */
1051     virtual void cancel() {}
1052
1053
1054     /**
1055      * Wait for completion of an operation initiated earlier.
1056      * The operation can be a send with optional reply or
1057      * a close request.
1058      *
1059      * Returns immediately if no operations is pending.
1060      *
1061      * @param noReply    true if no reply is required for a running send;
1062      *                   only relevant for transports used by a SyncML server
1063      */
1064     virtual Status wait(bool noReply = false)
1065     {
1066         SuspendFlags &s = SuspendFlags::getSuspendFlags();
1067         while (m_status == ACTIVE &&
1068                s.getState() == SuspendFlags::NORMAL) {
1069             step("waiting for next message");
1070         }
1071         return m_status;
1072     }
1073
1074     /**
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
1078      * status in wait().
1079      *
1080      * @param seconds      number of seconds to wait before timing out, zero for no timeout
1081      */
1082     virtual void setTimeout(int seconds) {}
1083
1084     /**
1085      * provides access to reply data
1086      *
1087      * Memory pointer remains valid as long as
1088      * transport agent is not deleted and no other
1089      * message is sent.
1090      */
1091     virtual void getReply(const char *&data, size_t &len, std::string &contentType)
1092     {
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");
1096         }
1097         data = m_message.c_str();
1098         len = m_message.size();
1099         contentType = m_messageType;
1100     }
1101 };
1102
1103 int LocalTransportMain(int argc, char **argv)
1104 {
1105     // delay the client for debugging purposes
1106     const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY");
1107     if (delay) {
1108         Sleep(atoi(delay));
1109     }
1110
1111     SyncContext::initMain("syncevo-local-sync");
1112
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);
1125
1126     // SIGPIPE must be ignored, some system libs (glib GIO?) trigger
1127     // it. SIGINT/TERM will be handled via SuspendFlags once the sync
1128     // runs.
1129     struct sigaction sa;
1130     memset(&sa, 0, sizeof(sa));
1131     sa.sa_handler = SIG_IGN;
1132     sigaction(SIGPIPE, &sa, NULL);
1133
1134     try {
1135         if (getenv("SYNCEVOLUTION_DEBUG")) {
1136             Logger::instance().setLevel(Logger::DEBUG);
1137         }
1138         // process name will be set to target config name once it is known
1139         Logger::setProcessName("syncevo-local-sync");
1140
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).
1145         {
1146             Logger::Handle handle(child->createLogger());
1147             logger.reset(handle);
1148         }
1149         child->run();
1150         int ret = child->getReturnCode();
1151         logger.reset();
1152         child.reset();
1153         return ret;
1154     } catch ( const std::exception &ex ) {
1155         SE_LOG_ERROR(NULL, "%s", ex.what());
1156     } catch (...) {
1157         SE_LOG_ERROR(NULL, "unknown error");
1158     }
1159
1160     return 1;
1161 }
1162
1163 SE_END_CXX