1/*	$NetBSD: file_locking.c,v 1.2 2011/01/05 14:57:28 haad Exp $	*/
2
3/*
4 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
5 * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
6 *
7 * This file is part of LVM2.
8 *
9 * This copyrighted material is made available to anyone wishing to use,
10 * modify, copy, or redistribute it subject to the terms and conditions
11 * of the GNU Lesser General Public License v.2.1.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software Foundation,
15 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 */
17
18#include "lib.h"
19#include "locking.h"
20#include "locking_types.h"
21#include "activate.h"
22#include "config.h"
23#include "defaults.h"
24#include "lvm-file.h"
25#include "lvm-string.h"
26#include "lvmcache.h"
27
28#include <limits.h>
29#include <unistd.h>
30#include <sys/stat.h>
31#include <sys/file.h>
32#include <fcntl.h>
33#include <signal.h>
34
35struct lock_list {
36	struct dm_list list;
37	int lf;
38	char *res;
39};
40
41static struct dm_list _lock_list;
42static char _lock_dir[NAME_LEN];
43static int _prioritise_write_locks;
44
45static sig_t _oldhandler;
46static sigset_t _fullsigset, _intsigset;
47static volatile sig_atomic_t _handler_installed;
48
49static void _undo_flock(const char *file, int fd)
50{
51	struct stat buf1, buf2;
52
53	log_debug("_undo_flock %s", file);
54	if (!flock(fd, LOCK_NB | LOCK_EX) &&
55	    !stat(file, &buf1) &&
56	    !fstat(fd, &buf2) &&
57	    is_same_inode(buf1, buf2))
58		if (unlink(file))
59			log_sys_error("unlink", file);
60
61	if (close(fd) < 0)
62		log_sys_error("close", file);
63}
64
65static int _release_lock(const char *file, int unlock)
66{
67	struct lock_list *ll;
68	struct dm_list *llh, *llt;
69
70	dm_list_iterate_safe(llh, llt, &_lock_list) {
71		ll = dm_list_item(llh, struct lock_list);
72
73		if (!file || !strcmp(ll->res, file)) {
74			dm_list_del(llh);
75			if (unlock) {
76				log_very_verbose("Unlocking %s", ll->res);
77				if (flock(ll->lf, LOCK_NB | LOCK_UN))
78					log_sys_error("flock", ll->res);
79			}
80
81			_undo_flock(ll->res, ll->lf);
82
83			dm_free(ll->res);
84			dm_free(llh);
85
86			if (file)
87				return 1;
88		}
89	}
90
91	return 0;
92}
93
94static void _fin_file_locking(void)
95{
96	_release_lock(NULL, 1);
97}
98
99static void _reset_file_locking(void)
100{
101	_release_lock(NULL, 0);
102}
103
104static void _remove_ctrl_c_handler(void)
105{
106	siginterrupt(SIGINT, 0);
107	if (!_handler_installed)
108		return;
109
110	_handler_installed = 0;
111
112	sigprocmask(SIG_SETMASK, &_fullsigset, NULL);
113	if (signal(SIGINT, _oldhandler) == SIG_ERR)
114		log_sys_error("signal", "_remove_ctrl_c_handler");
115}
116
117static void _trap_ctrl_c(int sig __attribute((unused)))
118{
119	_remove_ctrl_c_handler();
120	log_error("CTRL-c detected: giving up waiting for lock");
121}
122
123static void _install_ctrl_c_handler()
124{
125	_handler_installed = 1;
126
127	if ((_oldhandler = signal(SIGINT, _trap_ctrl_c)) == SIG_ERR) {
128		_handler_installed = 0;
129		return;
130	}
131
132	sigprocmask(SIG_SETMASK, &_intsigset, NULL);
133	siginterrupt(SIGINT, 1);
134}
135
136static int _do_flock(const char *file, int *fd, int operation, uint32_t nonblock)
137{
138	int r = 1;
139	int old_errno;
140	struct stat buf1, buf2;
141
142	log_debug("_do_flock %s %c%c",
143		  file, operation == LOCK_EX ? 'W' : 'R', nonblock ? ' ' : 'B');
144	do {
145		if ((*fd > -1) && close(*fd))
146			log_sys_error("close", file);
147
148		if ((*fd = open(file, O_CREAT | O_APPEND | O_RDWR, 0777)) < 0) {
149			log_sys_error("open", file);
150			return 0;
151		}
152
153		if (nonblock)
154			operation |= LOCK_NB;
155		else
156			_install_ctrl_c_handler();
157
158		r = flock(*fd, operation);
159		old_errno = errno;
160		if (!nonblock)
161			_remove_ctrl_c_handler();
162
163		if (r) {
164			errno = old_errno;
165			log_sys_error("flock", file);
166			close(*fd);
167			return 0;
168		}
169
170		if (!stat(file, &buf1) && !fstat(*fd, &buf2) &&
171		    is_same_inode(buf1, buf2))
172			return 1;
173	} while (!nonblock);
174
175	return_0;
176}
177
178#define AUX_LOCK_SUFFIX ":aux"
179
180static int _do_write_priority_flock(const char *file, int *fd, int operation, uint32_t nonblock)
181{
182	int r, fd_aux = -1;
183	char *file_aux = alloca(strlen(file) + sizeof(AUX_LOCK_SUFFIX));
184
185	strcpy(file_aux, file);
186	strcat(file_aux, AUX_LOCK_SUFFIX);
187
188	if ((r = _do_flock(file_aux, &fd_aux, LOCK_EX, 0))) {
189		if (operation == LOCK_EX) {
190			r = _do_flock(file, fd, operation, nonblock);
191			_undo_flock(file_aux, fd_aux);
192		} else {
193			_undo_flock(file_aux, fd_aux);
194			r = _do_flock(file, fd, operation, nonblock);
195		}
196	}
197
198	return r;
199}
200
201static int _lock_file(const char *file, uint32_t flags)
202{
203	int operation;
204	uint32_t nonblock = flags & LCK_NONBLOCK;
205	int r;
206
207	struct lock_list *ll;
208	char state;
209
210	switch (flags & LCK_TYPE_MASK) {
211	case LCK_READ:
212		operation = LOCK_SH;
213		state = 'R';
214		break;
215	case LCK_WRITE:
216		operation = LOCK_EX;
217		state = 'W';
218		break;
219	case LCK_UNLOCK:
220		return _release_lock(file, 1);
221	default:
222		log_error("Unrecognised lock type: %d", flags & LCK_TYPE_MASK);
223		return 0;
224	}
225
226	if (!(ll = dm_malloc(sizeof(struct lock_list))))
227		return_0;
228
229	if (!(ll->res = dm_strdup(file))) {
230		dm_free(ll);
231		return_0;
232	}
233
234	ll->lf = -1;
235
236	log_very_verbose("Locking %s %c%c", ll->res, state,
237			 nonblock ? ' ' : 'B');
238
239	if (_prioritise_write_locks)
240		r = _do_write_priority_flock(file, &ll->lf, operation, nonblock);
241	else
242		r = _do_flock(file, &ll->lf, operation, nonblock);
243
244	if (r)
245		dm_list_add(&_lock_list, &ll->list);
246	else {
247		dm_free(ll->res);
248		dm_free(ll);
249		stack;
250	}
251
252	return r;
253}
254
255static int _file_lock_resource(struct cmd_context *cmd, const char *resource,
256			       uint32_t flags)
257{
258	char lockfile[PATH_MAX];
259
260	switch (flags & LCK_SCOPE_MASK) {
261	case LCK_VG:
262		/* Skip cache refresh for VG_GLOBAL - the caller handles it */
263		if (strcmp(resource, VG_GLOBAL))
264			lvmcache_drop_metadata(resource);
265
266		/* LCK_CACHE does not require a real lock */
267		if (flags & LCK_CACHE)
268			break;
269
270		if (*resource == '#')
271			dm_snprintf(lockfile, sizeof(lockfile),
272				     "%s/P_%s", _lock_dir, resource + 1);
273		else
274			dm_snprintf(lockfile, sizeof(lockfile),
275				     "%s/V_%s", _lock_dir, resource);
276
277		if (!_lock_file(lockfile, flags))
278			return_0;
279		break;
280	case LCK_LV:
281		switch (flags & LCK_TYPE_MASK) {
282		case LCK_UNLOCK:
283			log_very_verbose("Unlocking LV %s", resource);
284			if (!lv_resume_if_active(cmd, resource))
285				return 0;
286			break;
287		case LCK_NULL:
288			log_very_verbose("Locking LV %s (NL)", resource);
289			if (!lv_deactivate(cmd, resource))
290				return 0;
291			break;
292		case LCK_READ:
293			log_very_verbose("Locking LV %s (R)", resource);
294			if (!lv_activate_with_filter(cmd, resource, 0))
295				return 0;
296			break;
297		case LCK_PREAD:
298			log_very_verbose("Locking LV %s (PR) - ignored", resource);
299			break;
300		case LCK_WRITE:
301			log_very_verbose("Locking LV %s (W)", resource);
302			if (!lv_suspend_if_active(cmd, resource))
303				return 0;
304			break;
305		case LCK_EXCL:
306			log_very_verbose("Locking LV %s (EX)", resource);
307			if (!lv_activate_with_filter(cmd, resource, 1))
308				return 0;
309			break;
310		default:
311			break;
312		}
313		break;
314	default:
315		log_error("Unrecognised lock scope: %d",
316			  flags & LCK_SCOPE_MASK);
317		return 0;
318	}
319
320	return 1;
321}
322
323int init_file_locking(struct locking_type *locking, struct cmd_context *cmd)
324{
325	mode_t old_umask;
326
327	locking->lock_resource = _file_lock_resource;
328	locking->reset_locking = _reset_file_locking;
329	locking->fin_locking = _fin_file_locking;
330	locking->flags = 0;
331
332	/* Get lockfile directory from config file */
333	strncpy(_lock_dir, find_config_tree_str(cmd, "global/locking_dir",
334						DEFAULT_LOCK_DIR),
335		sizeof(_lock_dir));
336
337	_prioritise_write_locks =
338	    find_config_tree_bool(cmd, "global/prioritise_write_locks",
339				  DEFAULT_PRIORITISE_WRITE_LOCKS);
340	old_umask = umask(LVM_LOCKDIR_MODE);
341	if (!dm_create_dir(_lock_dir)){
342		umask(old_umask);
343		return 0;
344	} else {
345		/* Change lockfile directory owner to match with others */
346		if (chown(_lock_dir, DM_DEVICE_UID, DM_DEVICE_GID) == -1) {
347			if (errno == EPERM)
348				goto next;
349			log_sys_error("chown", _lock_dir);
350			return 0;
351		}
352	}
353
354next:
355	umask(old_umask);
356
357	/* Trap a read-only file system */
358	if ((access(_lock_dir, R_OK | W_OK | X_OK) == -1) && (errno == EROFS))
359		return 0;
360
361	dm_list_init(&_lock_list);
362
363	if (sigfillset(&_intsigset) || sigfillset(&_fullsigset)) {
364		log_sys_error("sigfillset", "init_file_locking");
365		return 0;
366	}
367
368	if (sigdelset(&_intsigset, SIGINT)) {
369		log_sys_error("sigdelset", "init_file_locking");
370		return 0;
371	}
372
373	return 1;
374}
375