#include <config.h>
#include "cfg.h"
#include <arpa/inet.h>
+#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.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"
#define CC_FILE_NAME CC_ALNUM "._-"
#define CC_KEY CC_ALNUM "._-@$:+"
-/* Adds 'file_name' to the set of files or directories that are read by
- * cfg_read(). Returns 0 on success, otherwise a positive errno value if
- * 'file_name' cannot be opened.
- *
- * If 'file_name' names a file, then cfg_read() will read it. If 'file_name'
- * names a directory, then cfg_read() will read all of the files in that
- * directory whose names consist entirely of the English letters, digits,
- * periods, underscores, and hyphens and do not begin with a period.
- * Subdirectories are not processed recursively.
+/* Sets 'file_name' as the configuration file read by cfg_read(). Returns 0 on
+ * success, otherwise a positive errno value if 'file_name' cannot be opened.
*
* This function does not actually read the named file or directory. Use
* cfg_read() to (re)read all the configuration files. */
int
cfg_set_file(const char *file_name)
{
+ const char *slash;
int fd;
if (cfg_name) {
+ assert(lock_fd < 0);
free(cfg_name);
free(lock_name);
free(tmp_name);
cfg_name = lock_name = tmp_name = NULL;
}
- /* Make sure that we can open this file or directory for reading. */
+ /* Make sure that we can open this file for reading. */
fd = open(file_name, O_RDONLY);
if (fd < 0) {
return errno;
}
close(fd);
- /* Add it to the list. */
- VLOG_INFO("using \"%s\" as the configuration file", file_name);
cfg_name = xstrdup(file_name);
- lock_name = xasprintf("%s.~lock~", file_name);
+
+ /* Put the temporary file in the same directory as cfg_name, so that they
+ * are guaranteed to be in the same file system, to guarantee that
+ * 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);
+ }
+
+ VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file",
+ file_name, lock_name);
return 0;
}
cfg_unlock(void)
{
if (lock_fd != -1) {
+ COVERAGE_INC(cfg_unlock);
close(lock_fd);
- unlink(lock_name);
lock_fd = -1;
}
}
-/* Config may change, so caller is responsible for notifying others if it
- * did. The 'timeout' specifies the maximum number of milliseconds to
- * wait for the config file to become free. Returns positive errno. */
+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. */
+ }
+}
+
+static int
+try_lock(int fd, bool block)
+{
+ struct flock l;
+ memset(&l, 0, sizeof l);
+ l.l_type = F_WRLCK;
+ l.l_whence = SEEK_SET;
+ l.l_start = 0;
+ l.l_len = 0;
+ return fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
+}
+
+/* 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. */
int
cfg_lock(uint8_t *cookie, int timeout)
{
long long int start = time_msec();
+ long long int elapsed = 0;
int fd;
uint8_t curr_cookie[CFG_COOKIE_LEN];
+ assert(lock_fd < 0);
+ COVERAGE_INC(cfg_lock);
for (;;) {
- /* Put the lock file in the same directory as cfg_name, so that
- * they are guaranteed to be in the same file system. */
- fd = create_lockfile(lock_name);
- if (fd < 0 && (timeout <= (time_msec() - start))) {
- /* Wait a millisecond before trying again. */
- usleep(1000);
- } else if (fd < 0) {
- /* Couldn't lock config file in time. */
+ int error;
+
+ /* Open lock file. */
+ fd = open_lockfile(lock_name);
+ if (fd < 0) {
return -fd;
- } else {
+ }
+
+ /* 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;
+ }
+
+ /* 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);
}
lock_fd = fd;
+++ /dev/null
-/* Copyright (C) 2008, 2009 Nicira Networks, Inc. All rights reserved. */
-
-#include <config.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stropts.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "lockfile.h"
-
-#define THIS_MODULE VLM_lockfile
-#include "vlog.h"
-
-static int
-fcntl_lock(int fd)
-{
- struct flock l;
- memset(&l, 0, sizeof l);
- l.l_type = F_WRLCK;
- l.l_whence = SEEK_SET;
- l.l_start = 0;
- l.l_len = 0;
- return fcntl(fd, F_SETLK, &l) == -1 ? errno : 0;
-}
-
-/* Remove the lockfile with 'name', if it is stale. Returns 0 if successful,
- * otherwise a negative errno value if the lockfile is fresh or if it otherwise
- * cannot be removed. */
-int
-remove_lockfile(const char *name)
-{
- char buffer[BUFSIZ];
- ssize_t n;
- pid_t pid;
- int fd;
-
- /* Remove existing lockfile. */
- fd = open(name, O_RDWR);
- if (fd < 0) {
- if (errno == ENOENT) {
- return 0;
- } else {
- VLOG_ERR("%s: open: %s", name, strerror(errno));
- return -errno;
- }
- }
-
- /* Read lockfile. */
- n = read(fd, buffer, sizeof buffer - 1);
- if (n < 0) {
- int error = errno;
- VLOG_ERR("%s: read: %s", name, strerror(error));
- close(fd);
- return -error;
- }
- buffer[n] = '\0';
- if (n == 4 && memchr(buffer, '\0', n)) {
- int32_t x;
- memcpy(&x, buffer, sizeof x);
- pid = x;
- } else if (n >= 0) {
- pid = strtol(buffer, NULL, 10);
- }
- if (pid <= 0) {
- close(fd);
- VLOG_WARN("%s: format not recognized, treating as locked.", name);
- return -EACCES;
- }
-
- /* Is lockfile fresh? */
- if (strstr(buffer, "fcntl")) {
- int retval = fcntl_lock(fd);
- if (retval) {
- int error = errno;
- close(fd);
- VLOG_ERR("%s: device is locked (via fcntl): %s",
- name, strerror(retval));
- return -error;
- } else {
- VLOG_WARN("%s: removing stale lockfile (checked via fcntl)", name);
- }
- } else {
- if (!(kill(pid, 0) < 0 && errno == ESRCH)) {
- close(fd);
- VLOG_ERR("%s: device is locked (without fcntl)", name);
- return -EACCES;
- } else {
- VLOG_WARN("%s: removing stale lockfile (without fcntl)", name);
- }
- }
- close(fd);
-
- /* Remove stale lockfile. */
- if (unlink(name)) {
- VLOG_ERR("%s: unlink: %s", name, strerror(errno));
- return -errno;
- }
- return 0;
-}
-
-/* Attempt to create the lockfile. On error, return -1 with errno set
- * to an appropriate value. On success, return a file descriptor. */
-int
-create_lockfile(const char *name)
-{
- const char *username;
- char buffer[BUFSIZ];
- struct passwd *pwd;
- mode_t old_umask;
- uid_t uid;
- int retval;
- int fd;
-
- /* Remove an existing lockfile if it was created by us. */
- retval = remove_lockfile(name);
- if (retval) {
- return retval;
- }
-
- /* Create file. */
- old_umask = umask(022);
- fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0) {
- int error = errno;
- VLOG_ERR("%s: create: %s", name, strerror(error));
- umask(old_umask);
- return -error;
- }
- umask(old_umask);
-
- /* Lock file. */
- if (fcntl_lock(fd)) {
- int error = errno;
- close(fd);
- VLOG_ERR("%s: cannot lock: %s", name, strerror(error));
- return -error;
- }
-
- /* Write to file. */
- uid = getuid();
- pwd = getpwuid(uid);
- username = pwd ? pwd->pw_name : "unknown";
- snprintf(buffer, sizeof buffer, "%10ld %s %.20s fcntl\n",
- (long int) getpid(), program_name, username);
- if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
- int error = errno;
- VLOG_ERR("%s: write: %s", name, strerror(error));
- close(fd);
- unlink(name);
- return -error;
- }
-
- return fd;
-}
+++ /dev/null
-/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
- * Junior University
- *
- * We are making the OpenFlow specification and associated documentation
- * (Software) available for public use and benefit with the expectation
- * that others will use, modify and enhance the Software and contribute
- * those enhancements back to the community. However, since we would
- * like to make the Software available for broadest use, with as few
- * restrictions as possible permission is hereby granted, free of
- * charge, to any person obtaining a copy of this Software to deal in
- * the Software under the copyrights without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- *
- * The name and trademarks of copyright holder(s) may NOT be used in
- * advertising or publicity pertaining to the Software or any
- * derivatives without specific, written prior permission.
- */
-
-#ifndef LOCKFILE_H
-#define LOCKFILE_H 1
-
-int create_lockfile(const char *name);
-int remove_lockfile(const char *name);
-
-#endif /* lockfile.h */