From d7f227cb7ed0c1dc5dc5b37d3014fcf8ea37d209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Mon, 6 Jan 2025 09:28:04 +0100 Subject: [PATCH] Add event ports support (Solaris file monitoring backend) --- CMakeLists.txt | 8 + README.cmake | 4 + bus/CMakeLists.txt | 2 + bus/dir-watch-evports.c | 324 ++++++++++++++++++++++++++++++++++++ bus/meson.build | 2 + cmake/ConfigureChecks.cmake | 1 + meson.build | 17 ++ meson_options.txt | 7 + 8 files changed, 365 insertions(+) create mode 100644 bus/dir-watch-evports.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c6aa0a08..d691ee2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,6 +498,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") @@ -751,6 +758,7 @@ message(" Building bus stats API: ${DBUS_ENABLE_STATS} " 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} ") diff --git a/README.cmake b/README.cmake index 81cac84b..cbec8ccc 100644 --- a/README.cmake +++ b/README.cmake @@ -214,6 +214,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 diff --git a/bus/CMakeLists.txt b/bus/CMakeLists.txt index febfaeb0..0b7735f0 100644 --- a/bus/CMakeLists.txt +++ b/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() diff --git a/bus/dir-watch-evports.c b/bus/dir-watch-evports.c new file mode 100644 index 00000000..c55f08bb --- /dev/null +++ b/bus/dir-watch-evports.c @@ -0,0 +1,324 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch-evports.c OS specific directory change notification for message bus + * + * Copyright (C) 2024 Oracle and/or its affiliates. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * 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); + } +} diff --git a/bus/meson.build b/bus/meson.build index 058e3b25..5d98c5fc 100644 --- a/bus/meson.build +++ b/bus/meson.build @@ -112,6 +112,8 @@ libdbus_daemon_internal_sources = [ if use_kqueue libdbus_daemon_internal_sources += 'dir-watch-kqueue.c' +elif use_evports + libdbus_daemon_internal_sources += 'dir-watch-evports.c' elif use_inotify libdbus_daemon_internal_sources += 'dir-watch-inotify.c' else diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake index 786ba9dd..d69a24b2 100644 --- a/cmake/ConfigureChecks.cmake +++ b/cmake/ConfigureChecks.cmake @@ -20,6 +20,7 @@ check_include_file(io.h HAVE_IO_H) # internal check_include_file(linux/close_range.h HAVE_LINUX_CLOSE_RANGE_H) check_include_file(linux/magic.h HAVE_LINUX_MAGIC_H) check_include_file(locale.h HAVE_LOCALE_H) +check_include_file(port.h HAVE_PORT_H) check_include_file(signal.h HAVE_SIGNAL_H) check_include_file(stdatomic.h HAVE_STDATOMIC_H) check_include_file(stdio.h HAVE_STDIO_H) # dbus-sysdeps.h diff --git a/meson.build b/meson.build index 7281b7ec..336f2a35 100644 --- a/meson.build +++ b/meson.build @@ -499,6 +499,22 @@ else endif endif +if get_option('evports').disabled() + use_evports = false +else + use_evports = ( + cc.has_header('port.h', args: compile_args_c) and + cc.has_function( + 'port_create', + prefix: '#include ', + args: compile_args_c, + ) + ) + if get_option('evports').enabled() and not use_evports + error('event ports support requested but not found') + endif +endif + if get_option('launchd').disabled() use_launchd = false else @@ -1431,6 +1447,7 @@ summary_dict += { 'Building AppArmor support': apparmor.found(), 'Building inotify support': use_inotify, 'Building kqueue support': use_kqueue, + 'Building evports support': use_evports, 'Building systemd support': use_systemd, 'Building elogind support': use_elogind, 'Traditional activation': use_traditional_activation, diff --git a/meson_options.txt b/meson_options.txt index 6f382437..e3b8aaa1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -130,6 +130,13 @@ option( description: 'Kqueue support' ) +option( + 'evports', + type: 'feature', + value: 'auto', + description: 'event port support' +) + option( 'launchd', type: 'feature',