/* * 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 #include 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) && (errno != ENOENT)) 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[PATH_MAX]; snprintf(file_aux, sizeof(file_aux), "%s%s", file, 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); }