mirror of
https://github.com/libratbag/libratbag.git
synced 2026-11-07 23:23:52 +01:00
Import initial components
This adds two new components: 1) ratbagd The ratbagd daemon is a very basic dbus daemon that provides an org.freedesktop.DBus.ObjectManager based enumeration and modification API for libratbag devices. So far, all it does is enumerate input devices on the bus. However, this will be extended to advertise full libratbag hierarchies on the bus and allow clients to modify properties via DBus. So far, it is root-only, but the DBus policy can be losened and PolKit support can be added. 2) ratbagctl A very basic command-line tool to manage ratbagd. It's basically a command-line interface for the DBus API of ratbagd. So far, it has no hookup for anything. This will be added once ratbagd gains further DBus APIs. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -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-*
|
||||
93
Makefile.am
Normal file
93
Makefile.am
Normal file
@@ -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)
|
||||
48
autogen.sh
Executable file
48
autogen.sh
Executable file
@@ -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
|
||||
151
configure.ac
Normal file
151
configure.ac
Normal file
@@ -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}
|
||||
])
|
||||
14
src/org.freedesktop.ratbag1.conf
Normal file
14
src/org.freedesktop.ratbag1.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
|
||||
<busconfig>
|
||||
|
||||
<policy user="root">
|
||||
<allow own="org.freedesktop.ratbag1"/>
|
||||
<allow send_destination="org.freedesktop.ratbag1"/>
|
||||
<allow receive_sender="org.freedesktop.ratbag1"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
213
src/ratbagctl.c
Normal file
213
src/ratbagctl.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/***
|
||||
This file is part of ratbagd.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
288
src/ratbagd-device.c
Normal file
288
src/ratbagd-device.c
Normal file
@@ -0,0 +1,288 @@
|
||||
/***
|
||||
This file is part of ratbagd.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <systemd/sd-event.h>
|
||||
#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;
|
||||
}
|
||||
303
src/ratbagd.c
Normal file
303
src/ratbagd.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/***
|
||||
This file is part of ratbagd.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <libudev.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <systemd/sd-event.h>
|
||||
#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;
|
||||
}
|
||||
71
src/ratbagd.h
Normal file
71
src/ratbagd.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
/***
|
||||
This file is part of ratbagd.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <systemd/sd-event.h>
|
||||
#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);
|
||||
165
src/shared-macro.h
Normal file
165
src/shared-macro.h
Normal file
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
|
||||
/***
|
||||
This file is part of ratbagd.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
450
src/shared-rbtree.c
Normal file
450
src/shared-rbtree.c
Normal file
@@ -0,0 +1,450 @@
|
||||
/***
|
||||
This file is part of rbtree.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
Based on the rbtree implementation of the linux-kernel:
|
||||
Copyright 1999 Andrea Arcangeli <andrea@suse.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
99
src/shared-rbtree.h
Normal file
99
src/shared-rbtree.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
/***
|
||||
This file is part of rbtree.
|
||||
|
||||
Copyright 2015 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef RBTREE_DEBUG
|
||||
# include <assert.h>
|
||||
# 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
|
||||
Reference in New Issue
Block a user