This patch implements file monitoring backend using Solaris native event ports. D-Bus uses it to check for changes in configuration files so that it can automatically re-read them when necessary. Should be offered upstream once we are sure it works as expected. --- dbus-1.14.6/configure.ac +++ dbus-1.14.6/configure.ac @@ -214,6 +214,7 @@ AC_ARG_ENABLE([apparmor], AC_ARG_ENABLE(libaudit,AS_HELP_STRING([--enable-libaudit],[build audit daemon support for SELinux]),enable_libaudit=$enableval,enable_libaudit=auto) AC_ARG_ENABLE(inotify, AS_HELP_STRING([--enable-inotify],[build with inotify support (linux only)]),enable_inotify=$enableval,enable_inotify=auto) AC_ARG_ENABLE(kqueue, AS_HELP_STRING([--enable-kqueue],[build with kqueue support]),enable_kqueue=$enableval,enable_kqueue=auto) +AC_ARG_ENABLE(evports, AS_HELP_STRING([--enable-evports],[build with evports support]),enable_evports=$enableval,enable_evports=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(launchd, AS_HELP_STRING([--enable-launchd],[build with launchd auto-launch support]),enable_launchd=$enableval,enable_launchd=auto) AC_ARG_ENABLE(systemd, AS_HELP_STRING([--enable-systemd],[build with systemd at_console support]),enable_systemd=$enableval,enable_systemd=auto) @@ -934,6 +935,26 @@ fi AM_CONDITIONAL(DBUS_BUS_ENABLE_KQUEUE, test x$have_kqueue = xyes) +# evports checks +if test x$enable_evports = xno ; then + have_evports=no +else + have_evports=yes + AC_CHECK_HEADER(port.h, , have_evports=no) + AC_CHECK_FUNC(port_create, , have_evports=no) + + if test x$enable_evports = xyes -a x$have_evports = xno; then + AC_MSG_ERROR(evports support explicitly enabled but not available) + fi +fi + +dnl check if evports backend is enabled +if test x$have_evports = xyes; then + AC_DEFINE(DBUS_BUS_ENABLE_EVPORTS,1,[Use evports]) +fi + +AM_CONDITIONAL(DBUS_BUS_ENABLE_EVPORTS, test x$have_evports = xyes) + # launchd checks if test x$enable_launchd = xno ; then have_launchd=no @@ -1807,6 +1828,7 @@ echo " Building AppArmor support: ${have_apparmor} Building inotify support: ${have_inotify} Building kqueue support: ${have_kqueue} + Building evports support: ${have_evports} Building systemd support: ${have_systemd} Traditional activation: ${enable_traditional_activation} Building X11 code: ${have_x11} --- dbus-1.14.6/bus/Makefile.am +++ dbus-1.14.6/bus/Makefile.am @@ -79,12 +79,16 @@ endif if DBUS_BUS_ENABLE_KQUEUE DIR_WATCH_SOURCE=dir-watch-kqueue.c else +if DBUS_BUS_ENABLE_EVPORTS +DIR_WATCH_SOURCE=dir-watch-evports.c +else if DBUS_BUS_ENABLE_INOTIFY DIR_WATCH_SOURCE=dir-watch-inotify.c else DIR_WATCH_SOURCE=dir-watch-default.c endif endif +endif noinst_LTLIBRARIES = libdbus-daemon-internal.la --- dbus-1.14.6/bus/CMakeLists.txt +++ dbus-1.14.6/bus/CMakeLists.txt @@ -30,6 +30,8 @@ if(DBUS_BUS_ENABLE_INOTIFY) set(DIR_WATCH_SOURCE dir-watch-inotify.c) elseif(DBUS_BUS_ENABLE_KQUEUE) set(DIR_WATCH_SOURCE dir-watch-kqueue.c) +elseif(DBUS_BUS_ENABLE_EVPORTS) + set(DIR_WATCH_SOURCE dir-watch-evports.c) else(DBUS_BUS_ENABLE_INOTIFY) set(DIR_WATCH_SOURCE dir-watch-default.c) endif() --- dbus-1.14.6/README.cmake +++ dbus-1.14.6/README.cmake @@ -211,6 +211,10 @@ DBUS_BUS_ENABLE_INOTIFY:BOOL=ON // enable kqueue as dir watch backend DBUS_BUS_ENABLE_KQUEUE:BOOL=ON +*Solaris only: +// enable evports as dir watch backend +DBUS_BUS_ENABLE_EVPORTS:BOOL=ON + x11 only: // Build with X11 auto launch support DBUS_BUILD_X11:BOOL=ON --- dbus-1.14.6/cmake/ConfigureChecks.cmake +++ dbus-1.14.6/cmake/ConfigureChecks.cmake @@ -27,6 +27,7 @@ check_include_file(string.h HAVE_STR check_include_file(strings.h HAVE_STRINGS_H) check_include_file(syslog.h HAVE_SYSLOG_H) check_include_files("stdint.h;sys/types.h;sys/event.h" HAVE_SYS_EVENT_H) +check_include_files("port.h" HAVE_PORT_H) check_include_file(sys/inotify.h HAVE_SYS_INOTIFY_H) check_include_file(sys/random.h HAVE_SYS_RANDOM_H) check_include_file(sys/resource.h HAVE_SYS_RESOURCE_H) --- dbus-1.14.6/CMakeLists.txt +++ dbus-1.14.6/CMakeLists.txt @@ -510,6 +510,13 @@ endif() string(TOUPPER ${CMAKE_SYSTEM_NAME} sysname) if("${sysname}" MATCHES ".*SOLARIS.*") + option(DBUS_BUS_ENABLE_EVPORTS "build with evports support (solaris only)" ON) + if(DBUS_BUS_ENABLE_EVPORTS) + if(NOT HAVE_PORT_H) + message(FATAL_ERROR "port.h not found!") + endif() + endif() + option(HAVE_CONSOLE_OWNER_FILE "enable console owner file(solaris only)" ON) if(HAVE_CONSOLE_OWNER_FILE) set(DBUS_CONSOLE_OWNER_FILE "/dev/console" CACHE STRING "Directory to check for console ownerhip") @@ -765,6 +772,7 @@ message(" Building bus stats API: message(" installing system libs: ${DBUS_INSTALL_SYSTEM_LIBS} ") message(" Building inotify support: ${DBUS_BUS_ENABLE_INOTIFY} ") message(" Building kqueue support: ${DBUS_BUS_ENABLE_KQUEUE} ") +message(" Building evports support: ${DBUS_BUS_ENABLE_EVPORTS} ") message(" Building systemd support: ${DBUS_BUS_ENABLE_SYSTEMD} ") message(" systemd system install dir:${DBUS_SYSTEMD_SYSTEMUNITDIR} ") message(" systemd user install dir: ${DBUS_SYSTEMD_USERUNITDIR} ") --- dbus-1.14.6/bus/dir-watch-evports.c +++ dbus-1.14.6/bus/dir-watch-evports.c @@ -0,0 +1,322 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch-evports.c OS specific directory change notification for message bus + * + * Copyright (C) 2023 Oracle and/or its affiliates. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "dir-watch.h" + +/* This limit is not just for the directories given by dbus but also for + * all the files and directories within. + */ +#define MAX_OBJECTS_TO_WATCH 128 + +/* Because the data passed to port_associate are no longer significant after + * the call, we can save memory by overlapping file_obj structures (such that + * alignment is not an issue). + */ +static dbus_int64_t fobjs[MAX_OBJECTS_TO_WATCH + sizeof(struct file_obj) - 1]; + +static int port = -1; +static DBusWatch *watch = NULL; +static DBusLoop *loop = NULL; +/* Used to define the point since given directories are being watched */ +struct timespec last_sighup; + +/* For the comparison of timespec structures */ +# define tslower(a, b) \ + (((a).tv_sec == (b).tv_sec) ? \ + ((a).tv_nsec < (b).tv_nsec) : \ + ((a).tv_sec < (b).tv_sec)) + + +static dbus_bool_t +_handle_evport_watch (DBusWatch *_watch, unsigned int flags, void *data) +{ + struct timespec nullts = { 0, 0 }; + int res; + port_event_t pe; + + res = port_get (port, &pe, &nullts); + + /* Sleep for half a second to avoid a race when files are being installed. */ + usleep (500000); + + if (res == 0) + { + /* Remember this point in order to not miss any further changes. */ + clock_gettime (CLOCK_REALTIME, &last_sighup); + _dbus_verbose ("Sending SIGHUP signal on reception of an event\n"); + (void) kill (_dbus_getpid (), SIGHUP); + } + else if (res < 0 && errno == EBADF) + { + port = -1; + _dbus_assert (watch == _watch); + if (watch != NULL) + { + _dbus_loop_remove_watch (loop, watch); + _dbus_watch_invalidate (watch); + _dbus_watch_unref (watch); + watch = NULL; + } + _dbus_verbose ("Sending SIGHUP signal since event port has been closed\n"); + (void) kill (_dbus_getpid (), SIGHUP); + } + + return TRUE; +} + +static void +_shutdown_watch (void *data) +{ + if (loop == NULL) + return; + + _dbus_loop_unref (loop); + loop = NULL; +} + +static int +_init_watch (BusContext *context) +{ + if (loop == NULL) + { + if (!_dbus_register_shutdown_func (_shutdown_watch, NULL)) + { + _dbus_warn ("Unable to register shutdown function"); + return FALSE; + } + + loop = bus_context_get_loop (context); + _dbus_loop_ref (loop); + + /* This is the point since when changes to files is being watched */ + if (clock_gettime (CLOCK_REALTIME, &last_sighup)) + { + _dbus_warn ("Cannot create evport; error '%s'", _dbus_strerror (errno)); + return FALSE; + } + } + + return TRUE; +} + +static dbus_bool_t +_init_evport (void) +{ + /* First, close the old port if necessary. We can do so now and not miss any + * notification as any changes since the last SIGHUP will be caught by the + * new port (as last_sighup was set back then). + */ + if (loop && watch) + { + _dbus_loop_remove_watch (loop, watch); + } + + if (watch) + { + _dbus_watch_invalidate (watch); + _dbus_watch_unref (watch); + watch = NULL; + } + + if (port != -1) + { + /* This will also dissociate all the objects previously associated + * with given port - no need to do so one by one. + */ + close (port); + port = -1; + } + + /* Rebuild everything at this point */ + port = port_create (); + if (port == -1) + { + _dbus_warn ("Cannot create evport; error '%s'", _dbus_strerror (errno)); + goto out; + } + + watch = _dbus_watch_new (port, DBUS_WATCH_READABLE, TRUE, + _handle_evport_watch, NULL, NULL); + + if (watch == NULL) + { + _dbus_warn ("Unable to create evport watch\n"); + goto out1; + } + + if (!_dbus_loop_add_watch (loop, watch)) + { + _dbus_warn ("Unable to add reload watch to main loop"); + goto out2; + } + + return TRUE; + +out2: + if (watch) + { + _dbus_watch_invalidate (watch); + _dbus_watch_unref (watch); + watch = NULL; + } + +out1: + if (port != -1) + { + close (port); + port = -1; + } + +out: + return FALSE; +} + +static dbus_bool_t +_associate (char *filepath, int index, dbus_bool_t file_only) +{ + int res; + struct stat sb; + struct file_obj *fobj; + + res = stat (filepath, &sb); + if (res < 0) + { + if (errno != ENOENT) + { + _dbus_warn ("Cannot stat '%s'; error '%s'", filepath, _dbus_strerror (errno)); + } + return FALSE; + } + + if (file_only && !S_ISREG(sb.st_mode)) + return FALSE; + + /* file_obj structures can safely overlap as the data is no longer + * necessary after the call + */ + fobj = (struct file_obj *)(&fobjs[index]); + fobj->fo_name = filepath; + fobj->fo_atime = sb.st_atim; + fobj->fo_mtime = sb.st_mtim; + fobj->fo_ctime = sb.st_ctim; + + /* Event ports sadly don't let us set last_sighup to fobj directly as it only + * checks for differences; it doesn't make timespec comparison. + */ + if (tslower (last_sighup, sb.st_mtim) || tslower (last_sighup, sb.st_ctim)) { + /* Changing one value to something different from stat forces immediate event. */ + fobj->fo_mtime = last_sighup; + } + + res = port_associate (port, PORT_SOURCE_FILE, (uintptr_t)fobj, FILE_MODIFIED|FILE_ATTRIB, NULL); + if (res < 0) + { + _dbus_warn ("Cannot setup evport for '%s'; error '%s'", filepath, _dbus_strerror (errno)); + return FALSE; + } + return TRUE; +} + +void +bus_set_watched_dirs (BusContext *context, DBusList **directories) +{ + DBusList *link; + char buffer[256]; + int num_objects; + DIR *folder; + struct dirent *entry = NULL; + + if (!_init_watch (context)) + return; + + if (!_init_evport ()) + return; + + num_objects = 0; + link = _dbus_list_get_first_link (directories); + while (link != NULL && num_objects < MAX_OBJECTS_TO_WATCH) + { + if (!_associate ((char *)link->data, num_objects, FALSE)) + { + /* Currently, this implementation goes through every directory given, + * even if some of them fail stat/association, which is different + * from other implementations. I am not sure whether that is an + * issue; we'll see. + */ + link = _dbus_list_get_next_link (directories, link); + continue; + } + + num_objects++; + + /* Go through the entire directory */ + folder = opendir ((char *)link->data); + if (folder == NULL) + { + _dbus_warn ("Cannot read directory '%s'; error '%s'", (char *)link->data, _dbus_strerror (errno)); + link = _dbus_list_get_next_link (directories, link); + continue; + } + + while ((entry = readdir (folder))) + { + if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, "..")) + continue; + + if (num_objects >= MAX_OBJECTS_TO_WATCH) + break; + + /* Construct full path to files within */ + buffer[0] = 0; + strlcat (buffer, (char*)link->data, 256); + if (buffer[strlen ((char*)link->data)-1] != '/') { + strlcat (buffer, "/", 256); + } + strlcat (buffer, entry->d_name, 256); + + if (!_associate (buffer, num_objects, TRUE)) + continue; + + num_objects++; + } + closedir (folder); + link = _dbus_list_get_next_link (directories, link); + } + + if (link != NULL || entry != NULL) + { + _dbus_warn ("Too many files and directories to watch them all, only watching first %d.", MAX_OBJECTS_TO_WATCH); + } +}