From 918525201c7b5b59d223354a90cbd8ad342ff22f Mon Sep 17 00:00:00 2001 From: Benjamin Reed Date: Mon, 28 Jan 2008 13:24:13 -0500 Subject: [PATCH] Launchd support for Mac OS X (and potentially other platforms) Add support for launchd (http://developer.apple.com/macosx/launchd.html) Launchd is superficially like inetd in that it controls all resources for system-wide and session-level daemons. In the case of this dbus implementation, launchd allocates and listens on a socket, dynamically launching dbus when a client tries to access the socket for the first time. When dbus-daemon is started, it asks launchd for the socket file descriptor, and then initializes itself using that pre-existing FD. Note that this only occurs if in session.conf is set to "autolaunch:" -- it is still possible to us DBus on Mac OS X in the normal UNIXy way by specifying a tcp: or unix: session address. From the client-side, this code will do a number of things to attempt to auto-determine the socket. If DBUS_SESSION_BUS_ADDRESS is set, the client will connect normally, just like any other unix client. If it is not, it checks for the environment variable DBUS_LAUNCHD_SESSION_BUS_SOCKET and crafts a unix: socket URL from it. If that is not set, it asks launchd (through the 'launchctl' command) for the socket directly, and crafts a unix: socket URL from it instead. It then continues normally. --- bus/Makefile.am | 8 ++- bus/org.freedesktop.dbus-session.plist.in | 31 +++++++ bus/session.conf.in | 2 +- configure.in | 44 +++++++++ dbus/Makefile.am | 9 ++- dbus/dbus-bus.c | 89 +++++++++++++++++++- dbus/dbus-server-launchd.c | 136 +++++++++++++++++++++++++++++ dbus/dbus-server-launchd.h | 35 ++++++++ dbus/dbus-server-unix.c | 26 ++++++ 9 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 bus/org.freedesktop.dbus-session.plist.in create mode 100644 dbus/dbus-server-launchd.c create mode 100644 dbus/dbus-server-launchd.h diff --git a/bus/Makefile.am b/bus/Makefile.am index 3b4f69d..420742b 100644 --- a/bus/Makefile.am +++ b/bus/Makefile.am @@ -9,12 +9,18 @@ EFENCE= CONFIG_IN_FILES= \ session.conf.in \ - system.conf.in + system.conf.in \ + org.freedesktop.dbus-session.plist.in config_DATA= \ session.conf \ system.conf +if DBUS_ENABLE_LAUNCHD +agentdir=$(LAUNCHD_AGENT_DIR) +agent_DATA=org.freedesktop.dbus-session.plist +endif + if DBUS_USE_LIBXML XML_SOURCES=config-loader-libxml.c endif diff --git a/bus/org.freedesktop.dbus-session.plist.in b/bus/org.freedesktop.dbus-session.plist.in new file mode 100644 index 0000000..6f1ad8e --- /dev/null +++ b/bus/org.freedesktop.dbus-session.plist.in @@ -0,0 +1,31 @@ + + + + + Label + org.freedesktop.dbus-session + + ServiceIPC + + + + OnDemand + + + ProgramArguments + + @DBUS_DAEMONDIR@/dbus-daemon + --nofork + --session + + + Sockets + + session + + SecureSocketWithKey + DBUS_LAUNCHD_SESSION_BUS_SOCKET + + + + diff --git a/bus/session.conf.in b/bus/session.conf.in index b2dee5b..ea29849 100644 --- a/bus/session.conf.in +++ b/bus/session.conf.in @@ -8,7 +8,7 @@ session - unix:tmpdir=@DBUS_SESSION_SOCKET_DIR@ + @DBUS_SESSION_BUS_DEFAULT_ADDRESS@ diff --git a/configure.in b/configure.in index 34918ee..00723f2 100644 --- a/configure.in +++ b/configure.in @@ -77,6 +77,7 @@ AC_ARG_ENABLE(inotify, AS_HELP_STRING([--enable-inotify],[build with inotify sup AC_ARG_ENABLE(kqueue, AS_HELP_STRING([--enable-kqueue],[build with kqueue support]),enable_kqueue=$enableval,enable_kqueue=auto) AC_ARG_ENABLE(console-owner-file, AS_HELP_STRING([--enable-console-owner-file],[enable console owner file]),enable_console_owner_file=$enableval,enable_console_owner_file=auto) AC_ARG_ENABLE(userdb-cache, AS_HELP_STRING([--enable-userdb-cache],[build with userdb-cache support]),enable_userdb_cache=$enableval,enable_userdb_cache=yes) +AC_ARG_ENABLE(launchd, AS_HELP_STRING([--enable-launchd],[build with launchd auto-launch support]),enable_launchd=$enableval,enable_launchd=auto) AC_ARG_WITH(xml, AS_HELP_STRING([--with-xml=[libxml/expat]],[XML library to use])) AC_ARG_WITH(init-scripts, AS_HELP_STRING([--with-init-scripts=[redhat]],[Style of init scripts to install])) @@ -86,6 +87,7 @@ AC_ARG_WITH(system-pid-file, AS_HELP_STRING([--with-system-pid-file=[pidfile]],[ AC_ARG_WITH(system-socket, AS_HELP_STRING([--with-system-socket=[filename]],[UNIX domain socket for systemwide daemon])) AC_ARG_WITH(console-auth-dir, AS_HELP_STRING([--with-console-auth-dir=[dirname]],[directory to check for console ownerhip])) AC_ARG_WITH(console-owner-file, AS_HELP_STRING([--with-console-owner-file=[filename]],[file whose owner determines current console owner])) +AC_ARG_WITH(launchd-agent-dir, AS_HELP_STRING([--with-launchd-agent-dir=[dirname]],[directory to put the launchd agent (default: /Library/LaunchAgents)])) AC_ARG_WITH(dbus_user, AS_HELP_STRING([--with-dbus-user=],[User for running the DBUS daemon (messagebus)])) AC_ARG_WITH(dbus_daemondir, AS_HELP_STRING([--with-dbus-daemondir=[dirname]],[Directory for installing the DBUS daemon])) @@ -949,6 +951,38 @@ fi AM_CONDITIONAL(DBUS_BUS_ENABLE_KQUEUE, test x$have_kqueue = xyes) +# launchd checks +if test x$enable_launchd = xno ; then + have_launchd=no +else + have_launchd=yes + AC_CHECK_HEADER([launch.h], , have_launchd=no) + AC_PATH_PROG([LAUNCHCTL], [launchctl]) + if test "x$LAUNCHCTL" = "x"; then + have_launchd=no + fi + + if test x$enable_launchd = xyes -a x$have_launchd = xno ; then + AC_MSG_ERROR([launchd support explicitly enabled but not available]) + fi +fi + +dnl check if launchd is enabled +if test x$have_launchd = xyes; then + AC_DEFINE(DBUS_ENABLE_LAUNCHD,1,[Use launchd autolaunch]) +fi + +AM_CONDITIONAL(DBUS_ENABLE_LAUNCHD, test x$have_launchd = xyes) + +#### Directory to place launchd agent file +if test "x$with_launchd_agent_dir" = "x"; then + LAUNCHD_AGENT_DIR="/Library/LaunchAgents" +else + LAUNCHD_AGENT_DIR="$with_launchd_agent_dir" +fi + +AC_SUBST(LAUNCHD_AGENT_DIR) + dnl console owner file if test x$enable_console_owner_file = xno ; then have_console_owner_file=no; @@ -1271,6 +1305,13 @@ fi AC_DEFINE_UNQUOTED(DBUS_SESSION_SOCKET_DIR, "$DBUS_SESSION_SOCKET_DIR", [Where per-session bus puts its sockets]) AC_SUBST(DBUS_SESSION_SOCKET_DIR) +if test x$have_launchd = xyes; then + DBUS_SESSION_BUS_DEFAULT_ADDRESS="autolaunch:" +else + DBUS_SESSION_BUS_DEFAULT_ADDRESS="unix:tmpdir=$DBUS_SESSION_SOCKET_DIR" +fi +AC_SUBST(DBUS_SESSION_BUS_DEFAULT_ADDRESS) + AC_DEFINE_UNQUOTED(DBUS_UNIX, "1", [Defined on UNIX and Linux systems and not on Windows]) AC_OUTPUT([ @@ -1279,6 +1320,7 @@ dbus/dbus-arch-deps.h bus/system.conf bus/session.conf bus/messagebus +bus/org.freedesktop.dbus-session.plist bus/rc.messagebus bus/dbus-daemon.1 Makefile @@ -1345,6 +1387,7 @@ echo " Building Doxygen docs: ${enable_doxygen_docs} Building XML docs: ${enable_xml_docs} Building cache support: ${enable_userdb_cache} + Building launchd support: ${have_launchd} Gettext libs (empty OK): ${INTLLIBS} Using XML parser: ${with_xml} Init scripts style: ${with_init_scripts} @@ -1359,6 +1402,7 @@ echo " System bus user: ${DBUS_USER} Session bus services dir: ${EXPANDED_DATADIR}/dbus-1/services 'make check' socket dir: ${TEST_SOCKET_DIR} + launchd agent dir: ${LAUNCHD_AGENT_DIR} " if test x$enable_tests = xyes; then diff --git a/dbus/Makefile.am b/dbus/Makefile.am index e966a43..3a9c3ad 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -9,6 +9,12 @@ INCLUDES=-I$(top_builddir) -I$(top_srcdir) $(DBUS_CLIENT_CFLAGS) @PIC_CFLAGS@ -D dbusincludedir=$(includedir)/dbus-1.0/dbus dbusarchincludedir=$(libdir)/dbus-1.0/include/dbus +if DBUS_ENABLE_LAUNCHD +LAUNCHD_SOURCES = \ + dbus-server-launchd.h \ + dbus-server-launchd.c +endif + lib_LTLIBRARIES=libdbus-1.la dbusinclude_HEADERS= \ @@ -92,7 +98,8 @@ DBUS_LIB_SOURCES= \ dbus-uuidgen.c \ dbus-uuidgen.h \ dbus-watch.c \ - dbus-watch.h + dbus-watch.h \ + $(LAUNCHD_SOURCES) ## dbus-md5.c \ ## dbus-md5.h \ diff --git a/dbus/dbus-bus.c b/dbus/dbus-bus.c index c7f43e8..ac6d3c8 100644 --- a/dbus/dbus-bus.c +++ b/dbus/dbus-bus.c @@ -22,15 +22,25 @@ * */ +#include #include "dbus-bus.h" #include "dbus-protocol.h" #include "dbus-internals.h" #include "dbus-message.h" #include "dbus-marshal-validate.h" +#include "dbus-string.h" #include "dbus-threads-internal.h" #include "dbus-connection-internal.h" #include +#ifdef DBUS_ENABLE_LAUNCHD +#include +#include +#include +#define POPEN_MAX_LENGTH MAXPATHLEN + 2 +#endif + + /** * @defgroup DBusBus Message bus APIs * @ingroup DBus @@ -99,6 +109,77 @@ static dbus_bool_t initialized = FALSE; */ _DBUS_DEFINE_GLOBAL_LOCK (bus); +#ifdef DBUS_ENABLE_LAUNCHD +/** + * Static initialization for turning a launchd environment into + * DBUS_SESSION_BUS_ADDRESS + */ + +static void +launchd_init_environment() { + FILE *launchd = NULL; + const char *env_var = NULL; + char launchctl_output[POPEN_MAX_LENGTH]; + DBusString socket_filename; + DBusString session_address; + + if (!_dbus_string_init (&socket_filename)) + { + return; + } + + if (!_dbus_string_init (&session_address)) + goto launchd_finished_0; + + env_var = _dbus_getenv("DBUS_SESSION_BUS_ADDRESS"); + + // User has already overridden in the environment + if (env_var != NULL && (strlen(env_var) > 0)) + goto launchd_finished_1; + + env_var = _dbus_getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET"); + + if (env_var == NULL || (strlen(env_var) == 0) || access(env_var, W_OK) != 0) + { + launchd = popen("launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET", "r"); + if (launchd != NULL && !feof(launchd)) + { + if (fgets (launchctl_output, POPEN_MAX_LENGTH, launchd) != NULL) + { + if (!_dbus_string_append(&socket_filename, launchctl_output)) + goto launchd_finished_1; + + // strip the carriage-return + _dbus_string_shorten(&socket_filename, 1); + + env_var = _dbus_string_get_data(&socket_filename); + } + } + } + + if (env_var != NULL && strlen(env_var) != 0 && access(env_var, W_OK) == 0) + { + if (!_dbus_string_append (&session_address, "unix:path=")) + goto launchd_finished_1; + + if (!_dbus_string_append (&session_address, env_var)) + goto launchd_finished_1; + + _dbus_setenv("DBUS_SESSION_BUS_ADDRESS", _dbus_string_get_const_data(&session_address)); + } + +launchd_finished_1: + _dbus_string_free(&session_address); + +launchd_finished_0: + _dbus_string_free(&socket_filename); + if (launchd != NULL) + pclose(launchd); + +} + +#endif + /** * Global lock covering all BusData on any connection. The bet is * that some lock contention is better than more memory @@ -159,6 +240,10 @@ init_connections_unlocked (void) ++i; } +#ifdef DBUS_ENABLE_LAUNCHD + launchd_init_environment(); +#endif + /* Don't init these twice, we may run this code twice if * init_connections_unlocked() fails midway through. * In practice, each block below should contain only one @@ -195,10 +280,12 @@ init_connections_unlocked (void) if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) { _dbus_verbose ("Filling in session bus address...\n"); - + if (!get_from_env (&bus_connection_addresses[DBUS_BUS_SESSION], "DBUS_SESSION_BUS_ADDRESS")) + { return FALSE; + } if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) bus_connection_addresses[DBUS_BUS_SESSION] = diff --git a/dbus/dbus-server-launchd.c b/dbus/dbus-server-launchd.c new file mode 100644 index 0000000..e5101f3 --- /dev/null +++ b/dbus/dbus-server-launchd.c @@ -0,0 +1,136 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-win.c Server implementation for WIN network protocols. + * + * Copyright (C) 2008 Benjamin Reed + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include "dbus-server-socket.h" +#include "dbus-server-launchd.h" + +/** + * @defgroup DBusServerLaunchd DBusServer implementations for Launchd + * @ingroup DBusInternals + * @brief Implementation details of DBusServer with Launchd support + * + * @{ + */ + +/** + * Creates a new server from launchd. + * + * @param socket_key they key to use when looking up the file descriptor from launchd + * @param error location to store reason for failure. + * @returns the new server, or #NULL on failure. + */ + +DBusServer* +_dbus_server_new_for_launchd_fd (const char *socket_key, DBusError *error) +{ + DBusServer *server; + DBusString address; + int launchd_fd; + launch_data_t sockets_dict, checkin_response; + launch_data_t checkin_request; + launch_data_t listening_fd_array, listening_fd; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + if (!_dbus_string_append (&address, "autolaunch:key=")) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto l_failed_0; + } + if (!_dbus_string_append (&address, socket_key)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto l_failed_0; + } + + if ((checkin_request = launch_data_new_string(LAUNCH_KEY_CHECKIN)) == NULL) { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, "launch_data_new_string(\"%s\") Unable to create string.\n", LAUNCH_KEY_CHECKIN); + goto l_failed_0; + } + + if ((checkin_response = launch_msg(checkin_request)) == NULL) { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, "launch_msg(\"%s\") IPC failure: %s\n", LAUNCH_KEY_CHECKIN, strerror(errno)); + goto l_failed_0; + } + + if (LAUNCH_DATA_ERRNO == launch_data_get_type(checkin_response)) { + dbus_set_error (error, DBUS_ERROR_FAILED, "Check-in failed: %s\n", strerror(launch_data_get_errno(checkin_response))); + goto l_failed_0; + } + + sockets_dict = launch_data_dict_lookup(checkin_response, LAUNCH_JOBKEY_SOCKETS); + if (NULL == sockets_dict) { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, "No sockets found to answer requests on!\n"); + goto l_failed_0; + } + + listening_fd_array = launch_data_dict_lookup(sockets_dict, socket_key); + if (NULL == listening_fd_array) { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, "No known sockets found to answer requests on!\n"); + goto l_failed_0; + } + + if (launch_data_array_get_count(listening_fd_array)!=1) { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, "Expected 1 socket from launchd, got %d.\n", launch_data_array_get_count(listening_fd_array)); + goto l_failed_0; + } + + listening_fd=launch_data_array_get_index(listening_fd_array, 0); + launchd_fd=launch_data_get_fd(listening_fd); + + _dbus_fd_set_close_on_exec (launchd_fd); + + if (launchd_fd < 0) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto l_failed_0; + } + + server = _dbus_server_new_for_socket (&launchd_fd, 1, &address); + if (server == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_SERVER, "Unable to listen on launchd fd %d.", launchd_fd); + goto l_failed_0; + } + + _dbus_string_free (&address); + + return server; + + l_failed_0: + _dbus_string_free (&address); + + return NULL; +} + +/** @} */ + diff --git a/dbus/dbus-server-launchd.h b/dbus/dbus-server-launchd.h new file mode 100644 index 0000000..8719ecb --- /dev/null +++ b/dbus/dbus-server-launchd.h @@ -0,0 +1,35 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-launchd.h Server implementation for launchd integration. + * + * Copyright (C) 2008 Benjamin Reed + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef DBUS_SERVER_LAUNCHD_H +#define DBUS_SERVER_LAUNCHD_H + +#include +#include + +DBUS_BEGIN_DECLS + +DBusServer* _dbus_server_new_for_launchd_fd (const char *socket_fd, DBusError *error); + +DBUS_END_DECLS + +#endif /* DBUS_SERVER_LAUNCHD_H */ diff --git a/dbus/dbus-server-unix.c b/dbus/dbus-server-unix.c index 1dda5d1..ddc0944 100644 --- a/dbus/dbus-server-unix.c +++ b/dbus/dbus-server-unix.c @@ -21,6 +21,7 @@ * */ +#include #include "dbus-internals.h" #include "dbus-server-unix.h" #include "dbus-server-socket.h" @@ -29,6 +30,10 @@ #include "dbus-sysdeps-unix.h" #include "dbus-string.h" +#ifdef DBUS_ENABLE_LAUNCHD +#include "dbus-server-launchd.h" +#endif + /** * @defgroup DBusServerUnix DBusServer implementations for UNIX * @ingroup DBusInternals @@ -145,6 +150,27 @@ _dbus_server_listen_platform_specific (DBusAddressEntry *entry, return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; } } +#ifdef DBUS_ENABLE_LAUNCHD + else if (strcmp (method, "autolaunch") == 0) + { + + const char *launchd_key = dbus_address_entry_get_value (entry, "key"); + if (launchd_key == NULL) + launchd_key = "session"; + *server_p = _dbus_server_new_for_launchd_fd (launchd_key, error); + + if (*server_p != NULL) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_OK; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } +#endif else { /* If we don't handle the method, we return NULL with the -- 1.5.3.7