From 695119a0a324de98d340ca956e92c60fba949048 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 10 Jul 2012 08:48:34 +0200 Subject: [PATCH 1/2] Add libudev test bed wrapper The removal of $SYSFS_PATH and configurable paths made it impossible for test suites to set up a local sysfs tree and point libudev to that. Restore and generalize the functionality with checking the $UDEV_TEST_PREFIX environment variable, so that test suites can continue to run without root privileges and the danger of breaking the system. Add a small LD_PRELOAD library (based on path.c from kmod) to wrap the libc functions that libudev needs for prefixing the paths with $UDEV_TEST_PREFIX. Cover sysfs for now (which mostly needs open(), readlink(), and stat()), but this approach is extensible for intercepting sysctls, device node access, and similar things in the future as well. Also add a small shell wrapper "libudev-testbed" which runs the argument under the appropriate LD_PRELOAD. This is more robust for third party test suites and also allows us to change the test bed implementation in the future. Run all tests under this wrapper; it does not change behaviour if $UDEV_TEST_PREFIX is not set, and will make future selftests of libudev/gudev using the new test bed just work. --- Makefile.am | 27 ++++++ src/test/libudev-testbed | 5 ++ src/test/libudev-testbed.c | 214 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100755 src/test/libudev-testbed create mode 100644 src/test/libudev-testbed.c diff --git a/Makefile.am b/Makefile.am index 70b8b09..e4b8166 100644 --- a/Makefile.am +++ b/Makefile.am @@ -104,6 +104,7 @@ check_PROGRAMS = check_DATA = noinst_PROGRAMS = TESTS = +TESTS_ENVIRONMENT = LD_LIBRARY_PATH=.libs:$$LD_LIBRARY_PATH src/test/libudev-testbed udevlibexec_PROGRAMS = AM_CPPFLAGS = \ @@ -1508,6 +1509,32 @@ libudev_private_la_LIBADD = \ libsystemd-shared.la # ------------------------------------------------------------------------------ + +lib_LTLIBRARIES += \ + libudev-testbed.la + +libudev_testbed_la_SOURCES = \ + src/test/libudev-testbed.c + +libudev_testbed_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=default + +libudev_testbed_la_LDFLAGS = \ + $(AM_LDFLAGS) + +# this is an LD_PRELOAD library, so remove static library and libtool wrappers +libudev-testbed-install-hook: + rm $(DESTDIR)$(libdir)/libudev-testbed.a + rm $(DESTDIR)$(libdir)/libudev-testbed.la + +INSTALL_EXEC_HOOKS += \ + libudev-testbed-install-hook + +bin_SCRIPTS = \ + src/test/libudev-testbed + +# ------------------------------------------------------------------------------ MANPAGES += \ man/udev.7 \ man/udevadm.8 \ diff --git a/src/test/libudev-testbed b/src/test/libudev-testbed new file mode 100755 index 0000000..44b8258 --- /dev/null +++ b/src/test/libudev-testbed @@ -0,0 +1,5 @@ +#!/bin/sh +# Wrapper program to preload the libudev-testbed shared library, so that test +# programs can set $UDEV_TEST_PREFIX for redirecting sysfs and other queries to +# a test bed. +env LD_PRELOAD=libudev-testbed.so.0:$LD_PRELOAD "$@" diff --git a/src/test/libudev-testbed.c b/src/test/libudev-testbed.c new file mode 100644 index 0000000..0ecd211 --- /dev/null +++ b/src/test/libudev-testbed.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2012 ProFUSION embedded systems + * Portions Copyright (C) 2012 Canonical Ltd. + * Authors: + * Lucas De Marchi + * Martin Pitt + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void *nextlib; + +static inline int need_trap(const char *path) +{ + return path != NULL && strncmp(path, "/sys/", 5) == 0; +} + +static const char *trap_path(const char *path) +{ + static char buf[PATH_MAX * 2]; + const char *prefix; + size_t path_len, prefix_len; + + if (!need_trap(path)) + return path; + + prefix = getenv("UDEV_TEST_PREFIX"); + if (prefix == NULL) + return path; + + path_len = strlen(path); + prefix_len = strlen(prefix); + + if (path_len + prefix_len >= sizeof(buf)) { + errno = ENAMETOOLONG; + return NULL; + } + + strcpy(buf, prefix); + strcpy(buf + prefix_len, path); + return buf; +} + +static void *get_libc_func(const char *f) +{ + void *fp; + + if (nextlib == NULL) { +#ifdef RTLD_NEXT + nextlib = RTLD_NEXT; +#else + nextlib = dlopen("libc.so.6", RTLD_LAZY); +#endif + } + + fp = dlsym(nextlib, f); + assert(fp); + + return fp; +} + +FILE *fopen(const char *path, const char *mode) +{ + const char *p; + static FILE* (*_fopen)(const char *path, const char *mode); + + _fopen = get_libc_func("fopen"); + + p = trap_path(path); + if (p == NULL) + return NULL; + + return (*_fopen)(p, mode); +} + +int open(const char *path, int flags, ...) +{ + const char *p; + static int (*_open)(const char *path, int flags, ...); + + _open = get_libc_func("open"); + p = trap_path(path); + if (p == NULL) + return -1; + + if (flags & O_CREAT) { + mode_t mode; + va_list ap; + + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + return _open(p, flags, mode); + } + + return _open(p, flags); +} + +int mkdir(const char *path, mode_t mode) +{ + const char *p; + static int (*_mkdir)(const char *path, mode_t mode); + + _mkdir = get_libc_func("mkdir"); + p = trap_path(path); + if (p == NULL) + return -1; + + return _mkdir(p, mode); +} + +/* stat() comes in umpteen different flavours, so define them with templates */ +#define WRAP_STAT(prefix, suffix) \ +int prefix ## stat ## suffix (const char *path, struct stat ## suffix *st) \ +{ \ + const char *p; \ + static int (*_fn)(const char *path, struct stat ## suffix *buf);\ + _fn = get_libc_func(#prefix "stat" #suffix); \ + p = trap_path(path); \ + /* printf("testbed wrapped " #prefix "stat" #suffix "(%s) -> %s\n", path, p);*/ \ + if (p == NULL) \ + return -1; \ + return _fn(p, st); \ +} + +WRAP_STAT(,); +WRAP_STAT(,64); +WRAP_STAT(l,); +WRAP_STAT(l,64); + +#define WRAP_VERSTAT(prefix, suffix) \ +int prefix ## stat ## suffix (int ver, const char *path, struct stat ## suffix *st) \ +{ \ + const char *p; \ + static int (*_fn)(int ver, const char *path, struct stat ## suffix *buf); \ + _fn = get_libc_func(#prefix "stat" #suffix); \ + p = trap_path(path); \ + /* printf("testbed wrapped " #prefix "stat" #suffix "(%s) -> %s\n", path, p);*/ \ + if (p == NULL) \ + return -1; \ + return _fn(ver, p, st); \ +} + +WRAP_VERSTAT(__x,); +WRAP_VERSTAT(__x,64); +WRAP_VERSTAT(__lx,); +WRAP_VERSTAT(__lx,64); + +int access(const char *path, int mode) +{ + const char *p; + static int (*_access)(const char *path, int mode); + + _access = get_libc_func("access"); + + p = trap_path(path); + if (p == NULL) + return -1; + + return _access(p, mode); +} + +DIR *opendir(const char *path) +{ + const char *p; + static DIR* (*_opendir)(const char *path); + + _opendir = get_libc_func("opendir"); + + p = trap_path(path); + if (p == NULL) + return NULL; + + return (*_opendir)(p); +} + +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + const char *p; + static int (*_readlink)(const char *path, char *buf, size_t bufsiz); + + _readlink = get_libc_func("readlink"); + + p = trap_path(path); + if (p == NULL) + return -1; + + return _readlink(p, buf, bufsiz); +} -- 1.7.10.4