f2378b7909
The optional args array will be used when executing the helper executable.
310 lines
9.9 KiB
C++
310 lines
9.9 KiB
C++
/*
|
|
* Copyright (C) 2011 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#ifndef INCL_FORK_EXEC
|
|
# define INCL_FORK_EXEC
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#if defined(HAVE_GLIB)
|
|
|
|
#include <syncevo/util.h>
|
|
#include <syncevo/GLibSupport.h>
|
|
#include <syncevo/SmartPtr.h>
|
|
|
|
#include "gdbus-cxx-bridge.h"
|
|
|
|
#include <boost/signals2.hpp>
|
|
|
|
SE_BEGIN_CXX
|
|
|
|
#ifndef GDBUS_CXX_HAVE_DISCONNECT
|
|
class ForkExecParentDBusAPI;
|
|
#endif
|
|
|
|
/**
|
|
* Utility class which starts a specific helper binary in a second
|
|
* process. The helper binary is identified via its base name like
|
|
* "syncevo-dbus-helper", exact location is then determined
|
|
* automatically, or via an absolute path.
|
|
*
|
|
* Direct D-Bus communication is set up automatically. For this to
|
|
* work, the helper must use ForkExecChild::connect(). To debug this
|
|
* when using GIO DBus, set G_DBUS_DEBUG=message.
|
|
*
|
|
* There are more options mentioned here:
|
|
* http://developer.gnome.org/gio/unstable/ch03.html
|
|
*
|
|
* Progress (like "client connected") and failures ("client disconnected")
|
|
* are reported via boost::signal2 signals. To make progess, the user of
|
|
* this class must run a glib event loop in the default context.
|
|
*
|
|
* Note that failures encountered inside the class methods themselves
|
|
* will be reported via exceptions. Only asynchronous errors encountered
|
|
* inside the event loop are reported via the failure signal.
|
|
*
|
|
* Slots connected to the signals may throw exceptions. They will be
|
|
* propagated up to the caller when ForkExec methods were called directly.
|
|
* When thrown inside the event loop, the exception will be logged and
|
|
* then reported via the onFailure signal.
|
|
*/
|
|
|
|
class ForkExec : private boost::noncopyable {
|
|
public:
|
|
/**
|
|
* Called when the D-Bus connection is up and running. It is ready
|
|
* to register objects that the peer might need. It is
|
|
* guaranteed that any objects registered now will be ready before
|
|
* the helper gets a chance to make D-Bus calls.
|
|
*/
|
|
typedef boost::signals2::signal<void (const GDBusCXX::DBusConnectionPtr &)> OnConnect;
|
|
OnConnect m_onConnect;
|
|
|
|
/**
|
|
* Called when an exception cannot be propagated up to the caller
|
|
* because it was thrown inside the event loop, or when some other
|
|
* kind of failure is encountered which cannot be reported via some
|
|
* other means. The original problem is already logged when
|
|
* onFailure is invoked, so further logging should not be done unless
|
|
* it adds new information.
|
|
*
|
|
* Bad results of asynchronous method calls are reported via the
|
|
* result callback of the method, not via onFailure.
|
|
*
|
|
* When a failure occurs, the peer should be considered dead and
|
|
* the connection to it should be shut down (if any had been
|
|
* established at all).
|
|
*
|
|
* When the child quits before establishing a connection or quits
|
|
* with a non-zero return code, onFailure will be called. That way
|
|
* a user of ForkExecParent doesn't have to connect to onQuit.
|
|
*/
|
|
typedef boost::signals2::signal<void (SyncMLStatus, const std::string &)> OnFailure;
|
|
OnFailure m_onFailure;
|
|
|
|
/**
|
|
* A unique string for the ForkExecParent/Child pair which can be used
|
|
* as D-Bus path component.
|
|
*/
|
|
std::string getInstance() const { return m_instance; }
|
|
|
|
protected:
|
|
ForkExec();
|
|
|
|
std::string m_instance;
|
|
};
|
|
|
|
/**
|
|
* The parent side of a fork/exec.
|
|
*/
|
|
class ForkExecParent : public ForkExec
|
|
{
|
|
public:
|
|
~ForkExecParent();
|
|
|
|
/**
|
|
* A ForkExecParent instance must be created via this factory
|
|
* method and then be tracked in a shared pointer. This method
|
|
* will not start the helper yet: first connect your slots, then
|
|
* call start().
|
|
*/
|
|
static boost::shared_ptr<ForkExecParent> create(const std::string &helper,
|
|
const std::vector<std::string> &args = std::vector<std::string>());
|
|
|
|
/**
|
|
* the helper string passed to create()
|
|
*/
|
|
std::string getHelper() const { return m_helper; }
|
|
|
|
/**
|
|
* run the helper executable in the parent process
|
|
*/
|
|
void start();
|
|
|
|
/**
|
|
* request that the child process terminates by sending it a
|
|
* SIGINT and/or SIGTERM
|
|
* @param signal if zero (default), send both signals, otherwise
|
|
* the specified one
|
|
*/
|
|
void stop(int signal = 0);
|
|
|
|
/**
|
|
* kill the child process without giving it a chance to shut down
|
|
* by sending it a SIGKILL
|
|
*/
|
|
void kill();
|
|
|
|
/**
|
|
* Called when the helper has quit. The parameter of the signal is
|
|
* the return status of the helper (see waitpid()). If output
|
|
* redirection is active, then this signal will only be invoked
|
|
* after processing all output.
|
|
*/
|
|
typedef boost::signals2::signal<void (int)> OnQuit;
|
|
OnQuit m_onQuit;
|
|
|
|
/**
|
|
* Called when output from the helper is available. The buffer is
|
|
* guaranteed to be nul-terminated with a byte that is not
|
|
* included in the size.
|
|
*
|
|
* Register slots *before* calling start(), because output
|
|
* redirection of the helper will only be done if someone is
|
|
* waiting for it. If m_onOutput has a slot, then both stderr and
|
|
* stdout are redirected into the same stream and only m_onOutput
|
|
* will be invoked.
|
|
*/
|
|
typedef boost::signals2::signal<void (const char *buffer, size_t length)> OnOutput;
|
|
OnOutput m_onOutput;
|
|
OnOutput m_onStdout;
|
|
OnOutput m_onStderr;
|
|
|
|
enum State {
|
|
IDLE, /**< instance constructed, but start() not called yet */
|
|
STARTING, /**< start() called */
|
|
CONNECTED, /**< child has connected, D-Bus connection established */
|
|
TERMINATED /**< child has quit */
|
|
};
|
|
State getState()
|
|
{
|
|
return m_hasQuit ? TERMINATED :
|
|
m_hasConnected ? CONNECTED :
|
|
m_watchChild ? STARTING :
|
|
IDLE;
|
|
}
|
|
|
|
/**
|
|
* Get the childs pid. This can be used as a unique id common to
|
|
* both parent and child.
|
|
*/
|
|
int getChildPid() { return static_cast<int>(m_childPid); }
|
|
|
|
/**
|
|
* Simply pushes a new environment variable onto m_envStrings.
|
|
*/
|
|
void addEnvVar(const std::string &name, const std::string &value);
|
|
|
|
private:
|
|
ForkExecParent(const std::string &helper, const std::vector<std::string> &args);
|
|
|
|
std::string m_helper;
|
|
std::vector<std::string> m_args;
|
|
boost::shared_ptr<GDBusCXX::DBusServerCXX> m_server;
|
|
boost::scoped_array<char *> m_argv;
|
|
std::list<std::string> m_argvStrings;
|
|
boost::scoped_array<char *> m_env;
|
|
std::list<std::string> m_envStrings;
|
|
GPid m_childPid;
|
|
bool m_hasConnected;
|
|
bool m_hasQuit;
|
|
gint m_status;
|
|
bool m_sigIntSent;
|
|
bool m_sigTermSent;
|
|
#ifndef GDBUS_CXX_HAVE_DISCONNECT
|
|
boost::scoped_ptr<class ForkExecParentDBusAPI> m_api;
|
|
#endif
|
|
|
|
/** invoke m_onOutput while reading from a single stream */
|
|
bool m_mergedStdoutStderr;
|
|
GIOChannel *m_out, *m_err;
|
|
guint m_outID, m_errID;
|
|
|
|
GSource *m_watchChild;
|
|
static void watchChildCallback(GPid pid,
|
|
gint status,
|
|
gpointer data) throw();
|
|
|
|
void newClientConnection(GDBusCXX::DBusConnectionPtr &conn) throw();
|
|
|
|
void setupPipe(GIOChannel *&channel, guint &sourceID, int fd);
|
|
static gboolean outputReady(GIOChannel *source,
|
|
GIOCondition condition,
|
|
gpointer data) throw ();
|
|
|
|
void checkCompletion() throw ();
|
|
|
|
static void forked(gpointer me) throw();
|
|
};
|
|
|
|
/**
|
|
* The child side of a fork/exec.
|
|
*
|
|
* At the moment, the child cannot monitor the parent or kill it.
|
|
* Might be added (if needed), in which case the corresponding
|
|
* ForkExecParent members should be moved to the common ForkExec.
|
|
*/
|
|
class ForkExecChild : public ForkExec
|
|
{
|
|
public:
|
|
/**
|
|
* A ForkExecChild instance must be created via this factory
|
|
* method and then be tracked in a shared pointer. The process
|
|
* must have been started by ForkExecParent (directly or indirectly)
|
|
* and any environment variables set by ForkExecParent must still
|
|
* be set.
|
|
*/
|
|
static boost::shared_ptr<ForkExecChild> create();
|
|
|
|
/**
|
|
* Initiates connection to parent, connect to ForkExec::m_onConnect
|
|
* before calling this function to be notified of success and
|
|
* ForkExec::m_onFailure for failures.
|
|
*
|
|
* m_onConnect is guaranteed to be called before message processing
|
|
* starts. It's the right place to add objects to the bus that are
|
|
* expected by the parent.
|
|
*/
|
|
void connect();
|
|
|
|
/**
|
|
* Called when the parent has quit.
|
|
*/
|
|
typedef boost::signals2::signal<void ()> OnQuit;
|
|
OnQuit m_onQuit;
|
|
|
|
enum State {
|
|
IDLE, /**< created, connect() not called yet */
|
|
CONNECTING, /**< connect() called but no connection yet */
|
|
CONNECTED, /**< connection established */
|
|
DISCONNECTED /**< lost connection or failed to establish it */
|
|
};
|
|
State getState() const { return m_state; }
|
|
|
|
/**
|
|
* true if the current process was created by ForkExecParent
|
|
*/
|
|
static bool wasForked();
|
|
|
|
private:
|
|
ForkExecChild();
|
|
|
|
static const char *getParentDBusAddress();
|
|
void connectionLost();
|
|
State m_state;
|
|
};
|
|
|
|
SE_END_CXX
|
|
|
|
#endif // HAVE_GLIB
|
|
#endif // INCL_FORK_EXEC
|
|
|