On Mo, 2011-12-19 at 10:57 +0100, Chris Kühl wrote:
> On Fri, Dec 16, 2011 at 3:41 PM, Patrick Ohly <[email protected]> wrote:
> > I have that same example working based on GDBus + libdbus. What I don't
> > like about it is that the connection is based on listen() + connect().
> > This makes it harder for the parent to detect that the child died: if
> > the parent did the usual trick (create socket pair, fork+exec child,
> > close its own side of the socket pair), then a closed socket would
> > indicate that the child died. With listen() + connect() it has to check
> > for existence of the child periodically. SIGCHILD does not integrate
> > into the main loop well, but I see that g_spawn() has a GChildWatch -
> > that should do.
> >
> 
> Yes, looking at the source for the GChildWatch related code it blocks
> SIGCHLD. When the mainloop's worker routine is run it checks if any of
> the blocked signals are pending and then iterates over the
> GChildWatchSources checking if the process has exited. So by checking
> the return value of g_spawn_async_with_pipes and setting up the
> GChildWatch I don't see a way to miss the childs exit, either.

Attached is a first API proposal for the fork/exec helper class and how
it could be used in a small test program. Does that look sane?

I haven't started implementing any of the new methods, but it should be
fairly straightforward (famous last words).

> > While working on this I got unhappy with some aspects of the current
> > D-Bus C++ wrapper. The result is the "dbus-cleanup" branch. Chris, what
> > do you think about that?
> 
> This looks fine. Moving the connection info to DBusObject definitely
> makes sense and reduces the code a bit.

Okay, merged into master.

-- 
Best Regards, Patrick Ohly

The content of this message is my personal opinion only and although
I am an employee of Intel, the statements I make here in no way
represent Intel's position on the issue, nor am I authorized to speak
on behalf of Intel on this matter.

/*
 * 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

#ifdef HAVE_GLIB

#include <syncevo/util.h>
#include <syncevo/GLibSupport.h>
#include <syncevo/SmartPtr.h>

#include <boost/signals2.hpp>

SE_BEGIN_CXX

/**
 * 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().
 *
 * 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.
 *
 * 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.
 */

class ForkExec : private boost::noncopyable {
 public:
    /**
     * the glib main loop passed to create() or connect() in one of
     * the derived classes
     */
    GMainLoopPtr getLoop() const;

    /**
     * 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;

 protected:
    ForkExec(const GMainLoopPtr &loop);
};

/**
 * The parent side of a fork/exec.
 */
class ForkExecParent : public ForkExec
{
 public:
    /**
     * 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 GMainLoopPtr &loop,
                                                    const std::string &helper);

    /**
     * the helper string passed to create()
     */
    std::string getHelper() const;

    /**
     * run the helper executable in the parent process
     */
    void start();

    /**
     * request that the child process terminates by sending it a
     * SIGINT
     */
    void stop();

    /**
     * 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()).
     */
    typedef boost::signals2::signal<void (int)> OnQuit;
    OnQuit m_onQuit;

 private:
    ForkExecParent(const GMainLoopPtr &loop,
                   const std::string &helper);
};

/**
 * 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(const GMainLoopPtr &loop);

    /**
     * true if the current process was created by ForkExecParent
     */
    static bool wasForked();

 private:
    ForkExecChild(const GMainLoopPtr &loop);
};

SE_END_CXX

#endif // HAVE_GLIB
#endif // INCL_FORK_EXEC

#include "gdbus-cxx-bridge.h"
#include <syncevo/GLibSupport.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/ForkExec.h>

#include <iostream>
#include <signal.h>

#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>

SE_GOBJECT_TYPE(GMainLoop);

SyncEvo::GMainLoopPtr loop;

class Test
{
    GDBusCXX::DBusObjectHelper m_server;
    // GDBusCXX::DBusRemoteObject m_dbusAPI;
    // GDBusCXX::SignalWatch0 m_disconnected;

public:
    Test(const GDBusCXX::DBusConnectionPtr &conn) :
        // will close connection
        m_server(conn, "/test", "org.example.Test", GDBusCXX::DBusObjectHelper::Callback_t(), true)
        // m_dbusAPI(conn, DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "" /* sender? */),
        // m_disconnected(m_dbusAPI, "Disconnected")
    {
        m_server.add(this, &Test::hello, "Hello");
    }

    void activate()
    {
        m_server.activate();
        // fails with an unspecific error inside libdbus, don't rely on "Disconnected"
        // m_disconnected.activate(boost::bind(&Test::disconnected, this));

        // not implemented either
        // b_dbus_set_disconnect_function(m_server.getConnection(),
        // staticDisconnected,
        // NULL,
        // NULL);
    }

    std::string hello(const std::string &in)
    {
        std::cout << "hello() called with " << in << std::endl;
        return "world";
    }

    void disconnected()
    {
        std::cout << "connection disconnected";
    }

    // static void staticDisconnected(DBusConnection*conn, void *data)
    // {
    // std::cout << "connection disconnected";
    // }
};

static void newClientConnection(GDBusCXX::DBusServerCXX &server, GDBusCXX::DBusConnectionPtr &conn,
                                boost::scoped_ptr<Test> &testptr)
{
    std::cout << "new connection, " <<
        (dbus_connection_get_is_authenticated(conn.get()) ? "authenticated" : "not authenticated") <<
        std::endl;
    testptr.reset(new Test(conn.get()));
    testptr->activate();
}

static void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn,
                           boost::scoped_ptr<Test> &testptr)
{
    std::cout << "child is ready, " <<
        (dbus_connection_get_is_authenticated(conn.get()) ? "authenticated" : "not authenticated") <<
        std::endl;
    testptr.reset(new Test(conn.get()));
    testptr->activate();
}

static void onQuit(int status)
{
    std::cout << "child has quit, status " << status << std::endl;
    g_main_loop_quit(loop.get());
}

class TestProxy : public GDBusCXX::DBusRemoteObject
{
public:
    TestProxy(const GDBusCXX::DBusConnectionPtr &conn) :
        GDBusCXX::DBusRemoteObject(conn.get(), "/test", "org.example.Test", "direct.peer"),
        m_hello(*this, "Hello") {
    }

    GDBusCXX::DBusClientCall1<std::string> m_hello;
};

static void helloCB(GMainLoop *loop, const std::string &res, const std::string &error)
{
    if (!error.empty()) {
        std::cout << "call failed: " << error << std::endl;
    } else {
        std::cout << "hello('hello') = " << res << std::endl;
    }
    g_main_loop_quit(loop);
}

static void callServer(const GDBusCXX::DBusConnectionPtr &conn)
{
    TestProxy proxy(conn);
    proxy.m_hello(std::string("world"), boost::bind(helloCB, loop.get(), _1, _2));
}

void signalHandler (int sig)
{
    if (loop) {
        g_main_loop_quit(loop.get());
    }
}


int main(int argc, char **argv)
{
    int ret = 0;

    signal (SIGABRT, &signalHandler);
    signal (SIGTERM, &signalHandler);
    signal (SIGINT, &signalHandler);

    try {
        gboolean opt_server;
        gboolean opt_fork_exec;
        gchar *opt_address;
        GOptionContext *opt_context;
        // gboolean opt_allow_anonymous;
        SyncEvo::GErrorCXX gerror;
        GDBusCXX::DBusErrorCXX dbusError;
        GOptionEntry opt_entries[] = {
            { "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL },
            { "forkexec", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Use fork+exec to start the client (implies --server)", NULL },
            { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL },
            // { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL },
            { NULL}
        };

        g_type_init();

        opt_address = NULL;
        opt_server = FALSE;
        opt_fork_exec = FALSE;
        // opt_allow_anonymous = FALSE;

        opt_context = g_option_context_new("peer-to-peer example");
        g_option_context_add_main_entries(opt_context, opt_entries, NULL);
        if (!g_option_context_parse(opt_context, &argc, &argv, gerror)) {
            gerror.throwError("parsing command line options");
        }
        // if (!opt_server && opt_allow_anonymous) {
        // throw stdruntime_error("The --allow-anonymous option only makes sense when used with --server.");
        // }

        loop.set(g_main_loop_new (NULL, FALSE), "main loop");

        if (opt_fork_exec) {
            boost::scoped_ptr<Test> testptr;
            boost::shared_ptr<SyncEvo::ForkExecParent> forkexec =
                SyncEvo::ForkExecParent::create(loop,
                                                argv[0]);
            forkexec->m_onConnect.connect(boost::bind(onChildConnect, _1, boost::ref(testptr)));
            forkexec->m_onQuit.connect(onQuit);
            g_main_loop_run(loop.get());
        } else if (opt_server) {
            boost::shared_ptr<GDBusCXX::DBusServerCXX> server =
                GDBusCXX::DBusServerCXX::listen(opt_address ?
                                                opt_address : "",
                                                &dbusError);
            if (!server) {
                dbusError.throwFailure("starting server");
            }
            std::cout << "Server is listening at: " << server->getAddress() << std::endl;
            boost::scoped_ptr<Test> testptr;
            server->setNewConnectionCallback(boost::bind(newClientConnection, _1, _2, boost::ref(testptr)));

            g_main_loop_run(loop.get());
        } else if (SyncEvo::ForkExecChild::wasForked()) {
            boost::shared_ptr<SyncEvo::ForkExecChild> forkexec =
                SyncEvo::ForkExecChild::create(loop);

            forkexec->m_onConnect.connect(callServer);
            g_main_loop_run(loop.get());
        } else {
            if (!opt_address) {
                throw std::runtime_error("need server address");
            }

            GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection(opt_address,
                                                                       &dbusError);
            if (!conn) {
                dbusError.throwFailure("connecting to server");
            }
            // closes connection
            GDBusCXX::DBusObject guard(conn, "foo", "bar", true);

            callServer(conn);
            g_main_loop_run(loop.get());
        }

        loop.set(NULL);
    } catch (const std::exception &ex) {
        std::cout << ex.what() << std::endl;
        ret = 1;
        loop.set(NULL);
    }

    std::cout << "server done" << std::endl;

    return ret;
}
_______________________________________________
SyncEvolution mailing list
[email protected]
http://lists.syncevolution.org/listinfo/syncevolution

Reply via email to