summaryrefslogtreecommitdiff
path: root/lib/misc/lvm-flock.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/misc/lvm-flock.c')
-rw-r--r--lib/misc/lvm-flock.c256
1 files changed, 256 insertions, 0 deletions
diff --git a/lib/misc/lvm-flock.c b/lib/misc/lvm-flock.c
new file mode 100644
index 0000000..d48ff22
--- /dev/null
+++ b/lib/misc/lvm-flock.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
+ * Copyright (C) 2004-2014 Red Hat, Inc. All rights reserved.
+ *
+ * This file is part of LVM2.
+ *
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU Lesser General Public License v.2.1.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "lib/misc/lib.h"
+#include "lib/config/config.h"
+#include "lib/misc/lvm-flock.h"
+#include "lib/misc/lvm-signal.h"
+#include "lib/locking/locking.h"
+
+#include <sys/file.h>
+#include <fcntl.h>
+
+struct lock_list {
+ struct dm_list list;
+ int lf;
+ char *res;
+};
+
+static struct dm_list _lock_list;
+static int _prioritise_write_locks;
+
+/* Drop lock known to be shared with another file descriptor. */
+static void _drop_shared_flock(const char *file, int fd)
+{
+ log_debug_locking("_drop_shared_flock %s.", file);
+
+ if (close(fd) < 0)
+ log_sys_debug("close", file);
+}
+
+static void _undo_flock(const char *file, int fd)
+{
+ struct stat buf1, buf2;
+
+ log_debug_locking("_undo_flock %s", file);
+ if (!flock(fd, LOCK_NB | LOCK_EX) &&
+ !stat(file, &buf1) &&
+ !fstat(fd, &buf2) &&
+ is_same_inode(buf1, buf2))
+ if (unlink(file))
+ log_sys_debug("unlink", file);
+
+ if (close(fd) < 0)
+ log_sys_debug("close", file);
+}
+
+static struct lock_list *_get_lock_list_entry(const char *file)
+{
+ struct lock_list *ll;
+ struct dm_list *llh;
+
+ dm_list_iterate(llh, &_lock_list) {
+ ll = dm_list_item(llh, struct lock_list);
+
+ if (!strcmp(ll->res, file))
+ return ll;
+ }
+ return NULL;
+}
+
+static int _release_lock(const char *file, int unlock)
+{
+ struct lock_list *ll;
+ struct dm_list *llh, *llt;
+
+ dm_list_iterate_safe(llh, llt, &_lock_list) {
+ ll = dm_list_item(llh, struct lock_list);
+
+ if (!file || !strcmp(ll->res, file)) {
+ dm_list_del(llh);
+ if (unlock) {
+ log_very_verbose("Unlocking %s", ll->res);
+ if (flock(ll->lf, LOCK_NB | LOCK_UN))
+ log_sys_debug("flock", ll->res);
+ _undo_flock(ll->res, ll->lf);
+ } else
+ _drop_shared_flock(ll->res, ll->lf);
+
+ free(ll->res);
+ free(llh);
+
+ if (file)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void release_flocks(int unlock)
+{
+ _release_lock(NULL, unlock);
+}
+
+static int _do_flock(const char *file, int *fd, int operation, uint32_t nonblock)
+{
+ int r;
+ int old_errno;
+ struct stat buf1, buf2;
+
+ log_debug_locking("_do_flock %s %c%c", file,
+ operation == LOCK_EX ? 'W' : 'R', nonblock ? ' ' : 'B');
+ do {
+ if ((*fd > -1) && close(*fd))
+ log_sys_debug("close", file);
+
+ if ((*fd = open(file, O_CREAT | O_APPEND | O_RDWR, 0777)) < 0) {
+ log_sys_error("open", file);
+ return 0;
+ }
+
+ if (nonblock)
+ operation |= LOCK_NB;
+ else
+ sigint_allow();
+
+ r = flock(*fd, operation);
+ old_errno = errno;
+ if (!nonblock) {
+ sigint_restore();
+ if (sigint_caught()) {
+ log_error("Giving up waiting for lock.");
+ break;
+ }
+ }
+
+ if (r) {
+ errno = old_errno;
+ log_sys_error("flock", file);
+ break;
+ }
+
+ if (!stat(file, &buf1) && !fstat(*fd, &buf2) &&
+ is_same_inode(buf1, buf2))
+ return 1;
+ } while (!nonblock);
+
+ if (close(*fd))
+ log_sys_debug("close", file);
+ *fd = -1;
+
+ return_0;
+}
+
+#define AUX_LOCK_SUFFIX ":aux"
+
+static int _do_write_priority_flock(const char *file, int *fd, int operation, uint32_t nonblock)
+{
+ int r, fd_aux = -1;
+ char *file_aux = alloca(strlen(file) + sizeof(AUX_LOCK_SUFFIX));
+
+ strcpy(file_aux, file);
+ strcat(file_aux, AUX_LOCK_SUFFIX);
+
+ if ((r = _do_flock(file_aux, &fd_aux, LOCK_EX, nonblock))) {
+ if (operation == LOCK_EX) {
+ r = _do_flock(file, fd, operation, nonblock);
+ _undo_flock(file_aux, fd_aux);
+ } else {
+ _undo_flock(file_aux, fd_aux);
+ r = _do_flock(file, fd, operation, nonblock);
+ }
+ }
+
+ return r;
+}
+
+int lock_file(const char *file, uint32_t flags)
+{
+ int operation;
+ uint32_t nonblock = flags & LCK_NONBLOCK;
+ uint32_t convert = flags & LCK_CONVERT;
+ int r;
+ struct lock_list *ll;
+ char state;
+
+ switch (flags & LCK_TYPE_MASK) {
+ case LCK_READ:
+ operation = LOCK_SH;
+ state = 'R';
+ break;
+ case LCK_WRITE:
+ operation = LOCK_EX;
+ state = 'W';
+ break;
+ case LCK_UNLOCK:
+ return _release_lock(file, 1);
+ default:
+ log_error("Unrecognised lock type: %d", flags & LCK_TYPE_MASK);
+ return 0;
+ }
+
+ if (convert) {
+ if (nonblock)
+ operation |= LOCK_NB;
+ if (!(ll = _get_lock_list_entry(file)))
+ return 0;
+ log_very_verbose("Locking %s %c%c convert", ll->res, state,
+ nonblock ? ' ' : 'B');
+ r = flock(ll->lf, operation);
+ if (!r)
+ return 1;
+ log_error("Failed to convert flock on %s %d", file, errno);
+ return 0;
+ }
+
+ if (!(ll = malloc(sizeof(struct lock_list))))
+ return_0;
+
+ if (!(ll->res = strdup(file))) {
+ free(ll);
+ return_0;
+ }
+
+ ll->lf = -1;
+
+ log_very_verbose("Locking %s %c%c", ll->res, state,
+ nonblock ? ' ' : 'B');
+
+ (void) dm_prepare_selinux_context(file, S_IFREG);
+ if (_prioritise_write_locks)
+ r = _do_write_priority_flock(file, &ll->lf, operation, nonblock);
+ else
+ r = _do_flock(file, &ll->lf, operation, nonblock);
+ (void) dm_prepare_selinux_context(NULL, 0);
+
+ if (r)
+ dm_list_add(&_lock_list, &ll->list);
+ else {
+ free(ll->res);
+ free(ll);
+ stack;
+ }
+
+ return r;
+}
+
+void init_flock(struct cmd_context *cmd)
+{
+ dm_list_init(&_lock_list);
+
+ _prioritise_write_locks =
+ find_config_tree_bool(cmd, global_prioritise_write_locks_CFG, NULL);
+}