1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "istream.h"
5 #include "file-lock.h"
6 #include "file-dotlock.h"
7 #include "time-util.h"
8 
9 #include <time.h>
10 #include <sys/stat.h>
11 #ifdef HAVE_FLOCK
12 #  include <sys/file.h>
13 #endif
14 
15 struct file_lock {
16 	struct file_lock_settings set;
17 
18 	int fd;
19 	char *path;
20 	struct dotlock *dotlock;
21 
22 	struct timeval locked_time;
23 	int lock_type;
24 };
25 
26 static struct timeval lock_wait_start;
27 static uint64_t file_lock_wait_usecs = 0;
28 static long long file_lock_slow_warning_usecs = -1;
29 
30 static void file_lock_log_warning_if_slow(struct file_lock *lock);
31 
file_lock_method_parse(const char * name,enum file_lock_method * method_r)32 bool file_lock_method_parse(const char *name, enum file_lock_method *method_r)
33 {
34 	if (strcasecmp(name, "fcntl") == 0)
35 		*method_r = FILE_LOCK_METHOD_FCNTL;
36 	else if (strcasecmp(name, "flock") == 0)
37 		*method_r = FILE_LOCK_METHOD_FLOCK;
38 	else if (strcasecmp(name, "dotlock") == 0)
39 		*method_r = FILE_LOCK_METHOD_DOTLOCK;
40 	else
41 		return FALSE;
42 	return TRUE;
43 }
44 
file_lock_method_to_str(enum file_lock_method method)45 const char *file_lock_method_to_str(enum file_lock_method method)
46 {
47 	switch (method) {
48 	case FILE_LOCK_METHOD_FCNTL:
49 		return "fcntl";
50 	case FILE_LOCK_METHOD_FLOCK:
51 		return "flock";
52 	case FILE_LOCK_METHOD_DOTLOCK:
53 		return "dotlock";
54 	}
55 	i_unreached();
56 }
57 
file_try_lock(int fd,const char * path,int lock_type,const struct file_lock_settings * set,struct file_lock ** lock_r,const char ** error_r)58 int file_try_lock(int fd, const char *path, int lock_type,
59 		  const struct file_lock_settings *set,
60 		  struct file_lock **lock_r, const char **error_r)
61 {
62 	return file_wait_lock(fd, path, lock_type, set, 0, lock_r, error_r);
63 }
64 
65 static const char *
file_lock_find_fcntl(int lock_fd,int lock_type)66 file_lock_find_fcntl(int lock_fd, int lock_type)
67 {
68 	struct flock fl;
69 
70 	i_zero(&fl);
71 	fl.l_type = lock_type;
72 	fl.l_whence = SEEK_SET;
73 	fl.l_start = 0;
74 	fl.l_len = 0;
75 
76 	if (fcntl(lock_fd, F_GETLK, &fl) < 0 ||
77 	    fl.l_type == F_UNLCK || fl.l_pid == -1 || fl.l_pid == 0)
78 		return "";
79 	return t_strdup_printf(" (%s lock held by pid %ld)",
80 		fl.l_type == F_RDLCK ? "READ" : "WRITE", (long)fl.l_pid);
81 }
82 
83 static const char *
file_lock_find_proc_locks(int lock_fd ATTR_UNUSED)84 file_lock_find_proc_locks(int lock_fd ATTR_UNUSED)
85 {
86 	/* do anything except Linux support this? don't bother trying it for
87 	   OSes we don't know about. */
88 #ifdef __linux__
89 	static bool have_proc_locks = TRUE;
90 	struct stat st;
91 	char node_buf[MAX_INT_STRLEN * 3 + 2];
92 	struct istream *input;
93 	const char *line, *lock_type = "";
94 	pid_t pid = 0;
95 	int fd;
96 
97 	if (!have_proc_locks)
98 		return NULL;
99 
100 	if (fstat(lock_fd, &st) < 0)
101 		return "";
102 	i_snprintf(node_buf, sizeof(node_buf), "%02x:%02x:%llu",
103 		   major(st.st_dev), minor(st.st_dev),
104 		   (unsigned long long)st.st_ino);
105 	fd = open("/proc/locks", O_RDONLY);
106 	if (fd == -1) {
107 		have_proc_locks = FALSE;
108 		return "";
109 	}
110 	input = i_stream_create_fd_autoclose(&fd, 512);
111 	while (pid == 0 && (line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
112 		const char *const *args = t_strsplit_spaces(line, " ");
113 
114 		/* number: FLOCK/POSIX ADVISORY READ/WRITE pid
115 		   major:minor:inode region-start region-end */
116 		if (str_array_length(args) < 8) {
117 			; /* don't continue from within a T_BEGIN {...} T_END */
118 		} else if (strcmp(args[5], node_buf) == 0) {
119 			lock_type = strcmp(args[3], "READ") == 0 ?
120 				"READ" : "WRITE";
121 			if (str_to_pid(args[4], &pid) < 0)
122 				pid = 0;
123 		}
124 	} T_END;
125 	i_stream_destroy(&input);
126 	if (pid == 0) {
127 		/* not found */
128 		return "";
129 	}
130 	if (pid == getpid())
131 		return " (BUG: lock is held by our own process)";
132 	return t_strdup_printf(" (%s lock held by pid %ld)", lock_type, (long)pid);
133 #else
134 	return "";
135 #endif
136 }
137 
file_lock_find(int lock_fd,enum file_lock_method lock_method,int lock_type)138 const char *file_lock_find(int lock_fd, enum file_lock_method lock_method,
139 			   int lock_type)
140 {
141 	const char *ret;
142 
143 	if (lock_method == FILE_LOCK_METHOD_FCNTL) {
144 		ret = file_lock_find_fcntl(lock_fd, lock_type);
145 		if (ret[0] != '\0')
146 			return ret;
147 	}
148 	return file_lock_find_proc_locks(lock_fd);
149 }
150 
err_is_lock_timeout(time_t started,unsigned int timeout_secs)151 static bool err_is_lock_timeout(time_t started, unsigned int timeout_secs)
152 {
153 	/* if EINTR took at least timeout_secs-1 number of seconds,
154 	   assume it was the alarm. otherwise log EINTR failure.
155 	   (We most likely don't want to retry EINTR since a signal
156 	   means somebody wants us to stop blocking). */
157 	return errno == EINTR &&
158 		(unsigned long)(time(NULL) - started + 1) >= timeout_secs;
159 }
160 
file_lock_do(int fd,const char * path,int lock_type,const struct file_lock_settings * set,unsigned int timeout_secs,const char ** error_r)161 static int file_lock_do(int fd, const char *path, int lock_type,
162 			const struct file_lock_settings *set,
163 			unsigned int timeout_secs, const char **error_r)
164 {
165 	const char *lock_type_str;
166 	time_t started = time(NULL);
167 	int ret;
168 
169 	i_assert(fd != -1);
170 
171 	if (timeout_secs != 0) {
172 		alarm(timeout_secs);
173 		file_lock_wait_start();
174 	}
175 
176 	lock_type_str = lock_type == F_UNLCK ? "unlock" :
177 		(lock_type == F_RDLCK ? "read-lock" : "write-lock");
178 
179 	switch (set->lock_method) {
180 	case FILE_LOCK_METHOD_FCNTL: {
181 #ifndef HAVE_FCNTL
182 		*error_r = t_strdup_printf(
183 			"Can't lock file %s: fcntl() locks not supported", path);
184 		return -1;
185 #else
186 		struct flock fl;
187 
188 		fl.l_type = lock_type;
189 		fl.l_whence = SEEK_SET;
190 		fl.l_start = 0;
191 		fl.l_len = 0;
192 
193 		ret = fcntl(fd, timeout_secs != 0 ? F_SETLKW : F_SETLK, &fl);
194 		if (timeout_secs != 0) {
195 			alarm(0);
196 			file_lock_wait_end(path);
197 		}
198 
199 		if (ret == 0)
200 			break;
201 
202 		if (timeout_secs == 0 &&
203 		    (errno == EACCES || errno == EAGAIN)) {
204 			/* locked by another process */
205 			*error_r = t_strdup_printf(
206 				"fcntl(%s, %s, F_SETLK) locking failed: %m "
207 				"(File is already locked)", path, lock_type_str);
208 			return 0;
209 		}
210 
211 		if (err_is_lock_timeout(started, timeout_secs)) {
212 			errno = EAGAIN;
213 			*error_r = t_strdup_printf(
214 				"fcntl(%s, %s, F_SETLKW) locking failed: "
215 				"Timed out after %u seconds%s",
216 				path, lock_type_str, timeout_secs,
217 				file_lock_find(fd, set->lock_method,
218 					       lock_type));
219 			return 0;
220 		}
221 		*error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m",
222 			path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_SETLKW");
223 		if (errno == EDEADLK && !set->allow_deadlock) {
224 			i_panic("%s%s", *error_r,
225 				file_lock_find(fd, set->lock_method,
226 					       lock_type));
227 		}
228 		return -1;
229 #endif
230 	}
231 	case FILE_LOCK_METHOD_FLOCK: {
232 #ifndef HAVE_FLOCK
233 		*error_r = t_strdup_printf(
234 			"Can't lock file %s: flock() not supported", path);
235 		return -1;
236 #else
237 		int operation = timeout_secs != 0 ? 0 : LOCK_NB;
238 
239 		switch (lock_type) {
240 		case F_RDLCK:
241 			operation |= LOCK_SH;
242 			break;
243 		case F_WRLCK:
244 			operation |= LOCK_EX;
245 			break;
246 		case F_UNLCK:
247 			operation |= LOCK_UN;
248 			break;
249 		}
250 
251 		ret = flock(fd, operation);
252 		if (timeout_secs != 0) {
253 			alarm(0);
254 			file_lock_wait_end(path);
255 		}
256 
257 		if (ret == 0)
258 			break;
259 
260 		if (timeout_secs == 0 && errno == EWOULDBLOCK) {
261 			/* locked by another process */
262 			*error_r = t_strdup_printf(
263 				"flock(%s, %s) failed: %m "
264 				"(File is already locked)", path, lock_type_str);
265 			return 0;
266 		}
267 		if (err_is_lock_timeout(started, timeout_secs)) {
268 			errno = EAGAIN;
269 			*error_r = t_strdup_printf("flock(%s, %s) failed: "
270 				"Timed out after %u seconds%s",
271 				path, lock_type_str, timeout_secs,
272 				file_lock_find(fd, set->lock_method,
273 					       lock_type));
274 			return 0;
275 		}
276 		*error_r = t_strdup_printf("flock(%s, %s) failed: %m",
277 					   path, lock_type_str);
278 		if (errno == EDEADLK && !set->allow_deadlock) {
279 			i_panic("%s%s", *error_r,
280 				file_lock_find(fd, set->lock_method,
281 					       lock_type));
282 		}
283 		return -1;
284 #endif
285 	}
286 	case FILE_LOCK_METHOD_DOTLOCK:
287 		/* we shouldn't get here */
288 		i_unreached();
289 	}
290 
291 	return 1;
292 }
293 
file_wait_lock(int fd,const char * path,int lock_type,const struct file_lock_settings * set,unsigned int timeout_secs,struct file_lock ** lock_r,const char ** error_r)294 int file_wait_lock(int fd, const char *path, int lock_type,
295 		   const struct file_lock_settings *set,
296 		   unsigned int timeout_secs,
297 		   struct file_lock **lock_r, const char **error_r)
298 {
299 	struct file_lock *lock;
300 	int ret;
301 
302 	ret = file_lock_do(fd, path, lock_type, set, timeout_secs, error_r);
303 	if (ret <= 0)
304 		return ret;
305 
306 	lock = i_new(struct file_lock, 1);
307 	lock->set = *set;
308 	lock->fd = fd;
309 	lock->path = i_strdup(path);
310 	lock->lock_type = lock_type;
311 	i_gettimeofday(&lock->locked_time);
312 	*lock_r = lock;
313 	return 1;
314 }
315 
file_lock_try_update(struct file_lock * lock,int lock_type)316 int file_lock_try_update(struct file_lock *lock, int lock_type)
317 {
318 	const char *error;
319 	int ret;
320 
321 	ret = file_lock_do(lock->fd, lock->path, lock_type, &lock->set, 0,
322 			   &error);
323 	if (ret <= 0)
324 		return ret;
325 	file_lock_log_warning_if_slow(lock);
326 	lock->lock_type = lock_type;
327 	return 1;
328 }
329 
file_lock_set_unlink_on_free(struct file_lock * lock,bool set)330 void file_lock_set_unlink_on_free(struct file_lock *lock, bool set)
331 {
332 	lock->set.unlink_on_free = set;
333 }
334 
file_lock_set_close_on_free(struct file_lock * lock,bool set)335 void file_lock_set_close_on_free(struct file_lock *lock, bool set)
336 {
337 	lock->set.close_on_free = set;
338 }
339 
file_lock_from_dotlock(struct dotlock ** dotlock)340 struct file_lock *file_lock_from_dotlock(struct dotlock **dotlock)
341 {
342 	struct file_lock *lock;
343 
344 	lock = i_new(struct file_lock, 1);
345 	lock->set.lock_method = FILE_LOCK_METHOD_DOTLOCK;
346 	lock->fd = -1;
347 	lock->path = i_strdup(file_dotlock_get_lock_path(*dotlock));
348 	lock->lock_type = F_WRLCK;
349 	i_gettimeofday(&lock->locked_time);
350 	lock->dotlock = *dotlock;
351 
352 	*dotlock = NULL;
353 	return lock;
354 }
355 
file_unlock_real(struct file_lock * lock)356 static void file_unlock_real(struct file_lock *lock)
357 {
358 	const char *error;
359 
360 	if (file_lock_do(lock->fd, lock->path, F_UNLCK, &lock->set, 0,
361 			 &error) == 0) {
362 		/* this shouldn't happen */
363 		i_error("file_unlock(%s) failed: %m", lock->path);
364 	}
365 }
366 
file_unlock(struct file_lock ** _lock)367 void file_unlock(struct file_lock **_lock)
368 {
369 	struct file_lock *lock = *_lock;
370 
371 	*_lock = NULL;
372 
373 	/* unlocking is unnecessary when the file is unlinked. or alternatively
374 	   the unlink() must be done before unlocking, because otherwise it
375 	   could be deleting the new lock. */
376 	i_assert(!lock->set.unlink_on_free);
377 
378 	if (lock->dotlock == NULL)
379 		file_unlock_real(lock);
380 	file_lock_free(&lock);
381 }
382 
file_try_unlink_locked(struct file_lock * lock)383 static void file_try_unlink_locked(struct file_lock *lock)
384 {
385 	struct file_lock *temp_lock = NULL;
386 	struct file_lock_settings temp_set = lock->set;
387 	struct stat st1, st2;
388 	const char *error;
389 	int ret;
390 
391 	temp_set.close_on_free = FALSE;
392 	temp_set.unlink_on_free = FALSE;
393 
394 	file_unlock_real(lock);
395 	ret = file_try_lock(lock->fd, lock->path, F_WRLCK, &temp_set,
396 			    &temp_lock, &error);
397 	if (ret < 0) {
398 		i_error("file_lock_free(): Unexpectedly failed to retry locking %s: %s",
399 			lock->path, error);
400 	} else if (ret == 0) {
401 		/* already locked by someone else */
402 	} else if (fstat(lock->fd, &st1) < 0) {
403 		/* not expected to happen */
404 		i_error("file_lock_free(): fstat(%s) failed: %m", lock->path);
405 	} else if (stat(lock->path, &st2) < 0) {
406 		if (errno != ENOENT)
407 			i_error("file_lock_free(): stat(%s) failed: %m", lock->path);
408 	} else if (st1.st_ino != st2.st_ino ||
409 		   !CMP_DEV_T(st1.st_dev, st2.st_dev)) {
410 		/* lock file was recreated already - don't delete it */
411 	} else {
412 		/* nobody was waiting on the lock - unlink it */
413 		i_unlink(lock->path);
414 	}
415 	file_lock_free(&temp_lock);
416 }
417 
file_lock_free(struct file_lock ** _lock)418 void file_lock_free(struct file_lock **_lock)
419 {
420 	struct file_lock *lock = *_lock;
421 
422 	if (lock == NULL)
423 		return;
424 
425 	*_lock = NULL;
426 
427 	if (lock->dotlock != NULL)
428 		file_dotlock_delete(&lock->dotlock);
429 	if (lock->set.unlink_on_free)
430 		file_try_unlink_locked(lock);
431 	if (lock->set.close_on_free)
432 		i_close_fd(&lock->fd);
433 
434 	file_lock_log_warning_if_slow(lock);
435 	i_free(lock->path);
436 	i_free(lock);
437 }
438 
file_lock_get_path(struct file_lock * lock)439 const char *file_lock_get_path(struct file_lock *lock)
440 {
441 	return lock->path;
442 }
443 
file_lock_set_path(struct file_lock * lock,const char * path)444 void file_lock_set_path(struct file_lock *lock, const char *path)
445 {
446 	if (path != lock->path) {
447 		i_free(lock->path);
448 		lock->path = i_strdup(path);
449 	}
450 }
451 
file_lock_wait_start(void)452 void file_lock_wait_start(void)
453 {
454 	i_assert(lock_wait_start.tv_sec == 0);
455 
456 	i_gettimeofday(&lock_wait_start);
457 }
458 
file_lock_wait_init_warning(void)459 static void file_lock_wait_init_warning(void)
460 {
461 	const char *value;
462 
463 	i_assert(file_lock_slow_warning_usecs == -1);
464 
465 	value = getenv("FILE_LOCK_SLOW_WARNING_MSECS");
466 	if (value == NULL)
467 		file_lock_slow_warning_usecs = LLONG_MAX;
468 	else if (str_to_llong(value, &file_lock_slow_warning_usecs) == 0 &&
469 		 file_lock_slow_warning_usecs > 0) {
470 		file_lock_slow_warning_usecs *= 1000;
471 	} else {
472 		i_error("FILE_LOCK_SLOW_WARNING_MSECS: "
473 			"Invalid value '%s' - ignoring", value);
474 		file_lock_slow_warning_usecs = LLONG_MAX;
475 	}
476 }
477 
file_lock_log_warning_if_slow(struct file_lock * lock)478 static void file_lock_log_warning_if_slow(struct file_lock *lock)
479 {
480 	if (file_lock_slow_warning_usecs < 0)
481 		file_lock_wait_init_warning();
482 	if (file_lock_slow_warning_usecs == LLONG_MAX) {
483 		/* slowness checking is disabled */
484 		return;
485 	}
486 	if (lock->lock_type != F_WRLCK) {
487 		/* some shared locks can legitimately be kept for a long time.
488 		   don't warn about them. */
489 		return;
490 	}
491 
492 	struct timeval now;
493 	i_gettimeofday(&now);
494 
495 	int diff = timeval_diff_msecs(&now, &lock->locked_time);
496 	if (diff > file_lock_slow_warning_usecs/1000) {
497 		i_warning("Lock %s kept for %d.%03d secs", lock->path,
498 			  diff / 1000, diff % 1000);
499 	}
500 }
501 
file_lock_wait_end(const char * lock_name)502 void file_lock_wait_end(const char *lock_name)
503 {
504 	struct timeval now;
505 
506 	i_assert(lock_wait_start.tv_sec != 0);
507 
508 	i_gettimeofday(&now);
509 	long long diff = timeval_diff_usecs(&now, &lock_wait_start);
510 	if (diff < 0) {
511 		/* time moved backwards */
512 		diff = 0;
513 	}
514 	if (diff > file_lock_slow_warning_usecs) {
515 		if (file_lock_slow_warning_usecs < 0)
516 			file_lock_wait_init_warning();
517 		if (diff > file_lock_slow_warning_usecs) {
518 			int diff_msecs = (diff + 999) / 1000;
519 			i_warning("Locking %s took %d.%03d secs", lock_name,
520 				  diff_msecs / 1000, diff_msecs % 1000);
521 		}
522 	}
523 	file_lock_wait_usecs += diff;
524 	lock_wait_start.tv_sec = 0;
525 }
526 
file_lock_wait_get_total_usecs(void)527 uint64_t file_lock_wait_get_total_usecs(void)
528 {
529 	return file_lock_wait_usecs;
530 }
531