lib/learning-switch.h \
lib/list.c \
lib/list.h \
+ lib/lockfile.c \
+ lib/lockfile.h \
lib/mac-learning.c \
lib/mac-learning.h \
lib/netdev-linux.c \
# All the source files that have coverage counters.
COVERAGE_FILES = \
- lib/cfg.c \
lib/dpif.c \
lib/flow.c \
+ lib/lockfile.c \
lib/hmap.c \
lib/mac-learning.c \
lib/netdev.c \
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include "coverage.h"
#include "dynamic-string.h"
+#include "lockfile.h"
#include "ofpbuf.h"
#include "packets.h"
#include "svec.h"
static char *tmp_name;
/* Lock information. */
-static char *lock_name;
-static int lock_fd = -1;
+static struct lockfile *lockfile;
/* Flag to indicate whether local modifications have been made. */
static bool dirty;
int
cfg_set_file(const char *file_name)
{
- const char *slash;
+ char *lock_name;
int fd;
if (cfg_name) {
- assert(lock_fd < 0);
+ assert(!lockfile);
free(cfg_name);
- free(lock_name);
free(tmp_name);
- cfg_name = lock_name = tmp_name = NULL;
+ cfg_name = tmp_name = NULL;
}
/* Make sure that we can open this file for reading. */
* rename(tmp_name, cfg_name) will work. */
tmp_name = xasprintf("%s.~tmp~", file_name);
- /* Put the lock file in the same directory as cfg_name, but prefixed by
- * a dot so as not to garner administrator interest. */
- slash = strrchr(file_name, '/');
- if (slash) {
- lock_name = xasprintf("%.*s/.%s.~lock~",
- slash - file_name, file_name, slash + 1);
- } else {
- lock_name = xasprintf(".%s.~lock~", file_name);
- }
-
+ lock_name = lockfile_name(file_name);
VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file",
file_name, lock_name);
+ free(lock_name);
+
return 0;
}
void
cfg_unlock(void)
{
- if (lock_fd != -1) {
- COVERAGE_INC(cfg_unlock);
- close(lock_fd);
- lock_fd = -1;
- }
-}
-
-static int
-open_lockfile(const char *name)
-{
- for (;;) {
- /* Try to open an existing lock file. */
- int fd = open(name, O_RDWR);
- if (fd >= 0) {
- return fd;
- } else if (errno != ENOENT) {
- VLOG_WARN("%s: failed to open lock file: %s",
- name, strerror(errno));
- return -errno;
- }
-
- /* Try to create a new lock file. */
- VLOG_INFO("%s: lock file does not exist, creating", name);
- fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
- if (fd >= 0) {
- return fd;
- } else if (errno != EEXIST) {
- VLOG_WARN("%s: failed to create lock file: %s",
- name, strerror(errno));
- return -errno;
- }
-
- /* Someone else created the lock file. Try again. */
+ if (lockfile) {
+ lockfile_unlock(lockfile);
+ lockfile = NULL;
}
}
-static int
-try_lock(int fd, bool block)
-{
- struct flock l;
- int error;
-
- memset(&l, 0, sizeof l);
- l.l_type = F_WRLCK;
- l.l_whence = SEEK_SET;
- l.l_start = 0;
- l.l_len = 0;
-
- time_disable_restart();
- error = fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
- time_enable_restart();
-
- return error;
-}
-
/* Locks the configuration file against modification by other processes and
* re-reads it from disk.
*
int
cfg_lock(uint8_t *cookie, int timeout)
{
- long long int start;
- long long int elapsed = 0;
- int fd;
- uint8_t curr_cookie[CFG_COOKIE_LEN];
-
- assert(lock_fd < 0);
- COVERAGE_INC(cfg_lock);
-
- time_refresh();
- start = time_msec();
- for (;;) {
- int error;
-
- /* Open lock file. */
- fd = open_lockfile(lock_name);
- if (fd < 0) {
- return -fd;
- }
-
- /* Try to lock it. This will block (if 'timeout' > 0). */
- error = try_lock(fd, timeout > 0);
- time_refresh();
- elapsed = time_msec() - start;
- if (!error) {
- /* Success! */
- break;
- }
-
- /* Lock failed. Close the lock file and reopen it on the next
- * iteration, just in case someone deletes it underneath us (even
- * though that should not happen). */
- close(fd);
- if (error != EINTR) {
- /* Hard error, give up. */
- COVERAGE_INC(cfg_lock_error);
- VLOG_WARN("%s: failed to lock file "
- "(after %lld ms, with %d-ms timeout): %s",
- lock_name, elapsed, timeout, strerror(error));
- return error;
- }
+ int error;
- /* Probably, the periodic timer set up by time_init() woke up us. Just
- * check whether it's time to give up. */
- if (timeout != INT_MAX && elapsed >= timeout) {
- COVERAGE_INC(cfg_lock_timeout);
- VLOG_WARN("%s: giving up on lock file after %lld ms",
- lock_name, elapsed);
- return ETIMEDOUT;
- }
- COVERAGE_INC(cfg_lock_retry);
- }
- if (elapsed) {
- VLOG_WARN("%s: waited %lld ms for lock file", lock_name, elapsed);
+ assert(!lockfile);
+ error = lockfile_lock(cfg_name, timeout, &lockfile);
+ if (error) {
+ return error;
}
- lock_fd = fd;
cfg_read();
if (cookie) {
+ uint8_t curr_cookie[CFG_COOKIE_LEN];
cfg_get_cookie(curr_cookie);
if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) {
#include <unistd.h>
#include "fatal-signal.h"
#include "dirs.h"
+#include "lockfile.h"
#include "timeval.h"
#include "util.h"
chdir("/");
}
time_postfork();
+ lockfile_postfork();
break;
case -1:
--- /dev/null
+ /* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "hash.h"
+#include "hmap.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_lockfile
+#include "vlog.h"
+
+struct lockfile {
+ struct hmap_node hmap_node;
+ char *name;
+ dev_t device;
+ ino_t inode;
+ int fd;
+};
+
+/* Lock table.
+ *
+ * We have to do this stupid dance because POSIX says that closing *any* file
+ * descriptor for a file on which a process holds a lock drops *all* locks on
+ * that file. That means that we can't afford to open a lockfile more than
+ * once. */
+static struct hmap lock_table = HMAP_INITIALIZER(&lock_table);
+
+static void lockfile_unhash(struct lockfile *);
+static int lockfile_try_lock(const char *name, bool block,
+ struct lockfile **lockfilep);
+
+/* Returns the name of the lockfile that would be created for locking a file
+ * named 'file_name'. The caller is responsible for freeing the returned
+ * name, with free(), when it is no longer needed. */
+char *
+lockfile_name(const char *file_name)
+{
+ const char *slash = strrchr(file_name, '/');
+ return (slash
+ ? xasprintf("%.*s/.%s.~lock~", slash - file_name, file_name,
+ slash + 1)
+ : xasprintf(".%s.~lock~", file_name));
+}
+
+/* Locks the configuration file against modification by other processes and
+ * re-reads it from disk.
+ *
+ * The 'timeout' specifies the maximum number of milliseconds to wait for the
+ * config file to become free. Use 0 to avoid waiting or INT_MAX to wait
+ * forever.
+ *
+ * Returns 0 on success, otherwise a positive errno value. On success,
+ * '*lockfilep' is set to point to a new "struct lockfile *" that may be
+ * unlocked with lockfile_unlock(). On failure, '*lockfilep' is set to
+ * NULL. */
+int
+lockfile_lock(const char *file, int timeout, struct lockfile **lockfilep)
+{
+ /* Only exclusive ("write") locks are supported. This is not a problem
+ * because the Open vSwitch code that currently uses lock files does so in
+ * stylized ways such that any number of readers may access a file while it
+ * is being written. */
+ long long int start, elapsed;
+ char *lock_name;
+ int error;
+
+ COVERAGE_INC(lockfile_lock);
+
+ lock_name = lockfile_name(file);
+ time_refresh();
+ start = time_msec();
+
+ do {
+ error = lockfile_try_lock(lock_name, timeout > 0, lockfilep);
+ time_refresh();
+ elapsed = time_msec() - start;
+ } while (error == EINTR && (timeout == INT_MAX || elapsed < timeout));
+
+ if (!error) {
+ if (elapsed) {
+ VLOG_WARN("%s: waited %lld ms for lock file",
+ lock_name, elapsed);
+ }
+ } else if (error == EINTR) {
+ COVERAGE_INC(lockfile_timeout);
+ VLOG_WARN("%s: giving up on lock file after %lld ms",
+ lock_name, elapsed);
+ error = ETIMEDOUT;
+ } else {
+ COVERAGE_INC(lockfile_error);
+ if (error == EACCES) {
+ error = EAGAIN;
+ }
+ VLOG_WARN("%s: failed to lock file "
+ "(after %lld ms, with %d-ms timeout): %s",
+ lock_name, elapsed, timeout, strerror(error));
+ }
+
+ free(lock_name);
+ return error;
+}
+
+/* Unlocks 'lockfile', which must have been created by a call to
+ * lockfile_lock(), and frees 'lockfile'. */
+void
+lockfile_unlock(struct lockfile *lockfile)
+{
+ if (lockfile) {
+ COVERAGE_INC(lockfile_unlock);
+ lockfile_unhash(lockfile);
+ free(lockfile->name);
+ free(lockfile);
+ }
+}
+
+/* Marks all the currently locked lockfiles as no longer locked. It makes
+ * sense to call this function after fork(), because a child created by fork()
+ * does not hold its parents' locks. */
+void
+lockfile_postfork(void)
+{
+ struct lockfile *lockfile;
+
+ HMAP_FOR_EACH (lockfile, struct lockfile, hmap_node, &lock_table) {
+ if (lockfile->fd >= 0) {
+ VLOG_WARN("%s: child does not inherit lock", lockfile->name);
+ lockfile_unhash(lockfile);
+ }
+ }
+}
+\f
+static uint32_t
+lockfile_hash(dev_t device, ino_t inode)
+{
+ return hash_bytes(&device, sizeof device,
+ hash_bytes(&inode, sizeof inode, 0));
+}
+
+static struct lockfile *
+lockfile_find(dev_t device, ino_t inode)
+{
+ struct lockfile *lockfile;
+
+ HMAP_FOR_EACH_WITH_HASH (lockfile, struct lockfile, hmap_node,
+ lockfile_hash(device, inode), &lock_table) {
+ if (lockfile->device == device && lockfile->inode == inode) {
+ return lockfile;
+ }
+ }
+ return NULL;
+}
+
+static void
+lockfile_unhash(struct lockfile *lockfile)
+{
+ if (lockfile->fd >= 0) {
+ close(lockfile->fd);
+ lockfile->fd = -1;
+ hmap_remove(&lock_table, &lockfile->hmap_node);
+ }
+}
+
+static struct lockfile *
+lockfile_register(const char *name, dev_t device, ino_t inode, int fd)
+{
+ struct lockfile *lockfile;
+
+ lockfile = lockfile_find(device, inode);
+ if (lockfile) {
+ VLOG_ERR("%s: lock file disappeared and reappeared!", name);
+ lockfile_unhash(lockfile);
+ }
+
+ lockfile = xmalloc(sizeof *lockfile);
+ lockfile->name = xstrdup(name);
+ lockfile->device = device;
+ lockfile->inode = inode;
+ lockfile->fd = fd;
+ hmap_insert(&lock_table, &lockfile->hmap_node,
+ lockfile_hash(device, inode));
+ return lockfile;
+}
+
+static int
+lockfile_try_lock(const char *name, bool block, struct lockfile **lockfilep)
+{
+ struct flock l;
+ struct stat s;
+ int error;
+ int fd;
+
+ *lockfilep = NULL;
+
+ /* Open the lock file, first creating it if necessary. */
+ for (;;) {
+ /* Check whether we've already got a lock on that file. */
+ if (!stat(name, &s)) {
+ if (lockfile_find(s.st_dev, s.st_ino)) {
+ return EDEADLK;
+ }
+ } else if (errno != ENOENT) {
+ VLOG_WARN("%s: failed to stat lock file: %s",
+ name, strerror(errno));
+ return errno;
+ }
+
+ /* Try to open an existing lock file. */
+ fd = open(name, O_RDWR);
+ if (fd >= 0) {
+ break;
+ } else if (errno != ENOENT) {
+ VLOG_WARN("%s: failed to open lock file: %s",
+ name, strerror(errno));
+ return errno;
+ }
+
+ /* Try to create a new lock file. */
+ VLOG_INFO("%s: lock file does not exist, creating", name);
+ fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0) {
+ break;
+ } else if (errno != EEXIST) {
+ VLOG_WARN("%s: failed to create lock file: %s",
+ name, strerror(errno));
+ return errno;
+ }
+
+ /* Someone else created the lock file. Try again. */
+ }
+
+ /* Get the inode and device number for the lock table. */
+ if (fstat(fd, &s)) {
+ VLOG_ERR("%s: failed to fstat lock file: %s", name, strerror(errno));
+ close(fd);
+ return errno;
+ }
+
+ /* Try to lock the file. */
+ memset(&l, 0, sizeof l);
+ l.l_type = F_WRLCK;
+ l.l_whence = SEEK_SET;
+ l.l_start = 0;
+ l.l_len = 0;
+
+ time_disable_restart();
+ error = fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
+ time_enable_restart();
+
+ if (!error) {
+ *lockfilep = lockfile_register(name, s.st_dev, s.st_ino, fd);
+ } else {
+ close(fd);
+ }
+ return error;
+}
+
--- /dev/null
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCKFILE_H
+#define LOCKFILE_H 1
+
+struct lockfile;
+
+char *lockfile_name(const char *file);
+int lockfile_lock(const char *file, int timeout, struct lockfile **);
+void lockfile_unlock(struct lockfile *);
+void lockfile_postfork(void);
+
+#endif /* lib/lockfile.h */
VLOG_MODULE(in_band)
VLOG_MODULE(leak_checker)
VLOG_MODULE(learning_switch)
+VLOG_MODULE(lockfile)
VLOG_MODULE(mac_learning)
VLOG_MODULE(mgmt)
VLOG_MODULE(netdev)
tests/lcov-pre.at \
tests/library.at \
tests/timeval.at \
+ tests/lockfile.at \
tests/stp.at \
tests/ovs-vsctl.at \
tests/lcov-post.at
tests_test_list_SOURCES = tests/test-list.c
tests_test_list_LDADD = lib/libopenvswitch.a
+noinst_PROGRAMS += tests/test-lockfile
+tests_test_lockfile_SOURCES = tests/test-lockfile.c
+tests_test_lockfile_LDADD = lib/libopenvswitch.a
+
noinst_PROGRAMS += tests/test-sha1
tests_test_sha1_SOURCES = tests/test-sha1.c
tests_test_sha1_LDADD = lib/libopenvswitch.a
--- /dev/null
+AT_BANNER([lockfile unit tests])
+
+m4_define([CHECK_LOCKFILE],
+ [AT_SETUP([m4_translit([$1], [_], [ ])])
+ AT_KEYWORDS([lockfile])
+ OVS_CHECK_LCOV([test-lockfile $1], [0], [$1: success (m4_if(
+ [$2], [1], [$2 child], [$2 children]))
+])
+ AT_CLEANUP])
+
+CHECK_LOCKFILE([lock_and_unlock], [0])
+CHECK_LOCKFILE([lock_and_unlock_twice], [0])
+CHECK_LOCKFILE([lock_blocks_same_process], [0])
+CHECK_LOCKFILE([lock_blocks_same_process_twice], [0])
+CHECK_LOCKFILE([lock_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_twice_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_and_unlock_allows_other_process], [1])
+CHECK_LOCKFILE([lock_timeout_gets_the_lock], [1])
+CHECK_LOCKFILE([lock_timeout_runs_out], [1])
+CHECK_LOCKFILE([lock_multiple], [0])
--- /dev/null
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "process.h"
+#include "timeval.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct test {
+ const char *name;
+ void (*function)(void);
+};
+
+static const struct test tests[];
+
+static void
+run_lock_and_unlock(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_and_unlock_twice(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ lockfile_unlock(lockfile);
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+ lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process_twice(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+ assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+ lockfile_unlock(lockfile);
+}
+
+static enum { PARENT, CHILD }
+do_fork(void)
+{
+ switch (fork()) {
+ case 0:
+ time_postfork();
+ lockfile_postfork();
+ return CHILD;
+
+ default:
+ return PARENT;
+
+ case -1:
+ /* Error. */
+ ovs_fatal(errno, "fork failed");
+ }
+}
+
+static void
+run_lock_blocks_other_process(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ if (do_fork() == CHILD) {
+ assert(lockfile_lock("file", 0, &lockfile) == EAGAIN);
+ exit(11);
+ }
+}
+
+static void
+run_lock_twice_blocks_other_process(void)
+{
+ struct lockfile *lockfile, *dummy;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ assert(lockfile_lock("file", 0, &dummy) == EDEADLK);
+ if (do_fork() == CHILD) {
+ assert(lockfile_lock("file", 0, &dummy) == EAGAIN);
+ exit(11);
+ }
+}
+
+static void
+run_lock_and_unlock_allows_other_process(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ lockfile_unlock(lockfile);
+
+ if (do_fork() == CHILD) {
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+ exit(11);
+ }
+}
+
+static void
+run_lock_timeout_gets_the_lock(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+ if (do_fork() == CHILD) {
+ assert(lockfile_lock("file", TIME_UPDATE_INTERVAL * 3,
+ &lockfile) == 0);
+ exit(11);
+ } else {
+ long long int now = time_msec();
+ while (time_msec() < now + TIME_UPDATE_INTERVAL) {
+ pause();
+ }
+ lockfile_unlock(lockfile);
+ }
+}
+
+static void
+run_lock_timeout_runs_out(void)
+{
+ struct lockfile *lockfile;
+
+ assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+ if (do_fork() == CHILD) {
+ assert(lockfile_lock("file", TIME_UPDATE_INTERVAL,
+ &lockfile) == ETIMEDOUT);
+ exit(11);
+ } else {
+ long long int now = time_msec();
+ while (time_msec() < now + TIME_UPDATE_INTERVAL * 3) {
+ pause();
+ }
+ lockfile_unlock(lockfile);
+ }
+}
+
+static void
+run_lock_multiple(void)
+{
+ struct lockfile *a, *b, *c, *dummy;
+
+ assert(lockfile_lock("a", 0, &a) == 0);
+ assert(lockfile_lock("b", 0, &b) == 0);
+ assert(lockfile_lock("c", 0, &c) == 0);
+
+ lockfile_unlock(a);
+ assert(lockfile_lock("a", 0, &a) == 0);
+ assert(lockfile_lock("a", 0, &dummy) == EDEADLK);
+ lockfile_unlock(a);
+
+ lockfile_unlock(b);
+ assert(lockfile_lock("a", 0, &a) == 0);
+
+ lockfile_unlock(c);
+ lockfile_unlock(a);
+}
+
+static void
+run_help(void)
+{
+ size_t i;
+
+ printf("usage: %s TESTNAME\n"
+ "where TESTNAME is one of the following:\n",
+ program_name);
+ for (i = 0; tests[i].name; i++) {
+ fprintf(stderr, "\t%s\n", tests[i].name);
+ }
+}
+
+static const struct test tests[] = {
+#define TEST(NAME) { #NAME, run_##NAME }
+ TEST(lock_and_unlock),
+ TEST(lock_and_unlock_twice),
+ TEST(lock_blocks_same_process),
+ TEST(lock_blocks_same_process_twice),
+ TEST(lock_blocks_other_process),
+ TEST(lock_twice_blocks_other_process),
+ TEST(lock_and_unlock_allows_other_process),
+ TEST(lock_timeout_gets_the_lock),
+ TEST(lock_timeout_runs_out),
+ TEST(lock_multiple),
+ TEST(help),
+ { 0, 0 }
+#undef TEST
+};
+
+int
+main(int argc, char *argv[])
+{
+ size_t i;
+
+ set_program_name(argv[0]);
+ time_init();
+
+ if (argc != 2) {
+ ovs_fatal(0, "exactly one argument required; use \"%s help\" for help",
+ program_name);
+ return 1;
+ }
+
+ for (i = 0; tests[i].name; i++) {
+ if (!strcmp(argv[1], tests[i].name)) {
+ int n_children;
+ int status;
+
+ (tests[i].function)();
+
+ n_children = 0;
+ while (wait(&status) > 0) {
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 11) {
+ n_children++;
+ } else {
+ ovs_fatal(0, "child exited in unexpected way: %s",
+ process_status_msg(status));
+ }
+ }
+ if (errno != ECHILD) {
+ ovs_fatal(errno, "wait");
+ }
+
+ printf("%s: success (%d child%s)\n",
+ tests[i].name, n_children, n_children != 1 ? "ren" : "");
+ exit(0);
+ }
+ }
+ ovs_fatal(0, "unknown test \"%s\"; use \"%s help\" for help",
+ argv[1], program_name);
+}
+
m4_include([tests/lcov-pre.at])
m4_include([tests/library.at])
m4_include([tests/timeval.at])
+m4_include([tests/lockfile.at])
m4_include([tests/stp.at])
m4_include([tests/ovs-vsctl.at])
m4_include([tests/lcov-post.at])