[libvirt] [PATCH 1/4] tests: fix mocking of stat() / lstat() functions

Daniel P. Berrangé berrange at redhat.com
Tue Apr 2 12:40:33 UTC 2019


Quite a few of the tests have a need to mock the stat() / lstat()
functions and they are taking somewhat different & inconsistent
approaches none of which are actually fully correct. This is shown
by fact that 'make check' fails on 32-bit hosts. Investigation
revealed that the code was calling into the native C library impl,
not getting intercepted by our mocks.

The POSIX stat() function might resolve to any number of different
symbols in the C library.

The may be an additional stat64() function exposed by the headers
too.

On 64-bit hosts the stat & stat64 functions are identical, always
refering to the 64-bit ABI.

On 32-bit hosts they refer to the 32-bit & 64-bit ABIs respectively.

Libvirt uses _FILE_OFFSET_BITS=64 on 32-bit hosts, which causes the
C library to transparently rewrite stat() calls to be stat64() calls.
Libvirt will never see the 32-bit ABI from the traditional stat()
call. We cannot assume this rewriting is done using a macro. It might
be, but on GLibC it is done with a magic __asm__ statement to apply
the rewrite at link time instead of at preprocessing.

In GLibC there may be two additional functions exposed by the headers,
__xstat() and __xstat64(). When these exist, stat() and stat64() are
transparently rewritten to call __xstat() and __xstat64() respectively.
The former symbols will not actally exist in the library at all, only
the header. The leading "__" indicates the symbols are a private impl
detail of the C library that applications should not care about.
Unfortunately, because we are trying to mock replace the C library,
we need to know about this internal impl detail.

With all this in mind the list of functions we have to mock will depend
on several factors

 - If _FILE_OFFSET_BITS is set, then we are on a 32-bit host, and we
   only need to mock stat64 and __xstat64. The other stat / __xstat
   functions exist, but we'll never call them so they can be ignored
   for mocking.

 - If _FILE_OFFSET_BITS is not set, then we are on a 64-bit host and
   we should mock stat, stat64, __xstat & __xstat64. Either may be
   called by app code.

 - If __xstat & __xstat64 exist, then stat & stat64 will not exist
   as symbols in the library, so the latter should not be mocked.

The same all applies to lstat()

These rules are complex enough that we don't want to duplicate them
across every mock file, so this centralizes all the logic in a helper
file virmockstathelper.c that should be #included when needed. The
code merely need to provide a filename rewriting callback called
virMockStatRedirect(). Optionally VIR_MOCK_STAT_HOOK can be defined
as a macro if further processing is needed inline.

Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
---
 build-aux/mock-noinline.pl |   3 +
 cfg.mk                     |   4 +-
 tests/Makefile.am          |   1 +
 tests/qemusecuritymock.c   | 131 ++++-----------
 tests/vircgroupmock.c      | 146 +++--------------
 tests/virfilewrapper.c     |  85 ++--------
 tests/virmock.h            |  11 --
 tests/virmockstathelpers.c | 326 +++++++++++++++++++++++++++++++++++++
 tests/virpcimock.c         |  93 +----------
 tests/virtestmock.c        | 140 +---------------
 10 files changed, 413 insertions(+), 527 deletions(-)
 create mode 100644 tests/virmockstathelpers.c

diff --git a/build-aux/mock-noinline.pl b/build-aux/mock-noinline.pl
index c6b40001c5..958e133885 100644
--- a/build-aux/mock-noinline.pl
+++ b/build-aux/mock-noinline.pl
@@ -6,6 +6,9 @@ my %mocked;
 # Functions in public header don't get the noinline annotation
 # so whitelist them here
 $noninlined{"virEventAddTimeout"} = 1;
+# This one confuses the script as its defined in the mock file
+# but is actually just a local helper
+$noninlined{"virMockStatRedirect"} = 1;
 
 foreach my $arg (@ARGV) {
     if ($arg =~ /\.h$/) {
diff --git a/cfg.mk b/cfg.mk
index a62d4db13b..2c473121a4 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -1272,10 +1272,10 @@ exclude_file_name_regexp--sc_prohibit_xmlURI = ^src/util/viruri\.c$$
 exclude_file_name_regexp--sc_prohibit_return_as_function = \.py$$
 
 exclude_file_name_regexp--sc_require_config_h = \
-	^(examples/|tools/virsh-edit\.c$$)
+	^(examples/|tools/virsh-edit\.c$$|tests/virmockstathelpers.c)
 
 exclude_file_name_regexp--sc_require_config_h_first = \
-	^(examples/|tools/virsh-edit\.c$$)
+	^(examples/|tools/virsh-edit\.c$$|tests/virmockstathelpers.c)
 
 exclude_file_name_regexp--sc_trailing_blank = \
   /sysinfodata/.*\.data|/virhostcpudata/.*\.cpuinfo|^gnulib/local/.*/.*diff$$
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d3cdbff8bb..436aac285f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -146,6 +146,7 @@ EXTRA_DIST = \
 	virjsondata \
 	virmacmaptestdata \
 	virmock.h \
+	virmockstathelpers.h \
 	virnetdaemondata \
 	virnetdevtestdata \
 	virnwfilterbindingxml2xmldata \
diff --git a/tests/qemusecuritymock.c b/tests/qemusecuritymock.c
index d1b17d8aa4..3fdc165fb1 100644
--- a/tests/qemusecuritymock.c
+++ b/tests/qemusecuritymock.c
@@ -50,10 +50,6 @@
 
 
 static int (*real_chown)(const char *path, uid_t uid, gid_t gid);
-static int (*real_lstat)(const char *path, struct stat *sb);
-static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
-static int (*real_stat)(const char *path, struct stat *sb);
-static int (*real___xstat)(int ver, const char *path, struct stat *sb);
 static int (*real_open)(const char *path, int flags, ...);
 static int (*real_close)(int fd);
 
@@ -106,8 +102,6 @@ init_syms(void)
         return;
 
     VIR_MOCK_REAL_INIT(chown);
-    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
-    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
     VIR_MOCK_REAL_INIT(open);
     VIR_MOCK_REAL_INIT(close);
 
@@ -211,36 +205,35 @@ int virFileRemoveXAttr(const char *path,
 }
 
 
-static int
-mock_stat(const char *path,
-          struct stat *sb)
-{
-    uint32_t *val;
-
-    virMutexLock(&m);
-    init_hash();
-
-    memset(sb, 0, sizeof(*sb));
-
-    sb->st_mode = S_IFREG | 0666;
-    sb->st_size = 123456;
-    sb->st_ino = 1;
-
-    if (!(val = virHashLookup(chown_paths, path))) {
-        /* New path. Set the defaults */
-        sb->st_uid = DEFAULT_UID;
-        sb->st_gid = DEFAULT_GID;
-    } else {
-        /* Known path. Set values passed to chown() earlier */
-        sb->st_uid = *val % 16;
-        sb->st_gid = *val >> 16;
-    }
-
-    virMutexUnlock(&m);
-
-    return 0;
-}
-
+#define VIR_MOCK_STAT_HOOK \
+    do { \
+        if (getenv(ENVVAR)) { \
+            uint32_t *val; \
+\
+            virMutexLock(&m); \
+            init_hash(); \
+\
+            memset(sb, 0, sizeof(*sb)); \
+\
+            sb->st_mode = S_IFREG | 0666; \
+            sb->st_size = 123456; \
+            sb->st_ino = 1; \
+\
+            if (!(val = virHashLookup(chown_paths, path))) { \
+                /* New path. Set the defaults */ \
+                sb->st_uid = DEFAULT_UID; \
+                sb->st_gid = DEFAULT_GID; \
+            } else { \
+                /* Known path. Set values passed to chown() earlier */ \
+                sb->st_uid = *val % 16; \
+                sb->st_gid = *val >> 16; \
+            } \
+\
+            virMutexUnlock(&m); \
+\
+            return 0; \
+        } \
+    } while (0)
 
 static int
 mock_chown(const char *path,
@@ -276,68 +269,12 @@ mock_chown(const char *path,
 }
 
 
-#ifdef HAVE___LXSTAT
-int
-__lxstat(int ver, const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (getenv(ENVVAR))
-        ret = mock_stat(path, sb);
-    else
-        ret = real___lxstat(ver, path, sb);
-
-    return ret;
-}
-#endif /* HAVE___LXSTAT */
-
-int
-lstat(const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (getenv(ENVVAR))
-        ret = mock_stat(path, sb);
-    else
-        ret = real_lstat(path, sb);
-
-    return ret;
-}
-
-#ifdef HAVE___XSTAT
-int
-__xstat(int ver, const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (getenv(ENVVAR))
-        ret = mock_stat(path, sb);
-    else
-        ret = real___xstat(ver, path, sb);
-
-    return ret;
-}
-#endif /* HAVE___XSTAT */
+#include "virmockstathelpers.c"
 
-int
-stat(const char *path, struct stat *sb)
+static int
+virMockStatRedirect(const char *path ATTRIBUTE_UNUSED, char **newpath ATTRIBUTE_UNUSED)
 {
-    int ret;
-
-    init_syms();
-
-    if (getenv(ENVVAR))
-        ret = mock_stat(path, sb);
-    else
-        ret = real_stat(path, sb);
-
-    return ret;
+    return 0;
 }
 
 
@@ -386,6 +323,8 @@ close(int fd)
 {
     int ret;
 
+    init_syms();
+
     if (fd == 42 && getenv(ENVVAR))
         ret = 0;
     else
diff --git a/tests/vircgroupmock.c b/tests/vircgroupmock.c
index 9c67a44b0d..11a24035aa 100644
--- a/tests/vircgroupmock.c
+++ b/tests/vircgroupmock.c
@@ -38,10 +38,6 @@
 static int (*real_open)(const char *path, int flags, ...);
 static FILE *(*real_fopen)(const char *path, const char *mode);
 static int (*real_access)(const char *path, int mode);
-static int (*real_stat)(const char *path, struct stat *sb);
-static int (*real___xstat)(int ver, const char *path, struct stat *sb);
-static int (*real_lstat)(const char *path, struct stat *sb);
-static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
 static int (*real_mkdir)(const char *path, mode_t mode);
 
 /* Don't make static, since it causes problems with clang
@@ -317,8 +313,6 @@ static void init_syms(void)
 
     VIR_MOCK_REAL_INIT(fopen);
     VIR_MOCK_REAL_INIT(access);
-    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
-    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
     VIR_MOCK_REAL_INIT(mkdir);
     VIR_MOCK_REAL_INIT(open);
 }
@@ -508,139 +502,41 @@ int access(const char *path, int mode)
     return ret;
 }
 
-int __lxstat(int ver, const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
-        init_sysfs();
-        char *newpath;
-        if (asprintf(&newpath, "%s%s",
-                     fakesysfscgroupdir,
-                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
-            errno = ENOMEM;
-            return -1;
-        }
-        ret = real___lxstat(ver, newpath, sb);
-        free(newpath);
-    } else if (STRPREFIX(path, fakedevicedir0)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(8, 0);
-        return 0;
-    } else if (STRPREFIX(path, fakedevicedir1)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(9, 0);
-        return 0;
-    } else {
-        ret = real___lxstat(ver, path, sb);
-    }
-    return ret;
-}
-
-int lstat(const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
-        init_sysfs();
-        char *newpath;
-        if (asprintf(&newpath, "%s%s",
-                     fakesysfscgroupdir,
-                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
-            errno = ENOMEM;
-            return -1;
-        }
-        ret = real_lstat(newpath, sb);
-        free(newpath);
-    } else if (STRPREFIX(path, fakedevicedir0)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(8, 0);
-        return 0;
-    } else if (STRPREFIX(path, fakedevicedir1)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(9, 0);
-        return 0;
-    } else {
-        ret = real_lstat(path, sb);
-    }
-    return ret;
-}
-
-int __xstat(int ver, const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
+# define VIR_MOCK_STAT_HOOK \
+    do { \
+        if (STRPREFIX(path, fakedevicedir0)) { \
+            sb->st_mode = S_IFBLK; \
+            sb->st_rdev = makedev(8, 0); \
+            return 0; \
+        } else if (STRPREFIX(path, fakedevicedir1)) { \
+            sb->st_mode = S_IFBLK; \
+            sb->st_rdev = makedev(9, 0); \
+            return 0; \
+        } \
+    } while (0)
 
-    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
-        init_sysfs();
-        char *newpath;
-        if (asprintf(&newpath, "%s%s",
-                     fakesysfscgroupdir,
-                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
-            errno = ENOMEM;
-            return -1;
-        }
-        ret = real___xstat(ver, newpath, sb);
-        free(newpath);
-    } else if (STRPREFIX(path, fakedevicedir0)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(8, 0);
-        return 0;
-    } else if (STRPREFIX(path, fakedevicedir1)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(9, 0);
-        return 0;
-    } else {
-        ret = real___xstat(ver, path, sb);
-    }
-    return ret;
-}
+# include "virmockstathelpers.c"
 
-int stat(const char *path, struct stat *sb)
+static int
+virMockStatRedirect(const char *path, char **newpath)
 {
-    char *newpath = NULL;
-    int ret;
-
-    init_syms();
-
     if (STREQ(path, SYSFS_CPU_PRESENT)) {
         init_sysfs();
-        if (asprintf(&newpath, "%s/%s",
+        if (asprintf(newpath, "%s/%s",
                      fakesysfscgroupdir,
-                     SYSFS_CPU_PRESENT_MOCKED) < 0) {
-            errno = ENOMEM;
+                     SYSFS_CPU_PRESENT_MOCKED) < 0)
             return -1;
-        }
     } else if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
         init_sysfs();
-        if (asprintf(&newpath, "%s%s",
+        if (asprintf(newpath, "%s%s",
                      fakesysfscgroupdir,
-                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
-            errno = ENOMEM;
-            return -1;
-        }
-    } else if (STRPREFIX(path, fakedevicedir0)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(8, 0);
-        return 0;
-    } else if (STRPREFIX(path, fakedevicedir1)) {
-        sb->st_mode = S_IFBLK;
-        sb->st_rdev = makedev(9, 0);
-        return 0;
-    } else {
-        if (!(newpath = strdup(path)))
+                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0)
             return -1;
     }
-    ret = real_stat(newpath, sb);
-    free(newpath);
-    return ret;
+    return 0;
 }
 
+
 int mkdir(const char *path, mode_t mode)
 {
     int ret;
diff --git a/tests/virfilewrapper.c b/tests/virfilewrapper.c
index 88441331ce..067cb30657 100644
--- a/tests/virfilewrapper.c
+++ b/tests/virfilewrapper.c
@@ -39,15 +39,9 @@ static size_t nprefixes;
 static const char **prefixes;
 
 /* TODO: callbacks */
-
-
 static int (*real_open)(const char *path, int flags, ...);
 static FILE *(*real_fopen)(const char *path, const char *mode);
 static int (*real_access)(const char *path, int mode);
-static int (*real_stat)(const char *path, struct stat *sb);
-static int (*real___xstat)(int ver, const char *path, struct stat *sb);
-static int (*real_lstat)(const char *path, struct stat *sb);
-static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
 static int (*real_mkdir)(const char *path, mode_t mode);
 static DIR *(*real_opendir)(const char *path);
 
@@ -58,8 +52,6 @@ static void init_syms(void)
 
     VIR_MOCK_REAL_INIT(fopen);
     VIR_MOCK_REAL_INIT(access);
-    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
-    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
     VIR_MOCK_REAL_INIT(mkdir);
     VIR_MOCK_REAL_INIT(open);
     VIR_MOCK_REAL_INIT(opendir);
@@ -115,10 +107,11 @@ virFileWrapperClearPrefixes(void)
     VIR_FREE(overrides);
 }
 
-static char *
-virFileWrapperOverridePrefix(const char *path)
+# include "virmockstathelpers.c"
+
+int
+virMockStatRedirect(const char *path, char **newpath)
 {
-    char *ret = NULL;
     size_t i = 0;
 
     for (i = 0; i < noverrides; i++) {
@@ -127,16 +120,13 @@ virFileWrapperOverridePrefix(const char *path)
         if (!tmp)
             continue;
 
-        if (virAsprintfQuiet(&ret, "%s%s", overrides[i], tmp) < 0)
-            return NULL;
+        if (virAsprintfQuiet(newpath, "%s%s", overrides[i], tmp) < 0)
+            return -1;
 
         break;
     }
 
-    if (!ret)
-        ignore_value(VIR_STRDUP_QUIET(ret, path));
-
-    return ret;
+    return 0;
 }
 
 
@@ -144,8 +134,7 @@ virFileWrapperOverridePrefix(const char *path)
     do { \
         init_syms(); \
  \
-        newpath = virFileWrapperOverridePrefix(path); \
-        if (!newpath) \
+        if (virMockStatRedirect(path, &newpath) < 0) \
             abort(); \
     } while (0)
 
@@ -156,7 +145,7 @@ FILE *fopen(const char *path, const char *mode)
 
     PATH_OVERRIDE(newpath, path);
 
-    return real_fopen(newpath, mode);
+    return real_fopen(newpath ? newpath : path, mode);
 }
 
 int access(const char *path, int mode)
@@ -165,56 +154,7 @@ int access(const char *path, int mode)
 
     PATH_OVERRIDE(newpath, path);
 
-    return real_access(newpath, mode);
-}
-
-# ifdef HAVE___LXSTAT
-int __lxstat(int ver, const char *path, struct stat *sb)
-{
-    VIR_AUTOFREE(char *) newpath = NULL;
-
-    PATH_OVERRIDE(newpath, path);
-
-    return real___lxstat(ver, newpath, sb);
-}
-# endif /* HAVE___LXSTAT */
-
-int lstat(const char *path, struct stat *sb)
-{
-    VIR_AUTOFREE(char *) newpath = NULL;
-
-    PATH_OVERRIDE(newpath, path);
-
-    return real_lstat(newpath, sb);
-}
-
-# ifdef HAVE___XSTAT
-int __xstat(int ver, const char *path, struct stat *sb)
-{
-    VIR_AUTOFREE(char *) newpath = NULL;
-
-    PATH_OVERRIDE(newpath, path);
-
-    return real___xstat(ver, newpath, sb);
-}
-# endif /* HAVE___XSTAT */
-
-int stat(const char *path, struct stat *sb)
-{
-    VIR_AUTOFREE(char *) newpath = NULL;
-
-    PATH_OVERRIDE(newpath, path);
-
-    return real_stat(newpath, sb);
-}
-
-int mkdir(const char *path, mode_t mode)
-{
-    VIR_AUTOFREE(char *) newpath = NULL;
-
-    PATH_OVERRIDE(newpath, path);
-
-    return real_mkdir(newpath, mode);
+    return real_access(newpath ? newpath : path, mode);
 }
 
 int open(const char *path, int flags, ...)
@@ -234,7 +174,7 @@ int open(const char *path, int flags, ...)
         va_end(ap);
     }
 
-    return real_open(newpath, flags, mode);
+    return real_open(newpath ? newpath : path, flags, mode);
 }
 
 DIR *opendir(const char *path)
@@ -243,6 +183,7 @@ DIR *opendir(const char *path)
 
     PATH_OVERRIDE(newpath, path);
 
-    return real_opendir(newpath);
+    return real_opendir(newpath ? newpath : path);
 }
+
 #endif
diff --git a/tests/virmock.h b/tests/virmock.h
index 9c7ecf60ce..46631433c7 100644
--- a/tests/virmock.h
+++ b/tests/virmock.h
@@ -290,15 +290,4 @@
         } \
     } while (0)
 
-# define VIR_MOCK_REAL_INIT_ALT(name1, name2) \
-    do { \
-        real_ ## name1 = dlsym(RTLD_NEXT, #name1); \
-        real_ ## name2 = dlsym(RTLD_NEXT, #name2); \
-        if (!real_##name1 && !real_##name2) { \
-            fprintf(stderr, "Cannot find real '%s' or '%s' symbol\n", \
-                    #name1, #name2); \
-            abort(); \
-        } \
-    } while (0)
-
 #endif /* LIBVIRT_VIRMOCK_H */
diff --git a/tests/virmockstathelpers.c b/tests/virmockstathelpers.c
new file mode 100644
index 0000000000..0bbea4b437
--- /dev/null
+++ b/tests/virmockstathelpers.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2019 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Helpers for dealing with the many variants of stat(). This
+ * C file should be included from any file that wants to mock
+ * stat() correctly.
+ */
+
+#include "virmock.h"
+#include "viralloc.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+/*
+ * The POSIX stat() function might resolve to any number of different
+ * symbols in the C library.
+ *
+ * The may be an additional stat64() function exposed by the headers
+ * too.
+ *
+ * On 64-bit hosts the stat & stat64 functions are identical, always
+ * refering to the 64-bit ABI.
+ *
+ * On 32-bit hosts they refer to the 32-bit & 64-bit ABIs respectively.
+ *
+ * Libvirt uses _FILE_OFFSET_BITS=64 on 32-bit hosts, which causes the
+ * C library to transparently rewrite stat() calls to be stat64() calls.
+ * Libvirt will never see the 32-bit ABI from the traditional stat()
+ * call. We cannot assume this rewriting is done using a macro. It might
+ * be, but on GLibC it is done with a magic __asm__ statement to apply
+ * the rewrite at link time instead of at preprocessing.
+ *
+ * In GLibC there may be two additional functions exposed by the headers,
+ * __xstat() and __xstat64(). When these exist, stat() and stat64() are
+ * transparently rewritten to call __xstat() and __xstat64() respectively.
+ * The former symbols will not actally exist in the library at all, only
+ * the header. The leading "__" indicates the symbols are a private impl
+ * detail of the C library that applications should not care about.
+ * Unfortunately, because we are trying to mock replace the C library,
+ * we need to know about this internal impl detail.
+ *
+ * With all this in mind the list of functions we have to mock will depend
+ * on several factors
+ *
+ *  - If _FILE_OFFSET_BITS is set, then we are on a 32-bit host, and we
+ *    only need to mock stat64 and __xstat64. The other stat / __xstat
+ *    functions exist, but we'll never call them so they can be ignored
+ *    for mocking.
+ *
+ *  - If _FILE_OFFSET_BITS is not set, then we are on a 64-bit host and
+ *    we should mock stat, stat64, __xstat & __xstat64. Either may be
+ *    called by app code.
+ *
+ *  - If __xstat & __xstat64 exist, then stat & stat64 will not exist
+ *    as symbols in the library, so the latter should not be mocked.
+ *
+ * The same all applies to lstat()
+ */
+
+
+
+#if defined(HAVE_STAT) && !defined(HAVE___XSTAT) && !defined(_FILE_OFFSET_BITS)
+# define MOCK_STAT
+#endif
+#if defined(HAVE_STAT64) && !defined(HAVE___XSTAT64)
+# define MOCK_STAT64
+#endif
+#if defined(HAVE___XSTAT) && !defined(_FILE_OFFSET_BITS)
+# define MOCK___XSTAT
+#endif
+#if defined(HAVE___XSTAT64)
+# define MOCK___XSTAT64
+#endif
+#if defined(HAVE_LSTAT) && !defined(HAVE___LXSTAT) && !defined(_FILE_OFFSET_BITS)
+# define MOCK_LSTAT
+#endif
+#if defined(HAVE_LSTAT64) && !defined(HAVE___LXSTAT64)
+# define MOCK_LSTAT64
+#endif
+#if defined(HAVE___LXSTAT) && !defined(_FILE_OFFSET_BITS)
+# define MOCK___LXSTAT
+#endif
+#if defined(HAVE___LXSTAT64)
+# define MOCK___LXSTAT64
+#endif
+
+#ifdef MOCK_STAT
+static int (*real_stat)(const char *path, struct stat *sb);
+#endif
+#ifdef MOCK_STAT64
+static int (*real_stat64)(const char *path, struct stat64 *sb);
+#endif
+#ifdef MOCK___XSTAT
+static int (*real___xstat)(int ver, const char *path, struct stat *sb);
+#endif
+#ifdef MOCK___XSTAT64
+static int (*real___xstat64)(int ver, const char *path, struct stat64 *sb);
+#endif
+#ifdef MOCK_LSTAT
+static int (*real_lstat)(const char *path, struct stat *sb);
+#endif
+#ifdef MOCK_LSTAT64
+static int (*real_lstat64)(const char *path, struct stat64 *sb);
+#endif
+#ifdef MOCK___LXSTAT
+static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
+#endif
+#ifdef MOCK___LXSTAT64
+static int (*real___lxstat64)(int ver, const char *path, struct stat64 *sb);
+#endif
+
+static bool init;
+static bool debug;
+
+#define fdebug(msg, ...) do { if (debug) fprintf(stderr, msg, __VA_ARGS__); } while (0)
+
+static void virMockStatInit(void)
+{
+    if (init)
+        return;
+
+    init = true;
+    debug = getenv("VIR_MOCK_STAT_DEBUG");
+
+#ifdef MOCK_STAT
+    VIR_MOCK_REAL_INIT(stat);
+    fdebug("real stat %p\n", real_stat);
+#endif
+#ifdef MOCK_STAT64
+    VIR_MOCK_REAL_INIT(stat64);
+    fdebug("real stat64 %p\n", real_stat64);
+#endif
+#ifdef MOCK___XSTAT
+    VIR_MOCK_REAL_INIT(__xstat);
+    fdebug("real __xstat %p\n", real___xstat);
+#endif
+#ifdef MOCK___XSTAT64
+    VIR_MOCK_REAL_INIT(__xstat64);
+    fdebug("real __xstat64 %p\n", real___xstat64);
+#endif
+#ifdef MOCK_LSTAT
+    VIR_MOCK_REAL_INIT(lstat);
+    fdebug("real lstat %p\n", real_lstat);
+#endif
+#ifdef MOCK_LSTAT64
+    VIR_MOCK_REAL_INIT(lstat64);
+    fdebug("real lstat64 %p\n", real_lstat64);
+#endif
+#ifdef MOCK___LXSTAT
+    VIR_MOCK_REAL_INIT(__lxstat);
+    fdebug("real __lxstat %p\n", real___lxstat);
+#endif
+#ifdef MOCK___LXSTAT64
+    VIR_MOCK_REAL_INIT(__lxstat64);
+    fdebug("real __lxstat64 %p\n", real___lxstat64);
+#endif
+}
+
+/*
+ * @stat: the path being queried
+ * @newpath: fill with redirected path, or leave NULL to use orig path
+ *
+ * Return 0 on success, -1 on allocation error
+ */
+static int virMockStatRedirect(const char *path, char **newpath);
+
+#ifndef VIR_MOCK_STAT_HOOK
+# define VIR_MOCK_STAT_HOOK do { } while (0)
+#endif
+
+#ifdef MOCK_STAT
+int stat(const char *path, struct stat *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("stat redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real_stat(newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK_STAT64
+int stat64(const char *path, struct stat64 *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("stat64 redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real_stat64(newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK___XSTAT
+int
+__xstat(int ver, const char *path, struct stat *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("__xstat redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real___xstat(ver, newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK___XSTAT64
+int
+__xstat64(int ver, const char *path, struct stat64 *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("__xstat64 redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real___xstat64(ver, newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK_LSTAT
+int
+lstat(const char *path, struct stat *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("lstat redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real_lstat(newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK_LSTAT64
+int
+lstat64(const char *path, struct stat64 *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("lstat64 redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real_lstat64(newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK___LXSTAT
+int
+__lxstat(int ver, const char *path, struct stat *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("__lxstat redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real___lxstat(ver, newpath ? newpath : path, sb);
+}
+#endif
+
+#ifdef MOCK___LXSTAT64
+int
+__lxstat64(int ver, const char *path, struct stat64 *sb)
+{
+    VIR_AUTOFREE(char *) newpath = NULL;
+
+    virMockStatInit();
+
+    if (virMockStatRedirect(path, &newpath) < 0)
+        abort();
+    fdebug("__lxstat64 redirect %s to %s sb=%p\n", path, newpath ? newpath : path, sb);
+
+    VIR_MOCK_STAT_HOOK;
+
+    return real___lxstat64(ver, newpath ? newpath : path, sb);
+}
+#endif
diff --git a/tests/virpcimock.c b/tests/virpcimock.c
index ce8176cbec..7f9cdaa9b8 100644
--- a/tests/virpcimock.c
+++ b/tests/virpcimock.c
@@ -31,10 +31,6 @@
 # include "dirname.h"
 
 static int (*real_access)(const char *path, int mode);
-static int (*real_lstat)(const char *path, struct stat *sb);
-static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
-static int (*real_stat)(const char *path, struct stat *sb);
-static int (*real___xstat)(int ver, const char *path, struct stat *sb);
 static int (*real_open)(const char *path, int flags, ...);
 static int (*real_close)(int fd);
 static DIR * (*real_opendir)(const char *name);
@@ -365,15 +361,9 @@ pci_device_new_from_stub(const struct pciDevice *data)
     if (virFileMakePath(devpath) < 0)
         ABORT("Unable to create: %s", devpath);
 
-    if (real_stat && real_stat(configSrc, &sb) == 0)
+    if (stat(configSrc, &sb) == 0)
         configSrcExists = true;
 
-# ifdef HAVE___XSTAT
-    if (!configSrcExists &&
-        real___xstat && real___xstat(_STAT_VER, configSrc, &sb) == 0)
-        configSrcExists = true;
-# endif
-
     /* If there is a config file for the device within virpcitestdata dir,
      * symlink it. Otherwise create a dummy config file. */
     if (configSrcExists) {
@@ -813,8 +803,6 @@ init_syms(void)
         return;
 
     VIR_MOCK_REAL_INIT(access);
-    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
-    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
     VIR_MOCK_REAL_INIT(open);
     VIR_MOCK_REAL_INIT(close);
     VIR_MOCK_REAL_INIT(opendir);
@@ -896,85 +884,17 @@ access(const char *path, int mode)
     return ret;
 }
 
-# ifdef HAVE___LXSTAT
-int
-__lxstat(int ver, const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (STRPREFIX(path, SYSFS_PCI_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;
-}
-# endif /* HAVE___LXSTAT */
-
-int
-lstat(const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
-        char *newpath;
-        if (getrealpath(&newpath, path) < 0)
-            return -1;
-        ret = real_lstat(newpath, sb);
-        VIR_FREE(newpath);
-    } else {
-        ret = real_lstat(path, sb);
-    }
-    return ret;
-}
 
-# ifdef HAVE___XSTAT
-int
-__xstat(int ver, const char *path, struct stat *sb)
+static int
+virMockStatRedirect(const char *path, char **newpath)
 {
-    int ret;
-
-    init_syms();
-
     if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
-        char *newpath;
-        if (getrealpath(&newpath, path) < 0)
+        if (getrealpath(newpath, path) < 0)
             return -1;
-        ret = real___xstat(ver, newpath, sb);
-        VIR_FREE(newpath);
-    } else {
-        ret = real___xstat(ver, path, sb);
     }
-    return ret;
+    return 0;
 }
-# endif /* HAVE___XSTAT */
 
-int
-stat(const char *path, struct stat *sb)
-{
-    int ret;
-
-    init_syms();
-
-    if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
-        char *newpath;
-        if (getrealpath(&newpath, path) < 0)
-            return -1;
-        ret = real_stat(newpath, sb);
-        VIR_FREE(newpath);
-    } else {
-        ret = real_stat(path, sb);
-    }
-    return ret;
-}
 
 int
 open(const char *path, int flags, ...)
@@ -1058,6 +978,9 @@ virFileCanonicalizePath(const char *path)
 
     return ret;
 }
+
+# include "virmockstathelpers.c"
+
 #else
 /* Nothing to override on this platform */
 #endif
diff --git a/tests/virtestmock.c b/tests/virtestmock.c
index 3049c90789..bc62312444 100644
--- a/tests/virtestmock.c
+++ b/tests/virtestmock.c
@@ -36,33 +36,9 @@
 #include "viralloc.h"
 #include "virfile.h"
 
-/* stat can be a macro as follows:
- *
- *   #define stat stat64
- *
- * This wouldn't fly with our mock. Make sure that the macro (and
- * all its friends) are undefined. We don't want anybody mangling
- * our code. */
-#undef stat
-#undef stat64
-#undef __xstat
-#undef __xstat64
-#undef lstat
-#undef lstat64
-#undef __lxstat
-#undef __lxstat64
-
 static int (*real_open)(const char *path, int flags, ...);
 static FILE *(*real_fopen)(const char *path, const char *mode);
 static int (*real_access)(const char *path, int mode);
-static int (*real_stat)(const char *path, struct stat *sb);
-static int (*real_stat64)(const char *path, void *sb);
-static int (*real___xstat)(int ver, const char *path, struct stat *sb);
-static int (*real___xstat64)(int ver, const char *path, void *sb);
-static int (*real_lstat)(const char *path, struct stat *sb);
-static int (*real_lstat64)(const char *path, void *sb);
-static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
-static int (*real___lxstat64)(int ver, const char *path, void *sb);
 static int (*real_connect)(int fd, const struct sockaddr *addr, socklen_t addrlen);
 
 static const char *progname;
@@ -78,10 +54,6 @@ static void init_syms(void)
     VIR_MOCK_REAL_INIT(open);
     VIR_MOCK_REAL_INIT(fopen);
     VIR_MOCK_REAL_INIT(access);
-    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
-    VIR_MOCK_REAL_INIT_ALT(stat64, __xstat64);
-    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
-    VIR_MOCK_REAL_INIT_ALT(lstat64, __lxstat64);
     VIR_MOCK_REAL_INIT(connect);
 }
 
@@ -217,119 +189,15 @@ int access(const char *path, int mode)
     return real_access(path, mode);
 }
 
-/* Okay, the following ifdef rain forest may look messy at a
- * first glance. But here's the thing: during run time linking of
- * a binary, stat() may not be actually needing symbol stat. It
- * might as well not had been stat() in the first place (see the
- * reasoning at the beginning of this file). However, if we would
- * expose stat symbol here, we will poison the well and trick
- * dynamic linker into thinking we are some old binary that still
- * uses the symbol. So whenever code from upper layers calls
- * stat(), the control would get here, but real_stat can actually
- * be a NULL pointer because newer glibc have __xstat instead.
- * Worse, it can have __xstat64 instead __xstat.
- *
- * Anyway, these ifdefs are there to implement the following
- * preference function:
- *
- * stat < stat64 < __xstat < __xstat64
- *
- * It's the same story with lstat.
- * Also, I feel sorry for you that you had to read this.
- */
-#if defined(HAVE_STAT) && !defined(HAVE___XSTAT)
-int stat(const char *path, struct stat *sb)
-{
-    init_syms();
 
-    checkPath(path, "stat");
+#define VIR_MOCK_STAT_HOOK CHECK_PATH(path)
 
-    return real_stat(path, sb);
-}
-#endif
+#include "virmockstathelpers.c"
 
-#if defined(HAVE_STAT64) && !defined(HAVE___XSTAT64)
-int stat64(const char *path, struct stat64 *sb)
+static int virMockStatRedirect(const char *path ATTRIBUTE_UNUSED, char **newpath ATTRIBUTE_UNUSED)
 {
-    init_syms();
-
-    checkPath(path, "stat");
-
-    return real_stat64(path, sb);
+    return 0;
 }
-#endif
-
-#if defined(HAVE___XSTAT) && !defined(HAVE___XSTAT64)
-int
-__xstat(int ver, const char *path, struct stat *sb)
-{
-    init_syms();
-
-    checkPath(path, "stat");
-
-    return real___xstat(ver, path, sb);
-}
-#endif
-
-#if defined(HAVE___XSTAT64)
-int
-__xstat64(int ver, const char *path, struct stat64 *sb)
-{
-    init_syms();
-
-    checkPath(path, "stat");
-
-    return real___xstat64(ver, path, sb);
-}
-#endif
-
-#if defined(HAVE_LSTAT) && !defined(HAVE___LXSTAT)
-int
-lstat(const char *path, struct stat *sb)
-{
-    init_syms();
-
-    checkPath(path, "lstat");
-
-    return real_lstat(path, sb);
-}
-#endif
-
-#if defined(HAVE_LSTAT64) && !defined(HAVE___LXSTAT64)
-int
-lstat64(const char *path, struct stat64 *sb)
-{
-    init_syms();
-
-    checkPath(path, "lstat");
-
-    return real_lstat64(path, sb);
-}
-#endif
-
-#if defined(HAVE___LXSTAT) && !defined(HAVE___LXSTAT64)
-int
-__lxstat(int ver, const char *path, struct stat *sb)
-{
-    init_syms();
-
-    checkPath(path, "lstat");
-
-    return real___lxstat(ver, path, sb);
-}
-#endif
-
-#if defined(HAVE___LXSTAT64)
-int
-__lxstat64(int ver, const char *path, struct stat64 *sb)
-{
-    init_syms();
-
-    checkPath(path, "lstat");
-
-    return real___lxstat64(ver, path, sb);
-}
-#endif
 
 
 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
-- 
2.20.1




More information about the libvir-list mailing list