diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7f44e8b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +*.a +*.la +*.lo +*.log +*.o +*.stamp +*.swp +*.trs +*~ +.config.args +.deps/ +.dirstamp +.libs/ +/*.tar.xz +Makefile +Makefile.in +autom4te.cache/ +aclocal.m4 +build-aux/ +config.h +config.h.in +config.log +config.status +configure +libtool +m4/ +ratbagctl +ratbagd +stamp-* diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..e721ab8e --- /dev/null +++ b/Makefile.am @@ -0,0 +1,93 @@ +# ------------------------------------------------------------------------------ +# main + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_MAKEFLAGS = --no-print-directory +AUTOMAKE_OPTIONS = color-tests parallel-tests + +GCC_COLORS ?= 'ooh, shiny!' +export GCC_COLORS + +SUBDIRS = . + +# remove targets if the command fails +.DELETE_ON_ERROR: + +# keep intermediate files +.SECONDARY: + +# Keep the test-suite.log +.PRECIOUS: $(TEST_SUITE_LOG) Makefile + +CLEANFILES = $(BUILT_SOURCES) +DISTCLEANFILES = +EXTRA_DIST = +BUILT_SOURCES = +lib_LTLIBRARIES = +noinst_LTLIBRARIES = +bin_PROGRAMS = +noinst_PROGRAMS = + +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -I $(top_srcdir)/src \ + -I $(top_builddir)/src \ + $(OUR_CPPFLAGS) + +AM_CFLAGS = $(OUR_CFLAGS) +AM_LDFLAGS = $(OUR_LDFLAGS) + +# ------------------------------------------------------------------------------ +# libshared + +noinst_LTLIBRARIES += \ + libshared.la + +libshared_la_SOURCES = \ + src/shared-macro.h \ + src/shared-rbtree.h \ + src/shared-rbtree.c + +libshared_la_CFLAGS = \ + $(AM_CFLAGS) + +libshared_la_LIBADD = \ + -lm + +# ------------------------------------------------------------------------------ +# ratbagctl + +bin_PROGRAMS += \ + ratbagctl + +ratbagctl_SOURCES = \ + src/ratbagctl.c + +ratbagctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) + +ratbagctl_LDADD = \ + libshared.la \ + $(LIBSYSTEMD_LIBS) + +# ------------------------------------------------------------------------------ +# ratbagd + +bin_PROGRAMS += \ + ratbagd + +ratbagd_SOURCES = \ + src/ratbagd.h \ + src/ratbagd.c \ + src/ratbagd-device.c + +ratbagd_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) + +ratbagd_LDADD = \ + libshared.la \ + $(UDEV_LIBS) \ + $(LIBSYSTEMD_LIBS) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..dbb5141d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# +# autogen.sh: prepare build system +# Run this script to prepare the build-system for your local checkout. It +# requires the usual autotools dependencies to be installed. +# +# Prepare build system, but don't configure: +# $ ./autogen.sh +# +# Prepare build system and run ./configure with debugging enabled: +# $ ./autogen.sh c +# +# Prepare build system and run ./configure with llvm over gcc: +# $ ./autogen.sh l +# +# You can put additional arguments to ./configure into ".config.args". They +# will be appended to the arguments used by this script. +# + +set -e + +oldpwd=$(pwd) +topdir=$(dirname $0) +cd $topdir + +autoreconf --force --install --symlink + +if [ -f "$topdir/.config.args" ]; then + args="$args $(cat $topdir/.config.args)" +fi + +cd $oldpwd + +if [ "x$1" = "xc" ]; then + $topdir/configure --enable-debug $args + make clean +elif [ "x$1" = "xl" ]; then + $topdir/configure CC=clang $args + make clean +else + echo + echo "----------------------------------------------------------------" + echo "Initialized build system. For a common configuration please run:" + echo "----------------------------------------------------------------" + echo + echo "$topdir/configure $args" + echo +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..2d40488d --- /dev/null +++ b/configure.ac @@ -0,0 +1,151 @@ +# ------------------------------------------------------------------------------ +# main + +AC_PREREQ([2.64]) + +AC_INIT([ratbagd], + [1], + [http://www.github.com/libratbag/ratbagd], + [ratbagd], + [http://www.github.com/libratbag/ratbagd]) + +AC_CONFIG_SRCDIR([src/ratbagd.h]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_AUX_DIR([build-aux]) + +AC_USE_SYSTEM_EXTENSIONS +AC_SYS_LARGEFILE +AM_MAINTAINER_MODE([enable]) +AM_INIT_AUTOMAKE([foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects parallel-tests]) +AM_SILENT_RULES([yes]) +AC_CANONICAL_HOST +AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.]) + +LT_PREREQ(2.2) +LT_INIT([disable-static]) + +AC_PROG_MKDIR_P +AC_PROG_LN_S +AC_PROG_SED +AC_PROG_GREP +AC_PROG_AWK +AC_PROG_CC_C99 + +AC_PATH_PROG([M4], [m4]) +AC_PATH_PROG([XSLTPROC], [xsltproc]) + +AS_IF([! ln --relative --help > /dev/null 2>&1], [AC_MSG_ERROR([*** ln doesn't support --relative ***])]) + +# ------------------------------------------------------------------------------ +# arguments + +AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debug options])) + +# ------------------------------------------------------------------------------ +# toolchain + +CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\ + -pipe \ + -Wall \ + -Wextra \ + -Wno-inline \ + -Wundef \ + "-Wformat=2 -Wformat-security -Wformat-nonliteral" \ + -Wlogical-op \ + -Wsign-compare \ + -Wmissing-include-dirs \ + -Wold-style-definition \ + -Wpointer-arith \ + -Winit-self \ + -Wdeclaration-after-statement \ + -Wfloat-equal \ + -Wsuggest-attribute=noreturn \ + -Wmissing-prototypes \ + -Wstrict-prototypes \ + -Wredundant-decls \ + -Wmissing-declarations \ + -Wmissing-noreturn \ + -Wendif-labels \ + -Wstrict-aliasing=2 \ + -Wwrite-strings \ + -Wno-long-long \ + -Wno-overlength-strings \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -Wno-unused-result \ + -Werror=overflow \ + -Wdate-time \ + -Wnested-externs \ + -ffast-math \ + -fno-common \ + -fdiagnostics-show-option \ + -fno-strict-aliasing \ + -fvisibility=hidden \ + -ffunction-sections \ + -fdata-sections \ + -fstack-protector \ + -fstack-protector-strong \ + -fPIE \ + --param=ssp-buffer-size=4]) + +AS_CASE([$CC], [*clang*], + [CC_CHECK_FLAGS_APPEND([with_cppflags], [CPPFLAGS], [ \ + -Wno-typedef-redefinition \ + -Wno-gnu-variable-sized-type-not-at-end \ + ])]) + +CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [\ + -Wl,--as-needed \ + -Wl,--no-undefined \ + -Wl,--gc-sections \ + -Wl,-z,relro \ + -Wl,-z,now \ + -pie]) + +AS_IF([test "x$enable_debug" = "xyes"], + [ + CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [ \ + -g \ + -O0 \ + -ftrapv]) + ], [ + CC_CHECK_FLAGS_APPEND([with_cppflags], [CPPFLAGS], [\ + -Wp,-D_FORTIFY_SOURCE=2]) + ]) + +AC_SUBST([OUR_CFLAGS], "$with_cflags") +AC_SUBST([OUR_CPPFLAGS], "$with_cppflags") +AC_SUBST([OUR_LDFLAGS], "$with_ldflags") + +# ------------------------------------------------------------------------------ +# system features + +# This makes sure pkg.m4 is available. +m4_pattern_forbid([^_?PKG_[A-Z_]+$],[*** pkg.m4 missing, please install pkg-config]) + +# ------------------------------------------------------------------------------ +# dependencies + +PKG_CHECK_MODULES([UDEV], [libudev]) +PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd >= 225]) + +# ------------------------------------------------------------------------------ +# report + +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT +AC_MSG_RESULT([ + $PACKAGE_NAME $VERSION + debug: ${enable_debug} + + prefix: ${prefix} + exec_prefix: ${exec_prefix} + includedir: ${includedir} + libdir: ${libdir} + + CFLAGS: ${OUR_CFLAGS} ${CFLAGS} + CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS} + LDFLAGS: ${OUR_LDFLAGS} ${LDFLAGS} +]) diff --git a/src/org.freedesktop.ratbag1.conf b/src/org.freedesktop.ratbag1.conf new file mode 100644 index 00000000..cd7b8843 --- /dev/null +++ b/src/org.freedesktop.ratbag1.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/ratbagctl.c b/src/ratbagctl.c new file mode 100644 index 00000000..929c174c --- /dev/null +++ b/src/ratbagctl.c @@ -0,0 +1,213 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include +#include +#include +#include +#include +#include "shared-macro.h" + +struct ratbagctl { + int unused; +}; + +static int verb_help(struct ratbagctl *ctl, int argc, char **argv); + +static struct ratbagctl *ratbagctl_free(struct ratbagctl *ctl) +{ + if (!ctl) + return NULL; + + free(ctl); + + return NULL; +} + +static int ratbagctl_new(struct ratbagctl **out) +{ + struct ratbagctl *ctl; + + ctl = calloc(1, sizeof(*ctl)); + if (!ctl) + return -ENOMEM; + + *out = ctl; + return 0; +} + +static int verb_list(struct ratbagctl *ctl, int argc, char **argv) +{ + return 0; +} + +static const struct { + const char *verb; + const char *help; + int (*dispatch) (struct ratbagctl *ctl, int argc, char **argv); + void (*long_help) (void); +} verbs[] = { + { "list", "List available configurable mice", verb_list, NULL }, + { "help", "Show help for a command", verb_help, NULL }, + {}, +}; + +static void help(void) +{ + unsigned int i; + + printf("%s [OPTIONS..] COMMAND [OPTIONS..]\n\n" + "Query or modify configurable mice.\n\n" + "Commands:\n", + program_invocation_short_name); + + for (i = 0; i < ELEMENTSOF(verbs); ++i) { + if (verbs[i].help != NULL) + printf(" %-12s %s\n", verbs[i].verb, verbs[i].help); + } +} + +static int verb_help(struct ratbagctl *ctl, int argc, char **argv) +{ + static const struct option options[] = { + {}, + }; + const char *verb; + unsigned int i; + int c; + + while ((c = getopt_long(argc, argv, "+", options, NULL)) >= 0) { + switch (c) { + default: + return -EINVAL; + } + } + + verb = argv[optind]; + if (!verb) { + help(); + return 1; + } else { + for (i = 0; i < ELEMENTSOF(verbs); ++i) { + if (!streq_ptr(verb, verbs[i].verb)) + continue; + + if (verbs[i].long_help) + verbs[i].long_help(); + else + printf("%s: No help available for '%s'\n", + program_invocation_short_name, verb); + + return 1; + } + } + + fprintf(stderr, "%s: Unknown verb '%s'\n", program_invocation_short_name, verb); + return 1; +} + +static void version(void) +{ + printf("%s\n", VERSION); +} + +static int ratbagctl_dispatch(struct ratbagctl *ctl, int argc, char **argv) +{ + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {}, + }; + char *fake[] = { (char*)verbs[0].verb, NULL }; + const char *verb; + unsigned int i; + int c; + + /* '+' makes getopt_long() stop at the first non-argument */ + while ((c = getopt_long(argc, argv, "+hV", options, NULL)) >= 0) { + switch (c) { + case 'h': + help(); + return 1; + case 'V': + version(); + return 1; + default: + return -EINVAL; + } + } + + verb = argv[optind]; + if (!verb) { + assert(argc - optind == 0); + + argc = ELEMENTSOF(fake) - 1; + argv = fake; + verb = fake[0]; + optind = 0; + + assert(verb); + } + + for (i = 0; i < ELEMENTSOF(verbs); ++i) { + if (!streq_ptr(verb, verbs[i].verb)) + continue; + + argc -= optind; + argv += optind; + optind = 0; + + return verbs[i].dispatch(ctl, argc, argv); + } + + fprintf(stderr, "%s: Missing or unknown command\n", + program_invocation_short_name); + + return -EINVAL; +} + +int main(int argc, char **argv) +{ + struct ratbagctl *ctl = NULL; + int r; + + r = ratbagctl_new(&ctl); + if (r < 0) + goto exit; + + r = ratbagctl_dispatch(ctl, argc, argv); + +exit: + ratbagctl_free(ctl); + + if (r < 0) { + errno = -r; + fprintf(stderr, "%s: Failed: %m\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/ratbagd-device.c b/src/ratbagd-device.c new file mode 100644 index 00000000..8c54951f --- /dev/null +++ b/src/ratbagd-device.c @@ -0,0 +1,288 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" +#include "shared-rbtree.h" + +struct ratbagd_device { + struct ratbagd *ctx; + RBNode node; + char *name; +}; + +#define ratbagd_device_from_node(_ptr) \ + rbnode_of((_ptr), struct ratbagd_device, node) + +static int ratbagd_device_find(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd *ctx = userdata; + struct ratbagd_device *device; + int r; + + /* decodes the suffix into @name, returns 1 if valid */ + r = sd_bus_path_decode(path, "/org/freedesktop/ratbag1/device", &name); + if (r <= 0) + return r; + + device = ratbagd_find_device(ctx, name); + if (!device) + return 0; + + *found = device; + return 1; +} + +static int ratbagd_device_list(sd_bus *bus, + const char *path, + void *userdata, + char ***nodes, + sd_bus_error *error) +{ + struct ratbagd *ctx = userdata; + struct ratbagd_device *device; + char **devices, **pos; + RBNode *node; + int r; + + devices = calloc(ctx->n_devices + 1, sizeof(char *)); + if (!devices) + return -ENOMEM; + + pos = devices; + + for (node = rbtree_first(&ctx->device_map); + node; + node = rbnode_next(node)) { + device = ratbagd_device_from_node(node); + + r = sd_bus_path_encode("/org/freedesktop/ratbag1/device", + device->name, + pos++); + if (r < 0) + goto error; + } + + *pos = NULL; + + *nodes = devices; + return 1; + +error: + for (pos = devices; *pos; ++pos) + free(*pos); + free(devices); + return r; +} + +static const sd_bus_vtable ratbagd_device_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END, +}; + +int ratbagd_device_new(struct ratbagd_device **out, + struct ratbagd *ctx, + const char *name) +{ + _cleanup_(ratbagd_device_freep) struct ratbagd_device *device = NULL; + + assert(out); + assert(ctx); + assert(name); + + device = calloc(1, sizeof(*device)); + if (!device) + return -ENOMEM; + + device->ctx = ctx; + rbnode_init(&device->node); + + device->name = strdup(name); + if (!device->name) + return -ENOMEM; + + *out = device; + device = NULL; + return 0; +} + +struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device) +{ + if (!device) + return NULL; + + assert(!ratbagd_device_linked(device)); + + device->name = mfree(device->name); + return mfree(device); +} + +bool ratbagd_device_linked(struct ratbagd_device *device) +{ + return device && rbnode_linked(&device->node); +} + +void ratbagd_device_link(struct ratbagd_device *device) +{ + _cleanup_(freep) char *path = NULL; + struct ratbagd_device *iter; + RBNode **node, *parent; + int r, v; + + assert(device); + assert(!ratbagd_device_linked(device)); + + /* find place to link it to */ + parent = NULL; + node = &device->ctx->device_map.root; + while (*node) { + parent = *node; + iter = ratbagd_device_from_node(parent); + v = strcmp(device->name, iter->name); + + /* if there's a duplicate, the caller screwed up */ + assert(v != 0); + + if (v < 0) + node = &parent->left; + else /* if (v > 0) */ + node = &parent->right; + } + + /* link into context */ + rbtree_add(&device->ctx->device_map, parent, node, &device->node); + ++device->ctx->n_devices; + + /* send out object-manager notification */ + r = sd_bus_path_encode("/org/freedesktop/ratbag1/device", + device->name, + &path); + if (r >= 0) + r = sd_bus_emit_object_added(device->ctx->bus, path); + if (r < 0) { + errno = -r; + fprintf(stderr, + "Cannot send device notification for '%s': %m\n", + device->name); + } +} + +void ratbagd_device_unlink(struct ratbagd_device *device) +{ + _cleanup_(freep) char *path = NULL; + int r; + + if (!ratbagd_device_linked(device)) + return; + + /* send out object-manager notification */ + r = sd_bus_path_encode("/org/freedesktop/ratbag1/device", + device->name, + &path); + if (r >= 0) + r = sd_bus_emit_object_removed(device->ctx->bus, path); + if (r < 0) { + errno = -r; + fprintf(stderr, + "Cannot send device notification for '%s': %m\n", + device->name); + } + + /* unlink from context */ + --device->ctx->n_devices; + rbtree_remove(&device->ctx->device_map, &device->node); + rbnode_init(&device->node); +} + +int ratbagd_init_device(struct ratbagd *ctx) +{ + int r; + + assert(ctx); + + /* + * Called by the context initializer after the context was prepared but + * not run, yet. No need to cleanup anything if an error is returned, + * as long it is teared down automatically on destruction. + */ + + r = sd_bus_add_fallback_vtable(ctx->bus, + NULL, + "/org/freedesktop/ratbag1/device", + "org.freedesktop.ratbag1.Device", + ratbagd_device_vtable, + ratbagd_device_find, + ctx); + if (r < 0) + return r; + + r = sd_bus_add_node_enumerator(ctx->bus, + NULL, + "/org/freedesktop/ratbag1/device", + ratbagd_device_list, + ctx); + if (r < 0) + return r; + + return 0; +} + +struct ratbagd_device *ratbagd_find_device(struct ratbagd *ctx, + const char *name) +{ + struct ratbagd_device *device; + RBNode *node; + int v; + + assert(ctx); + assert(name); + + node = ctx->device_map.root; + while (node) { + device = ratbagd_device_from_node(node); + v = strcmp(name, device->name); + if (!v) + return device; + else if (v < 0) + node = node->left; + else /* if (v > 0) */ + node = node->right; + } + + return NULL; +} diff --git a/src/ratbagd.c b/src/ratbagd.c new file mode 100644 index 00000000..86c8cb88 --- /dev/null +++ b/src/ratbagd.c @@ -0,0 +1,303 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +static void ratbagd_process_device(struct ratbagd *ctx, + struct udev_device *udevice) +{ + struct ratbagd_device *device; + const char *name; + int r; + + /* + * TODO: So far we just enumerate udev input devices and add a new + * ratbag device for each one we find. That's a bit easy. We + * should really try to create a libratbag device for the udev + * device and only add devices that are actually accepted. + * Furthermore, the libratbag object should really be kept as + * context in ratbagd_device. + * + * TODO: libratbag should provide some mechanism to allow + * device-grouping, just like libinput does. If multiple input + * devices belong to the same virtual device, we should not add + * it multiple times. Instead, libratbag should group them and + * provide *us* a unique name that identifies the group, rather + * than taking a random input-device as tag. + */ + + name = udev_device_get_sysname(udevice); + if (!name) + return; + + device = ratbagd_find_device(ctx, name); + + if (streq_ptr("remove", udev_device_get_action(udevice))) { + /* device was removed, unlink it and destroy our context */ + if (device) { + ratbagd_device_unlink(device); + ratbagd_device_free(device); + } + } else if (device) { + /* device already known, refresh our view of the device */ + } else { + /* device unknown, create new one and link it */ + r = ratbagd_device_new(&device, ctx, name); + if (r < 0) { + fprintf(stderr, "Cannot track device '%s'\n", name); + return; + } + + ratbagd_device_link(device); + } +} + +static int ratbagd_monitor_event(sd_event_source *source, + int fd, + uint32_t mask, + void *userdata) +{ + struct ratbagd *ctx = userdata; + struct udev_device *udevice; + + udevice = udev_monitor_receive_device(ctx->monitor); + if (!udevice) + return 0; + + ratbagd_process_device(ctx, udevice); + udev_device_unref(udevice); + + return 0; +} + +static struct ratbagd *ratbagd_free(struct ratbagd *ctx) +{ + if (!ctx) + return NULL; + + ctx->bus = sd_bus_flush_close_unref(ctx->bus); + ctx->monitor_source = sd_event_source_unref(ctx->monitor_source); + ctx->monitor = udev_monitor_unref(ctx->monitor); + ctx->event = sd_event_unref(ctx->event); + + assert(!ctx->device_map.root); + + return mfree(ctx); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd *, ratbagd_free); + +static int ratbagd_init_monitor(struct ratbagd *ctx) +{ + struct udev *udev; + int r; + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + ctx->monitor = udev_monitor_new_from_netlink(udev, "udev"); + + /* we don't need the context to stay around; drop it */ + udev_unref(udev); + + if (!ctx->monitor) + return -ENOMEM; + + r = udev_monitor_filter_add_match_subsystem_devtype(ctx->monitor, + "input", + NULL); + if (r < 0) + return r; + + r = udev_monitor_enable_receiving(ctx->monitor); + if (r < 0) + return r; + + r = sd_event_add_io(ctx->event, + &ctx->monitor_source, + udev_monitor_get_fd(ctx->monitor), + EPOLLIN, + ratbagd_monitor_event, + ctx); + if (r < 0) + return r; + + return 0; +} + +static int ratbagd_new(struct ratbagd **out) +{ + _cleanup_(ratbagd_freep) struct ratbagd *ctx = NULL; + int r; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + return -ENOMEM; + + r = sd_event_default(&ctx->event); + if (r < 0) + return r; + + r = sd_event_set_watchdog(ctx->event, true); + if (r < 0) + return r; + + r = ratbagd_init_monitor(ctx); + if (r < 0) + return r; + + r = sd_bus_open_system(&ctx->bus); + if (r < 0) + return r; + + r = sd_bus_add_object_manager(ctx->bus, + NULL, + "/org/freedesktop/ratbag1"); + if (r < 0) + return r; + + r = ratbagd_init_device(ctx); + if (r < 0) + return r; + + r = sd_bus_request_name(ctx->bus, "org.freedesktop.ratbag1", 0); + if (r < 0) + return r; + + r = sd_bus_attach_event(ctx->bus, ctx->event, 0); + if (r < 0) + return r; + + *out = ctx; + ctx = NULL; + return 0; +} + +static int ratbagd_run_enumerate(struct ratbagd *ctx) +{ + struct udev_list_entry *list, *iter; + struct udev_enumerate *e; + struct udev *udev; + int r; + + udev = udev_monitor_get_udev(ctx->monitor); + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "input"); + if (r < 0) + goto exit; + + r = udev_enumerate_add_match_is_initialized(e); + if (r < 0) + goto exit; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto exit; + + list = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(iter, list) { + struct udev_device *udevice; + const char *p; + + p = udev_list_entry_get_name(iter); + udevice = udev_device_new_from_syspath(udev, p); + if (udevice) + ratbagd_process_device(ctx, udevice); + udev_device_unref(udevice); + } + + r = 0; + +exit: + udev_enumerate_unref(e); + return r; +} + +static int ratbagd_run(struct ratbagd *ctx) +{ + int r; + + /* + * TODO: We should support exit-on-idle and bus-activation. Note that + * we don't store any state on our own, hence, all we have to do + * is to make sure we advertise device add/remove events via the + * bus (in case there is a listener). + * To track such events, we should store devices we already + * advertised in /run/ratbagd/devices/ and read it out on + * activation. + * Note that this feature requires udev-activation, which might + * not be possible, yet. + */ + + r = ratbagd_run_enumerate(ctx); + if (r < 0) + return r; + + return sd_event_loop(ctx->event); +} + +int main(int argc, char *argv[]) +{ + struct ratbagd *ctx = NULL; + int r; + + if (argc != 1) { + fprintf(stderr, "%s: This program takes no arguments\n", + program_invocation_short_name); + r = -EINVAL; + goto exit; + } + + r = ratbagd_new(&ctx); + if (r < 0) + goto exit; + + r = ratbagd_run(ctx); + +exit: + ratbagd_free(ctx); + + if (r < 0) { + errno = -r; + fprintf(stderr, "%s: Failed: %m\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/ratbagd.h b/src/ratbagd.h new file mode 100644 index 00000000..84235f09 --- /dev/null +++ b/src/ratbagd.h @@ -0,0 +1,71 @@ +#pragma once + +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include +#include +#include +#include +#include +#include +#include +#include "shared-macro.h" +#include "shared-rbtree.h" + +struct ratbagd; +struct ratbagd_device; + +/* + * Devices + */ + +int ratbagd_device_new(struct ratbagd_device **out, + struct ratbagd *ctx, + const char *name); +struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device); +bool ratbagd_device_linked(struct ratbagd_device *device); +void ratbagd_device_link(struct ratbagd_device *device); +void ratbagd_device_unlink(struct ratbagd_device *device); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_device *, ratbagd_device_free); + +/* + * Context + */ + +struct ratbagd { + sd_event *event; + struct udev_monitor *monitor; + sd_event_source *monitor_source; + sd_bus *bus; + + RBTree device_map; + size_t n_devices; +}; + +int ratbagd_init_device(struct ratbagd *ctx); +struct ratbagd_device *ratbagd_find_device(struct ratbagd *ctx, + const char *name); diff --git a/src/shared-macro.h b/src/shared-macro.h new file mode 100644 index 00000000..c542b75f --- /dev/null +++ b/src/shared-macro.h @@ -0,0 +1,165 @@ +#pragma once + +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We require: + * sizeof(void*) == sizeof(long) + * sizeof(long) == 4 || sizeof(long) == 8 + * sizeof(int) == 4 + * The linux kernel requires the same from the toolchain, so this should work + * just fine. + */ +#if __SIZEOF_POINTER__ != __SIZEOF_LONG__ +# error "sizeof(void*) != sizeof(long)" +#elif __SIZEOF_LONG__ != 4 && __SIZEOF_LONG__ != 8 +# error "sizeof(long) != 4 && sizeof(long) != 8" +#elif __SIZEOF_INT__ != 4 +# error "sizeof(int) != 4" +#endif + +/* + * Shortcuts for gcc attributes. See GCC manual for details. + */ +#define _alignas_(_x) __attribute__((aligned(__alignof(_x)))) +#define _alloc_(...) __attribute__((alloc_size(__VA_ARGS__))) +#define _cleanup_(_x) __attribute__((cleanup(_x))) +#define _const_ __attribute__((const)) +#define _deprecated_ __attribute__((deprecated)) +#define _hidden_ __attribute__((visibility("hidden"))) +#define _likely_(_x) (__builtin_expect(!!(_x), 1)) +#define _malloc_ __attribute__((malloc)) +#define _packed_ __attribute__((packed)) +#define _printf_(_a, _b) __attribute__((format (printf, _a, _b))) +#define _public_ __attribute__((visibility("default"))) +#define _pure_ __attribute__((pure)) +#define _sentinel_ __attribute__((sentinel)) +#define _unlikely_(_x) (__builtin_expect(!!(_x), 0)) +#define _unused_ __attribute__((unused)) +#define _weak_ __attribute__((weak)) +#define _weakref_(_x) __attribute__((weakref(#_x))) + +/* + * DEFINE_TRIVIAL_CLEANUP_FUNC() - define helper suitable for _cleanup_() + * @_type: type of object to cleanup + * @_func: function to call on cleanup + */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \ + static inline void _func##p(_type *_p) \ + { \ + if (*_p) \ + _func(*_p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +/* + * ELEMENTSOF() - number of array elements + * @_array: C array + * + * This calculates the number of elements (compared to the byte size) of + * the given C array. This returns (void) if the passed argument is not + * actually a valid C array (decided at compile time). + */ +#define ELEMENTSOF(_array) \ + __extension__ (__builtin_choose_expr( \ + !__builtin_types_compatible_p(typeof(_array), \ + typeof(&*(_array))), \ + sizeof(_array) / sizeof((_array)[0]), \ + (void)0)) + +/* + * negative_errno() - return negative errno + * + * This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. + */ +static inline int negative_errno(void) +{ + return (errno <= 0) ? -EINVAL : -errno; +} + +/* + * mfree() - free memory + * @mem: memory to free + * + * This is basically the same as free(), but returns NULL. This makes free() + * follow the same style as all our other destructors. + */ +static inline void *mfree(void *mem) +{ + free(mem); + return NULL; +} + +/* + * Miscellaneous cleanup helpers + * All these helpers are suitable for use with _cleanup_(). They call the helper + * they're named after on the variable marked for cleanup. + */ +static inline void freep(void *p) +{ + free(*(void**)p); +} + +/* + * streq() - test whether two strings are equal + * @_a: string A + * @_b: string B + */ +#define streq(_a, _b) (!strcmp((_a), (_b))) + +/* + * streq_ptr() - test whether two strings are equal, considering NULL valid + * @a: string A or NULL + * @b: string B or NULL + */ +static inline bool streq_ptr(const char *a, const char *b) +{ + return (a && b) ? streq(a, b) : (!a && !b); +} + +/* + * now() - returns current time in nano-seconds + * @clock: clock to use + */ +static inline uint64_t now(clockid_t clock) +{ + struct timespec spec = {}; + clock_gettime(clock, &spec); + return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec; +} diff --git a/src/shared-rbtree.c b/src/shared-rbtree.c new file mode 100644 index 00000000..53866241 --- /dev/null +++ b/src/shared-rbtree.c @@ -0,0 +1,450 @@ +/*** + This file is part of rbtree. + + Copyright 2015 David Herrmann + + Based on the rbtree implementation of the linux-kernel: + Copyright 1999 Andrea Arcangeli + + rbtree 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) any later version. + + rbtree 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 rbtree; If not, see . +***/ + +/* + * Open Red-Black-Tree Implementation + * You're highly recommended to read a paper on rb-trees before reading this + * code. The reader is expected to be familiar with the different cases that + * might occur during insertion and removal of elements. The comments in this + * file do not contain a full prove for correctness. + */ + +#include "shared-rbtree.h" + +RBNode *rbnode_leftmost(RBNode *n) { + if (n) + while (n->left) + n = n->left; + return n; +} + +RBNode *rbnode_rightmost(RBNode *n) { + if (n) + while (n->right) + n = n->right; + return n; +} + +RBNode *rbtree_first(RBTree *t) { + return rbnode_leftmost(t->root); +} + +RBNode *rbtree_last(RBTree *t) { + return rbnode_rightmost(t->root); +} + +RBNode *rbnode_next(RBNode *n) { + RBNode *p; + + if (!rbnode_linked(n)) + return NULL; + + if (n->right) + return rbnode_leftmost(n->right); + + while ((p = rbnode_parent(n)) && n == p->right) + n = p; + + return p; +} + +RBNode *rbnode_prev(RBNode *n) { + RBNode *p; + + if (!rbnode_linked(n)) + return NULL; + + if (n->left) + return rbnode_rightmost(n->left); + + while ((p = rbnode_parent(n)) && n == p->left) + n = p; + + return p; +} + +static inline void rbnode_reparent(RBNode *n, RBNode *p, unsigned long c) { + /* change color and/or parent of a node */ + rbtree_assert(!((unsigned long)p & 1)); + rbtree_assert(c < 2); + n->__parent_and_color = (RBNode*)((unsigned long)p | c); +} + +static inline void rbtree_reparent(RBTree *t, RBNode *p, RBNode *old, RBNode *new) { + /* change previous parent/root from old to new node */ + if (p) { + if (p->left == old) + p->left = new; + else + p->right = new; + } else { + t->root = new; + } +} + +static inline RBNode *rbtree_paint_one(RBTree *t, RBNode *n) { + RBNode *p, *g, *gg, *u, *x; + + /* + * Paint a single node according to RB-Tree rules. The node must + * already be linked into the tree and painted red. + * We repaint the node or rotate the tree, if required. In case a + * recursive repaint is required, the next node to be re-painted + * is returned. + * p: parent + * g: grandparent + * gg: grandgrandparent + * u: uncle + * x: temporary + */ + + /* node is red, so we can access the parent directly */ + p = n->__parent_and_color; + + if (!p) { + /* Case 1: + * We reached the root. Mark it black and be done. As all + * leaf-paths share the root, the ratio of black nodes on each + * path stays the same. */ + rbnode_reparent(n, p, RBNODE_BLACK); + n = NULL; + } else if (rbnode_black(p)) { + /* Case 2: + * The parent is already black. As our node is red, we did not + * change the number of black nodes on any path, nor do we have + * multiple consecutive red nodes. */ + n = NULL; + } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */ + g = p->__parent_and_color; + gg = rbnode_parent(g); + u = g->right; + + if (u && rbnode_red(u)) { + /* Case 3: + * Parent and uncle are both red. We know the + * grandparent must be black then. Repaint parent and + * uncle black, the grandparent red and recurse into + * the grandparent. */ + rbnode_reparent(p, g, RBNODE_BLACK); + rbnode_reparent(u, g, RBNODE_BLACK); + rbnode_reparent(g, gg, RBNODE_RED); + n = g; + } else { + /* parent is red, uncle is black */ + + if (n == p->right) { + /* Case 4: + * We're the right child. Rotate on parent to + * become left child, so we can handle it the + * same as case 5. */ + x = n->left; + p->right = n->left; + n->left = p; + if (x) + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(p, n, RBNODE_RED); + p = n; + } + + /* 'n' is invalid from here on! */ + n = NULL; + + /* Case 5: + * We're the red left child or a red parent, black + * grandparent and uncle. Rotate on grandparent and + * switch color with parent. Number of black nodes on + * each path stays the same, but we got rid of the + * double red path. As the grandparent is still black, + * we're done. */ + x = p->right; + g->left = x; + p->right = g; + if (x) + rbnode_reparent(x, g, RBNODE_BLACK); + rbnode_reparent(p, gg, RBNODE_BLACK); + rbnode_reparent(g, p, RBNODE_RED); + rbtree_reparent(t, gg, g, p); + } + } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */ + g = p->__parent_and_color; + gg = rbnode_parent(g); + u = g->left; + + if (u && rbnode_red(u)) { + rbnode_reparent(p, g, RBNODE_BLACK); + rbnode_reparent(u, g, RBNODE_BLACK); + rbnode_reparent(g, gg, RBNODE_RED); + n = g; + } else { + if (n == p->left) { + x = n->right; + p->left = n->right; + n->right = p; + if (x) + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(p, n, RBNODE_RED); + p = n; + } + + n = NULL; + + x = p->left; + g->right = x; + p->left = g; + if (x) + rbnode_reparent(x, g, RBNODE_BLACK); + rbnode_reparent(p, gg, RBNODE_BLACK); + rbnode_reparent(g, p, RBNODE_RED); + rbtree_reparent(t, gg, g, p); + } + } + + return n; +} + +static inline void rbtree_paint(RBTree *t, RBNode *n) { + while (n) + n = rbtree_paint_one(t, n); +} + +void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n) { + n->__parent_and_color = p; + n->left = n->right = NULL; + *l = n; + + rbtree_paint(t, n); +} + +static inline RBNode *rbtree_rebalance_one(RBTree *t, RBNode *p, RBNode *n) { + RBNode *s, *x, *y, *g; + + /* + * Rebalance tree after a node was removed. This happens only if you + * remove a black node and one path is now left with an unbalanced + * number or black nodes. + * This function assumes all paths through p and n have one black node + * less than all other paths. If recursive fixup is required, the + * current node is returned. + */ + + if (n == p->left) { + s = p->right; + if (rbnode_red(s)) { + /* Case 3: + * We have a red node as sibling. Rotate it onto our + * side so we can later on turn it black. This way, we + * gain the additional black node in our path. */ + g = rbnode_parent(p); + x = s->left; + p->right = x; + s->left = p; + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_RED); + rbtree_reparent(t, g, p, s); + s = x; + } + + x = s->right; + if (!x || rbnode_black(x)) { + y = s->left; + if (!y || rbnode_black(y)) { + /* Case 4: + * Our sibling is black and has only black + * children. Flip it red and turn parent black. + * This way we gained a black node in our path, + * or we fix it recursively one layer up, which + * will rotate the red sibling as parent. */ + rbnode_reparent(s, p, RBNODE_RED); + if (rbnode_black(p)) + return p; + + rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); + return NULL; + } + + /* Case 5: + * Left child of our sibling is red, right one is black. + * Rotate on parent so the right child of our sibling is + * now red, and we can fall through to case 6. */ + x = y->right; + s->left = y->right; + y->right = s; + p->right = y; + if (x) + rbnode_reparent(x, s, RBNODE_BLACK); + x = s; + s = y; + } + + /* Case 6: + * The right child of our sibling is red. Rotate left and flip + * colors, which gains us an additional black node in our path, + * that was previously on our sibling. */ + g = rbnode_parent(p); + y = s->left; + p->right = y; + s->left = p; + rbnode_reparent(x, s, RBNODE_BLACK); + if (y) + rbnode_reparent(y, p, rbnode_color(y)); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_BLACK); + rbtree_reparent(t, g, p, s); + } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */ + s = p->left; + if (rbnode_red(s)) { + g = rbnode_parent(p); + x = s->right; + p->left = x; + s->right = p; + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(s, g, RBNODE_BLACK); + rbnode_reparent(p, s, RBNODE_RED); + rbtree_reparent(t, g, p, s); + s = x; + } + + x = s->left; + if (!x || rbnode_black(x)) { + y = s->right; + if (!y || rbnode_black(y)) { + rbnode_reparent(s, p, RBNODE_RED); + if (rbnode_black(p)) + return p; + + rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); + return NULL; + } + + x = y->left; + s->right = y->left; + y->left = s; + p->left = y; + if (x) + rbnode_reparent(x, s, RBNODE_BLACK); + x = s; + s = y; + } + + g = rbnode_parent(p); + y = s->right; + p->left = y; + s->right = p; + rbnode_reparent(x, s, RBNODE_BLACK); + if (y) + rbnode_reparent(y, p, rbnode_color(y)); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_BLACK); + rbtree_reparent(t, g, p, s); + } + + return NULL; +} + +static inline void rbtree_rebalance(RBTree *t, RBNode *p) { + RBNode *n = NULL; + + while (p) { + n = rbtree_rebalance_one(t, p, n); + p = n ? rbnode_parent(n) : NULL; + } +} + +void rbtree_remove(RBTree *t, RBNode *n) { + RBNode *p, *s, *gc, *x, *next = NULL; + unsigned long c; + + /* + * To remove an interior node from a binary tree, we simply find its + * successor, swap both nodes and then remove the node. Therefore, the + * only interesting case is were the node to be removed has at most one + * child. + * p: parent + * s: successor + * gc: grand-...-child + * x: temporary + * next: next node to rebalance on + */ + + if (!n->left) { + /* Case 1: + * We have at most one child, which must be red. We're + * guaranteed to be black then. Replace our node with the child + * (in case it exists), but turn it black. + * If no child exists, we need to rebalance, in case we were + * black as our path now lost a black node. */ + p = rbnode_parent(n); + c = rbnode_color(n); + rbtree_reparent(t, p, n, n->right); + if (n->right) + rbnode_reparent(n->right, p, c); + else + next = (c == RBNODE_BLACK) ? p : NULL; + } else if (!n->right) { + /* case 1 mirrored (but n->left guaranteed non-NULL) */ + p = rbnode_parent(n); + c = rbnode_color(n); + rbnode_reparent(n->left, p, c); + rbtree_reparent(t, p, n, n->left); + } else { + /* Case 2: + * We're an interior node. Find our successor and swap it with + * our node. Then remove our node. For performance reasons we + * don't perform the full swap, but skip links that are about to + * be removed, anyway. */ + s = n->right; + if (!s->left) { + /* right child is next, no need to touch grandchild */ + p = s; + gc = s->right; + } else { + /* find successor and swap partially */ + s = rbnode_leftmost(s); + p = rbnode_parent(s); + + gc = s->right; + p->left = s->right; + s->right = n->right; + rbnode_reparent(n->right, s, rbnode_color(n->right)); + } + + /* node is partially swapped, now remove as in case 1 */ + + s->left = n->left; + rbnode_reparent(n->left, s, rbnode_color(n->left)); + + x = rbnode_parent(n); + c = rbnode_color(n); + rbtree_reparent(t, x, n, s); + if (gc) { + rbnode_reparent(s, x, c); + rbnode_reparent(gc, p, RBNODE_BLACK); + } else { + next = rbnode_black(s) ? p : NULL; + rbnode_reparent(s, x, c); + } + } + + if (next) + rbtree_rebalance(t, next); +} diff --git a/src/shared-rbtree.h b/src/shared-rbtree.h new file mode 100644 index 00000000..ffb0296d --- /dev/null +++ b/src/shared-rbtree.h @@ -0,0 +1,99 @@ +#pragma once + +/*** + This file is part of rbtree. + + Copyright 2015 David Herrmann + + rbtree 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) any later version. + + rbtree 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 rbtree; If not, see . +***/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef RBTREE_DEBUG +# include +# define rbtree_assert(_x) assert(_x) +#else +# define rbtree_assert(_x) +#endif + +typedef struct RBTree RBTree; +typedef struct RBNode RBNode; + +enum { + RBNODE_RED = 0, + RBNODE_BLACK = 1, +}; + +struct RBTree { + RBNode *root; +}; + +struct RBNode { + RBNode *__parent_and_color; + RBNode *left; + RBNode *right; +}; + +#define rbnode_of(_ptr, _type, _member) \ + __extension__ ({ \ + const typeof( ((_type*)0)->_member ) *__ptr = (_ptr); \ + __ptr ? (_type*)( (char*)__ptr - offsetof(_type, _member) ) : NULL; \ + }) + +#define RBNODE_INIT(var) ((RBNode){ .__parent_and_color = (&var) }) + +static inline RBNode *rbnode_init(RBNode *n) { + *n = RBNODE_INIT(*n); + return n; +} + +static inline RBNode *rbnode_parent(RBNode *n) { + return (RBNode*)((unsigned long)n->__parent_and_color & ~1UL); +} + +static inline int rbnode_linked(RBNode *n) { + return n && n->__parent_and_color != n; +} + +static inline unsigned long rbnode_color(RBNode *n) { + return (unsigned long)n->__parent_and_color & 1UL; +} + +static inline int rbnode_red(RBNode *n) { + return rbnode_color(n) == RBNODE_RED; +} + +static inline int rbnode_black(RBNode *n) { + return rbnode_color(n) == RBNODE_BLACK; +} + +RBNode *rbnode_leftmost(RBNode *n); +RBNode *rbnode_rightmost(RBNode *n); + +RBNode *rbtree_first(RBTree *t); +RBNode *rbtree_last(RBTree *t); +RBNode *rbnode_next(RBNode *n); +RBNode *rbnode_prev(RBNode *n); + +void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n); +void rbtree_remove(RBTree *t, RBNode *n); + +#ifdef __cplusplus +} +#endif