bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
f83fd83f9c6708d198748e714aa947cad9362c02Timo Sirainenstatic long long file_lock_slow_warning_usecs = -1;
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainenstatic void file_lock_log_warning_if_slow(struct file_lock *lock);
6e77746e501c2b45850b1c530836058ed75e09eeTimo Sirainenbool file_lock_method_parse(const char *name, enum file_lock_method *method_r)
8c8f7ac580b661aee3d8b8dd37df4a9b41c77000Timo Sirainenconst char *file_lock_method_to_str(enum file_lock_method method)
8c8f7ac580b661aee3d8b8dd37df4a9b41c77000Timo Sirainen return "fcntl";
8c8f7ac580b661aee3d8b8dd37df4a9b41c77000Timo Sirainen return "flock";
8c8f7ac580b661aee3d8b8dd37df4a9b41c77000Timo Sirainen return "dotlock";
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainenint file_try_lock(int fd, const char *path, int lock_type,
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen return file_wait_lock(fd, path, lock_type, lock_method, 0, lock_r);
0dc7891233a973829f00371b27810f849b987c66Timo Sirainenint file_try_lock_error(int fd, const char *path, int lock_type,
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen struct file_lock **lock_r, const char **error_r)
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen return file_wait_lock_error(fd, path, lock_type, lock_method, 0,
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainenstatic const char *
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainenfile_lock_find_fcntl(int lock_fd, int lock_type)
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen fl.l_type == F_UNLCK || fl.l_pid == -1 || fl.l_pid == 0)
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen return t_strdup_printf(" (%s lock held by pid %ld)",
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen fl.l_type == F_RDLCK ? "READ" : "WRITE", (long)fl.l_pid);
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainenstatic const char *
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainenfile_lock_find_proc_locks(int lock_fd ATTR_UNUSED)
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen /* do anything except Linux support this? don't bother trying it for
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen OSes we don't know about. */
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen i_snprintf(node_buf, sizeof(node_buf), "%02x:%02x:%llu",
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen input = i_stream_create_fd_autoclose(&fd, 512);
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen while (pid == 0 && (line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen const char *const *args = t_strsplit_spaces(line, " ");
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen /* number: FLOCK/POSIX ADVISORY READ/WRITE pid
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen major:minor:inode region-start region-end */
14cac26dcb71108abfdc95ea524e74be1f95774cPhil Carmody ; /* don't continue from within a T_BEGIN {...} T_END */
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen /* not found */
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen return " (BUG: lock is held by our own process)";
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen return t_strdup_printf(" (%s lock held by pid %ld)", lock_type, (long)pid);
1107c86ff3fa4f29796c2e76134b78d0b4a0db50Timo Sirainenconst char *file_lock_find(int lock_fd, enum file_lock_method lock_method,
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen ret = file_lock_find_fcntl(lock_fd, lock_type);
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainenstatic bool err_is_lock_timeout(time_t started, unsigned int timeout_secs)
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen /* if EINTR took at least timeout_secs-1 number of seconds,
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen assume it was the alarm. otherwise log EINTR failure.
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen (We most likely don't want to retry EINTR since a signal
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen means somebody wants us to stop blocking). */
a8b37b688ceaa3ed3d40b3ccbdba5bb75cfb64b0Timo Sirainen (unsigned long)(time(NULL) - started + 1) >= timeout_secs;
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainenstatic int file_lock_do(int fd, const char *path, int lock_type,
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen unsigned int timeout_secs, const char **error_r)
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen lock_type_str = lock_type == F_UNLCK ? "unlock" :
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen (lock_type == F_RDLCK ? "read-lock" : "write-lock");
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "Can't lock file %s: fcntl() locks not supported", path);
23bdbb7b1831785c6ba6df190f6369da882d2b9dTimo Sirainen ret = fcntl(fd, timeout_secs != 0 ? F_SETLKW : F_SETLK, &fl);
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen /* locked by another process */
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "fcntl(%s, %s, F_SETLK) locking failed: %m "
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "(File is already locked)", path, lock_type_str);
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen if (err_is_lock_timeout(started, timeout_secs)) {
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "fcntl(%s, %s, F_SETLKW) locking failed: "
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen "Timed out after %u seconds%s",
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen *error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m",
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_SETLKW");
3d2fd3816b79caf8048582f1e951019187ee9d9dTimo Sirainen i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, lock_type));
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "Can't lock file %s: flock() not supported", path);
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen int operation = timeout_secs != 0 ? 0 : LOCK_NB;
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen if (timeout_secs == 0 && errno == EWOULDBLOCK) {
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen /* locked by another process */
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "flock(%s, %s) failed: %m "
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen "(File is already locked)", path, lock_type_str);
4eb418849d5c6bf77b2721e4e6aef2e97deaa197Timo Sirainen if (err_is_lock_timeout(started, timeout_secs)) {
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen *error_r = t_strdup_printf("flock(%s, %s) failed: "
b5ff746939712c6a9bef71405fa786d5471cf177Timo Sirainen "Timed out after %u seconds%s",
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen *error_r = t_strdup_printf("flock(%s, %s) failed: %m",
3d2fd3816b79caf8048582f1e951019187ee9d9dTimo Sirainen i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, lock_type));
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen /* we shouldn't get here */
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainenint file_wait_lock(int fd, const char *path, int lock_type,
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen ret = file_wait_lock_error(fd, path, lock_type, lock_method,
0dc7891233a973829f00371b27810f849b987c66Timo Sirainenint file_wait_lock_error(int fd, const char *path, int lock_type,
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen struct file_lock **lock_r, const char **error_r)
0dc7891233a973829f00371b27810f849b987c66Timo Sirainen ret = file_lock_do(fd, path, lock_type, lock_method, timeout_secs, error_r);
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen if (gettimeofday(&lock->locked_time, NULL) < 0)
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainenint file_lock_try_update(struct file_lock *lock, int lock_type)
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen ret = file_lock_do(lock->fd, lock->path, lock_type,
68332e3a49dea15013aa8f4daa16b5e07eb3d543Timo Sirainenvoid file_lock_set_unlink_on_free(struct file_lock *lock, bool set)
65f9a90ef5ed4c86fb9e44f22e472509126ae9f5Timo Sirainenvoid file_lock_set_close_on_free(struct file_lock *lock, bool set)
f27a51cd9a66685b63357de25e733c37eb03b07cTimo Sirainenstruct file_lock *file_lock_from_dotlock(struct dotlock **dotlock)
f27a51cd9a66685b63357de25e733c37eb03b07cTimo Sirainen lock->path = i_strdup(file_dotlock_get_lock_path(*dotlock));
f27a51cd9a66685b63357de25e733c37eb03b07cTimo Sirainen if (gettimeofday(&lock->locked_time, NULL) < 0)
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainenstatic void file_unlock_real(struct file_lock *lock)
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen if (file_lock_do(lock->fd, lock->path, F_UNLCK,
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen /* this shouldn't happen */
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen i_error("file_unlock(%s) failed: %m", lock->path);
68332e3a49dea15013aa8f4daa16b5e07eb3d543Timo Sirainen /* unlocking is unnecessary when the file is unlinked. or alternatively
68332e3a49dea15013aa8f4daa16b5e07eb3d543Timo Sirainen the unlink() must be done before unlocking, because otherwise it
68332e3a49dea15013aa8f4daa16b5e07eb3d543Timo Sirainen could be deleting the new lock. */
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainenstatic void file_try_unlink_locked(struct file_lock *lock)
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen ret = file_try_lock_error(lock->fd, lock->path, F_WRLCK,
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen i_error("file_lock_free(): Unexpectedly failed to retry locking %s: %s",
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen } else if (ret == 0) {
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen /* already locked by someone else */
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen /* not expected to happen */
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen i_error("file_lock_free(): fstat(%s) failed: %m", lock->path);
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen i_error("file_lock_free(): stat(%s) failed: %m", lock->path);
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen /* lock file was recreated already - don't delete it */
3581ece16eb740a5003c4c11dd3c7d02839a24a4Timo Sirainen /* nobody was waiting on the lock - unlink it */
f1243d7759a3dddc1c3eaf7eda8a153c8ef0112bTimo Sirainenconst char *file_lock_get_path(struct file_lock *lock)
d8d93ecd89efa7c84a3a21264a550449619f715bTimo Sirainenvoid file_lock_set_path(struct file_lock *lock, const char *path)
f83fd83f9c6708d198748e714aa947cad9362c02Timo Sirainen value = getenv("FILE_LOCK_SLOW_WARNING_MSECS");
f83fd83f9c6708d198748e714aa947cad9362c02Timo Sirainen else if (str_to_llong(value, &file_lock_slow_warning_usecs) == 0 &&
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainenstatic void file_lock_log_warning_if_slow(struct file_lock *lock)
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen if (file_lock_slow_warning_usecs == LLONG_MAX) {
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen /* slowness checking is disabled */
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen /* some shared locks can legitimately be kept for a long time.
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen don't warn about them. */
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen int diff = timeval_diff_msecs(&now, &lock->locked_time);
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen if (diff > file_lock_slow_warning_usecs/1000) {
1ea0aa8e14e843f2776746776a429b0a1aae299dTimo Sirainen i_warning("Lock %s kept for %d.%03d secs", lock->path,
f83fd83f9c6708d198748e714aa947cad9362c02Timo Sirainen long long diff = timeval_diff_usecs(&now, &lock_wait_start);
f83fd83f9c6708d198748e714aa947cad9362c02Timo Sirainen i_warning("Locking %s took %d.%03d secs", lock_name,