Implement library for lockfiles and use it in cfg code.
authorBen Pfaff <blp@nicira.com>
Wed, 14 Oct 2009 23:52:04 +0000 (16:52 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 29 Oct 2009 22:20:56 +0000 (15:20 -0700)
This is useful because the upcoming configuration database also needs a
lockfile implementation.

Also adds tests.

lib/automake.mk
lib/cfg.c
lib/daemon.c
lib/lockfile.c [new file with mode: 0644]
lib/lockfile.h [new file with mode: 0644]
lib/vlog-modules.def
tests/automake.mk
tests/lockfile.at [new file with mode: 0644]
tests/test-lockfile.c [new file with mode: 0644]
tests/testsuite.at

index 9ba513a..825395a 100644 (file)
@@ -55,6 +55,8 @@ lib_libopenvswitch_a_SOURCES = \
        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 \
@@ -175,9 +177,9 @@ install-data-local:
 
 # 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 \
index 2cb4f34..d61cd77 100644 (file)
--- a/lib/cfg.c
+++ b/lib/cfg.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"
@@ -52,8 +51,7 @@ static char *cfg_name;
 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;
@@ -106,15 +104,14 @@ cfg_init(void)
 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. */
@@ -131,18 +128,11 @@ cfg_set_file(const char *file_name)
      * 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;
 }
 
@@ -280,61 +270,12 @@ cfg_get_cookie(uint8_t *cookie)
 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.
  *
@@ -346,65 +287,18 @@ try_lock(int fd, bool block)
 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)) {
index a35c639..e78538c 100644 (file)
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include "fatal-signal.h"
 #include "dirs.h"
+#include "lockfile.h"
 #include "timeval.h"
 #include "util.h"
 
@@ -224,6 +225,7 @@ daemonize(void)
                 chdir("/");
             }
             time_postfork();
+            lockfile_postfork();
             break;
 
         case -1:
diff --git a/lib/lockfile.c b/lib/lockfile.c
new file mode 100644 (file)
index 0000000..e5a041e
--- /dev/null
@@ -0,0 +1,280 @@
+ /* 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;
+}
+
diff --git a/lib/lockfile.h b/lib/lockfile.h
new file mode 100644 (file)
index 0000000..c52fa21
--- /dev/null
@@ -0,0 +1,26 @@
+/* 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 */
index ce298b5..59ab045 100644 (file)
@@ -42,6 +42,7 @@ VLOG_MODULE(flow)
 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)
index 9336219..1d236a4 100644 (file)
@@ -9,6 +9,7 @@ TESTSUITE_AT = \
        tests/lcov-pre.at \
        tests/library.at \
        tests/timeval.at \
+       tests/lockfile.at \
        tests/stp.at \
        tests/ovs-vsctl.at \
        tests/lcov-post.at
@@ -63,6 +64,10 @@ noinst_PROGRAMS += tests/test-list
 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
diff --git a/tests/lockfile.at b/tests/lockfile.at
new file mode 100644 (file)
index 0000000..90a142c
--- /dev/null
@@ -0,0 +1,20 @@
+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])
diff --git a/tests/test-lockfile.c b/tests/test-lockfile.c
new file mode 100644 (file)
index 0000000..31e13a7
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * 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);
+}
+
index 9edfdee..b608349 100644 (file)
@@ -20,6 +20,7 @@ AT_TESTED([ovs-vsctl])
 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])