/*
* Copyright (C) 2013 Red Hat, Inc.
*
* This library 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.
*
* This library 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 this library. If not, see
* .
*
* Author: Michal Privoznik
*/
#include
#ifdef __linux__
# include "internal.h"
# include
# include
# include
# include
# include
# include
# include
# include "viralloc.h"
# include "virstring.h"
# include "virfile.h"
static int (*realaccess)(const char *path, int mode);
static int (*reallstat)(const char *path, struct stat *sb);
static int (*real__lxstat)(int ver, const char *path, struct stat *sb);
static int (*realopen)(const char *path, int flags, ...);
/* Don't make static, since it causes problems with clang
* when passed as an arg to virAsprintf()
* vircgroupmock.c:462:22: error: static variable 'fakesysfsdir' is used in an inline function with external linkage [-Werror,-Wstatic-in-inline]
*/
char *fakesysfsdir;
# define PCI_SYSFS_PREFIX "/sys/bus/pci/"
# define STDERR(...) \
fprintf(stderr, "%s %zu: ", __FUNCTION__, (size_t) __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
# define ABORT(...) \
do { \
STDERR(__VA_ARGS__); \
abort(); \
} while (0)
# define ABORT_OOM() \
ABORT("Out of memory")
/*
* The plan:
*
* Mock some file handling functions. Redirect them into a stub tree passed via
* LIBVIRT_FAKE_SYSFS_DIR env variable. All files and links within stub tree is
* created by us.
*/
/*
*
* Functions to model kernel behavior
*
*/
struct pciDevice {
char *id;
int vendor;
int device;
};
struct pciDevice **pciDevices = NULL;
size_t nPciDevices = 0;
static void init_env(void);
static void pci_device_new_from_stub(const struct pciDevice *data);
/*
* Helper functions
*/
static void
make_file(const char *path,
const char *name,
const char *value)
{
int fd = -1;
char *filepath = NULL;
if (virAsprintfQuiet(&filepath, "%s/%s", path, name) < 0)
ABORT_OOM();
if ((fd = realopen(filepath, O_CREAT|O_WRONLY, 0666)) < 0)
ABORT("Unable to open: %s", filepath);
if (value && safewrite(fd, value, strlen(value)) != strlen(value))
ABORT("Unable to write: %s", filepath);
VIR_FORCE_CLOSE(fd);
VIR_FREE(filepath);
}
static int
getrealpath(char **newpath,
const char *path)
{
if (!fakesysfsdir)
init_env();
if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
if (virAsprintfQuiet(newpath, "%s/%s",
fakesysfsdir,
path + strlen(PCI_SYSFS_PREFIX)) < 0) {
errno = ENOMEM;
return -1;
}
} else {
if (VIR_STRDUP_QUIET(*newpath, path) < 0)
return -1;
}
return 0;
}
/*
* PCI Device functions
*/
static void
pci_device_new_from_stub(const struct pciDevice *data)
{
struct pciDevice *dev;
char *devpath;
char tmp[32];
if (VIR_ALLOC_QUIET(dev) < 0 ||
virAsprintfQuiet(&devpath, "%s/devices/%s", fakesysfsdir, data->id) < 0)
ABORT_OOM();
memcpy(dev, data, sizeof(*dev));
if (virFileMakePath(devpath) < 0)
ABORT("Unable to create: %s", devpath);
make_file(devpath, "config", "some dummy config");
if (snprintf(tmp, sizeof(tmp), "0x%.4x", dev->vendor) < 0)
ABORT("@tmp overflow");
make_file(devpath, "vendor", tmp);
if (snprintf(tmp, sizeof(tmp), "0x%.4x", dev->device) < 0)
ABORT("@tmp overflow");
make_file(devpath, "device", tmp);
if (VIR_APPEND_ELEMENT_QUIET(pciDevices, nPciDevices, dev) < 0)
ABORT_OOM();
VIR_FREE(devpath);
}
/*
* Functions to load the symbols and init the environment
*/
static void
init_syms(void)
{
if (realaccess)
return;
# define LOAD_SYM(name) \
do { \
if (!(real ## name = dlsym(RTLD_NEXT, #name))) \
ABORT("Cannot find real '%s' symbol\n", #name); \
} while (0)
# define LOAD_SYM_ALT(name1, name2) \
do { \
if (!(real ## name1 = dlsym(RTLD_NEXT, #name1)) && \
!(real ## name2 = dlsym(RTLD_NEXT, #name2))) \
ABORT("Cannot find real '%s' or '%s' symbol\n", \
#name1, #name2); \
} while (0)
LOAD_SYM(access);
LOAD_SYM_ALT(lstat, __lxstat);
LOAD_SYM(open);
}
static void
init_env(void)
{
if (fakesysfsdir)
return;
if (!(fakesysfsdir = getenv("LIBVIRT_FAKE_SYSFS_DIR")))
ABORT("Missing LIBVIRT_FAKE_SYSFS_DIR env variable\n");
# define MAKE_PCI_DEVICE(Id, Vendor, Device, ...) \
do { \
struct pciDevice dev = {.id = (char *)Id, .vendor = Vendor, \
.device = Device, __VA_ARGS__}; \
pci_device_new_from_stub(&dev); \
} while (0)
MAKE_PCI_DEVICE("0000:00:00.0", 0x8086, 0x0044);
}
/*
*
* Mocked functions
*
*/
int
access(const char *path, int mode)
{
int ret;
init_syms();
if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
char *newpath;
if (getrealpath(&newpath, path) < 0)
return -1;
ret = realaccess(newpath, mode);
VIR_FREE(newpath);
} else {
ret = realaccess(path, mode);
}
return ret;
}
int
__lxstat(int ver, const char *path, struct stat *sb)
{
int ret;
init_syms();
if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
char *newpath;
if (getrealpath(&newpath, path) < 0)
return -1;
ret = real__lxstat(ver, newpath, sb);
VIR_FREE(newpath);
} else {
ret = real__lxstat(ver, path, sb);
}
return ret;
}
int
lstat(const char *path, struct stat *sb)
{
int ret;
init_syms();
if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
char *newpath;
if (getrealpath(&newpath, path) < 0)
return -1;
ret = reallstat(newpath, sb);
VIR_FREE(newpath);
} else {
ret = reallstat(path, sb);
}
return ret;
}
int
open(const char *path, int flags, ...)
{
int ret;
char *newpath = NULL;
init_syms();
if (STRPREFIX(path, PCI_SYSFS_PREFIX) &&
getrealpath(&newpath, path) < 0)
return -1;
if (flags & O_CREAT) {
va_list ap;
mode_t mode;
va_start(ap, flags);
mode = va_arg(ap, mode_t);
va_end(ap);
ret = realopen(newpath ? newpath : path, flags, mode);
} else {
ret = realopen(newpath ? newpath : path, flags);
}
VIR_FREE(newpath);
return ret;
}
#else
/* Nothing to override on non-__linux__ platforms */
#endif