mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
387 lines
8.8 KiB
C
387 lines
8.8 KiB
C
/*
|
|
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
|
|
* Copyright (C) 2004-2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "lib.h"
|
|
#include "locking.h"
|
|
#include "locking_types.h"
|
|
#include "activate.h"
|
|
#include "config.h"
|
|
#include "defaults.h"
|
|
#include "lvm-file.h"
|
|
#include "lvm-string.h"
|
|
#include "lvmcache.h"
|
|
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/file.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
|
|
struct lock_list {
|
|
struct dm_list list;
|
|
int lf;
|
|
char *res;
|
|
};
|
|
|
|
static struct dm_list _lock_list;
|
|
static char _lock_dir[NAME_LEN];
|
|
static int _prioritise_write_locks;
|
|
|
|
static sig_t _oldhandler;
|
|
static sigset_t _fullsigset, _intsigset;
|
|
static volatile sig_atomic_t _handler_installed;
|
|
|
|
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_error("unlink", file);
|
|
|
|
if (close(fd) < 0)
|
|
log_sys_error("close", file);
|
|
}
|
|
|
|
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_error("flock", ll->res);
|
|
}
|
|
|
|
_undo_flock(ll->res, ll->lf);
|
|
|
|
dm_free(ll->res);
|
|
dm_free(llh);
|
|
|
|
if (file)
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _fin_file_locking(void)
|
|
{
|
|
_release_lock(NULL, 1);
|
|
}
|
|
|
|
static void _reset_file_locking(void)
|
|
{
|
|
_release_lock(NULL, 0);
|
|
}
|
|
|
|
static void _remove_ctrl_c_handler(void)
|
|
{
|
|
siginterrupt(SIGINT, 0);
|
|
if (!_handler_installed)
|
|
return;
|
|
|
|
_handler_installed = 0;
|
|
|
|
sigprocmask(SIG_SETMASK, &_fullsigset, NULL);
|
|
if (signal(SIGINT, _oldhandler) == SIG_ERR)
|
|
log_sys_error("signal", "_remove_ctrl_c_handler");
|
|
}
|
|
|
|
static void _trap_ctrl_c(int sig __attribute__((unused)))
|
|
{
|
|
_remove_ctrl_c_handler();
|
|
log_error("CTRL-c detected: giving up waiting for lock");
|
|
}
|
|
|
|
static void _install_ctrl_c_handler(void)
|
|
{
|
|
_handler_installed = 1;
|
|
|
|
if ((_oldhandler = signal(SIGINT, _trap_ctrl_c)) == SIG_ERR) {
|
|
_handler_installed = 0;
|
|
return;
|
|
}
|
|
|
|
sigprocmask(SIG_SETMASK, &_intsigset, NULL);
|
|
siginterrupt(SIGINT, 1);
|
|
}
|
|
|
|
static int _do_flock(const char *file, int *fd, int operation, uint32_t nonblock)
|
|
{
|
|
int r = 1;
|
|
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_error("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
|
|
_install_ctrl_c_handler();
|
|
|
|
r = flock(*fd, operation);
|
|
old_errno = errno;
|
|
if (!nonblock)
|
|
_remove_ctrl_c_handler();
|
|
|
|
if (r) {
|
|
errno = old_errno;
|
|
log_sys_error("flock", file);
|
|
if (close(*fd))
|
|
log_sys_error("close", file);
|
|
return 0;
|
|
}
|
|
|
|
if (!stat(file, &buf1) && !fstat(*fd, &buf2) &&
|
|
is_same_inode(buf1, buf2))
|
|
return 1;
|
|
} while (!nonblock);
|
|
|
|
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, 0))) {
|
|
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;
|
|
}
|
|
|
|
static int _lock_file(const char *file, uint32_t flags)
|
|
{
|
|
int operation;
|
|
uint32_t nonblock = flags & LCK_NONBLOCK;
|
|
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 (!(ll = dm_malloc(sizeof(struct lock_list))))
|
|
return_0;
|
|
|
|
if (!(ll->res = dm_strdup(file))) {
|
|
dm_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 {
|
|
dm_free(ll->res);
|
|
dm_free(ll);
|
|
stack;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int _file_lock_resource(struct cmd_context *cmd, const char *resource,
|
|
uint32_t flags, struct logical_volume *lv)
|
|
{
|
|
char lockfile[PATH_MAX];
|
|
unsigned origin_only = (flags & LCK_ORIGIN_ONLY) ? 1 : 0;
|
|
unsigned revert = (flags & LCK_REVERT) ? 1 : 0;
|
|
|
|
switch (flags & LCK_SCOPE_MASK) {
|
|
case LCK_VG:
|
|
/* Skip cache refresh for VG_GLOBAL - the caller handles it */
|
|
if (strcmp(resource, VG_GLOBAL))
|
|
lvmcache_drop_metadata(resource, 0);
|
|
|
|
if (!strcmp(resource, VG_SYNC_NAMES))
|
|
fs_unlock();
|
|
|
|
/* LCK_CACHE does not require a real lock */
|
|
if (flags & LCK_CACHE)
|
|
break;
|
|
|
|
if (is_orphan_vg(resource) || is_global_vg(resource)) {
|
|
if (dm_snprintf(lockfile, sizeof(lockfile),
|
|
"%s/P_%s", _lock_dir, resource + 1) < 0) {
|
|
log_error("Too long locking filename %s/P_%s.",
|
|
_lock_dir, resource + 1);
|
|
return 0;
|
|
}
|
|
} else
|
|
if (dm_snprintf(lockfile, sizeof(lockfile),
|
|
"%s/V_%s", _lock_dir, resource) < 0) {
|
|
log_error("Too long locking filename %s/V_%s.",
|
|
_lock_dir, resource);
|
|
return 0;
|
|
}
|
|
|
|
if (!_lock_file(lockfile, flags))
|
|
return_0;
|
|
break;
|
|
case LCK_LV:
|
|
switch (flags & LCK_TYPE_MASK) {
|
|
case LCK_UNLOCK:
|
|
log_very_verbose("Unlocking LV %s%s%s", resource, origin_only ? " without snapshots" : "", revert ? " (reverting)" : "");
|
|
if (!lv_resume_if_active(cmd, resource, origin_only, 0, revert, lv_ondisk(lv)))
|
|
return 0;
|
|
break;
|
|
case LCK_NULL:
|
|
log_very_verbose("Locking LV %s (NL)", resource);
|
|
if (!lv_deactivate(cmd, resource, lv_ondisk(lv)))
|
|
return 0;
|
|
break;
|
|
case LCK_READ:
|
|
log_very_verbose("Locking LV %s (R)", resource);
|
|
if (!lv_activate_with_filter(cmd, resource, 0, lv_ondisk(lv)))
|
|
return 0;
|
|
break;
|
|
case LCK_PREAD:
|
|
log_very_verbose("Locking LV %s (PR) - ignored", resource);
|
|
break;
|
|
case LCK_WRITE:
|
|
log_very_verbose("Locking LV %s (W)%s", resource, origin_only ? " without snapshots" : "");
|
|
if (!lv_suspend_if_active(cmd, resource, origin_only, 0, lv_ondisk(lv), lv))
|
|
return 0;
|
|
break;
|
|
case LCK_EXCL:
|
|
log_very_verbose("Locking LV %s (EX)", resource);
|
|
if (!lv_activate_with_filter(cmd, resource, 1, lv_ondisk(lv)))
|
|
return 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
log_error("Unrecognised lock scope: %d",
|
|
flags & LCK_SCOPE_MASK);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int init_file_locking(struct locking_type *locking, struct cmd_context *cmd,
|
|
int suppress_messages)
|
|
{
|
|
int r;
|
|
const char *locking_dir;
|
|
|
|
locking->lock_resource = _file_lock_resource;
|
|
locking->reset_locking = _reset_file_locking;
|
|
locking->fin_locking = _fin_file_locking;
|
|
locking->flags = 0;
|
|
|
|
/* Get lockfile directory from config file */
|
|
locking_dir = find_config_tree_str(cmd, global_locking_dir_CFG);
|
|
if (strlen(locking_dir) >= sizeof(_lock_dir)) {
|
|
log_error("Path for locking_dir %s is invalid.", locking_dir);
|
|
return 0;
|
|
}
|
|
|
|
strcpy(_lock_dir, locking_dir);
|
|
|
|
_prioritise_write_locks =
|
|
find_config_tree_bool(cmd, global_prioritise_write_locks_CFG);
|
|
|
|
(void) dm_prepare_selinux_context(_lock_dir, S_IFDIR);
|
|
r = dm_create_dir(_lock_dir);
|
|
(void) dm_prepare_selinux_context(NULL, 0);
|
|
|
|
if (!r)
|
|
return 0;
|
|
|
|
/* Trap a read-only file system */
|
|
if ((access(_lock_dir, R_OK | W_OK | X_OK) == -1) && (errno == EROFS))
|
|
return 0;
|
|
|
|
dm_list_init(&_lock_list);
|
|
|
|
if (sigfillset(&_intsigset) || sigfillset(&_fullsigset)) {
|
|
log_sys_error_suppress(suppress_messages, "sigfillset",
|
|
"init_file_locking");
|
|
return 0;
|
|
}
|
|
|
|
if (sigdelset(&_intsigset, SIGINT)) {
|
|
log_sys_error_suppress(suppress_messages, "sigdelset",
|
|
"init_file_locking");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|