file-lock.c revision d8d93ecd89efa7c84a3a21264a550449619f715b
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "lib.h"
e05a4c4136fec723f019bee8383103080203f127Timo Sirainen#include "istream.h"
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen#include "file-lock.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "time-util.h"
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen
2b9dbb270ad82e58d5f3581436e6f143176d5819Timo Sirainen#include <time.h>
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen#include <sys/stat.h>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#ifdef HAVE_FLOCK
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen# include <sys/file.h>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#endif
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainenstruct file_lock {
128ea07dab8d67124ea74bcc085a478784b6358aTimo Sirainen int fd;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen char *path;
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen struct timeval locked_time;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen int lock_type;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen enum file_lock_method lock_method;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainenstatic struct timeval lock_wait_start;
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainenstatic uint64_t file_lock_wait_usecs = 0;
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainenstatic long long file_lock_slow_warning_usecs = -1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainenstatic void file_lock_log_warning_if_slow(struct file_lock *lock);
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainenbool file_lock_method_parse(const char *name, enum file_lock_method *method_r)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen{
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen if (strcasecmp(name, "fcntl") == 0)
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen *method_r = FILE_LOCK_METHOD_FCNTL;
2b9dbb270ad82e58d5f3581436e6f143176d5819Timo Sirainen else if (strcasecmp(name, "flock") == 0)
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen *method_r = FILE_LOCK_METHOD_FLOCK;
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen else if (strcasecmp(name, "dotlock") == 0)
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen *method_r = FILE_LOCK_METHOD_DOTLOCK;
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen else
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen return FALSE;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen return TRUE;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen}
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainenconst char *file_lock_method_to_str(enum file_lock_method method)
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen{
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen switch (method) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen case FILE_LOCK_METHOD_FCNTL:
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen return "fcntl";
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen case FILE_LOCK_METHOD_FLOCK:
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen return "flock";
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen case FILE_LOCK_METHOD_DOTLOCK:
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen return "dotlock";
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen }
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen i_unreached();
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen}
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainenint file_try_lock(int fd, const char *path, int lock_type,
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen enum file_lock_method lock_method,
50c617761ee9653bd44646a95178773a3686d62eTimo Sirainen struct file_lock **lock_r)
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen{
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen return file_wait_lock(fd, path, lock_type, lock_method, 0, lock_r);
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen}
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainenint file_try_lock_error(int fd, const char *path, int lock_type,
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen enum file_lock_method lock_method,
01937f71b3ae0d5b30b813372f44a3e7e86c89dcTimo Sirainen struct file_lock **lock_r, const char **error_r)
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen{
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen return file_wait_lock_error(fd, path, lock_type, lock_method, 0,
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen lock_r, error_r);
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen}
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainenstatic const char *
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenfile_lock_find_fcntl(int lock_fd, int lock_type)
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen{
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen struct flock fl;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen
1f1ee8db68d9ae1604350801cd8dc33ebe29fe8aTimo Sirainen i_zero(&fl);
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen fl.l_type = lock_type;
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen fl.l_whence = SEEK_SET;
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen fl.l_start = 0;
900bb5e316d030cdebff7ee128ce65881dfb27f7Timo Sirainen fl.l_len = 0;
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen if (fcntl(lock_fd, F_GETLK, &fl) < 0 ||
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen fl.l_type == F_UNLCK || fl.l_pid == -1 || fl.l_pid == 0)
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen return "";
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen return t_strdup_printf(" (%s lock held by pid %ld)",
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainen fl.l_type == F_RDLCK ? "READ" : "WRITE", (long)fl.l_pid);
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen}
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainenstatic const char *
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainenfile_lock_find_proc_locks(int lock_fd ATTR_UNUSED)
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen{
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen /* do anything except Linux support this? don't bother trying it for
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen OSes we don't know about. */
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen#ifdef __linux__
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen static bool have_proc_locks = TRUE;
e2d268e9531227ead6a98466ecf3c046c857ef70Timo Sirainen struct stat st;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen char node_buf[MAX_INT_STRLEN*3 + 2 + 1];
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct istream *input;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char *line, *lock_type = "";
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen pid_t pid = 0;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen int fd;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen if (!have_proc_locks)
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen return NULL;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen if (fstat(lock_fd, &st) < 0)
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen return "";
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen i_snprintf(node_buf, sizeof(node_buf), "%02x:%02x:%llu",
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainen major(st.st_dev), minor(st.st_dev),
897368f75a68f97e172ba76540759a10c557f1c3Timo Sirainen (unsigned long long)st.st_ino);
01937f71b3ae0d5b30b813372f44a3e7e86c89dcTimo Sirainen fd = open("/proc/locks", O_RDONLY);
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen if (fd == -1) {
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen have_proc_locks = FALSE;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen return "";
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen }
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen input = i_stream_create_fd_autoclose(&fd, 512);
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen while (pid == 0 && (line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen const char *const *args = t_strsplit_spaces(line, " ");
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen /* number: FLOCK/POSIX ADVISORY READ/WRITE pid
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen major:minor:inode region-start region-end */
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (str_array_length(args) < 8) {
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen ; /* don't continue from within a T_BEGIN {...} T_END */
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen } else if (strcmp(args[5], node_buf) == 0) {
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen lock_type = strcmp(args[3], "READ") == 0 ?
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen "READ" : "WRITE";
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (str_to_pid(args[4], &pid) < 0)
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen pid = 0;
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen }
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen } T_END;
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen i_stream_destroy(&input);
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (pid == 0) {
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen /* not found */
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen return "";
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen }
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (pid == getpid())
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen return " (BUG: lock is held by our own process)";
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen return t_strdup_printf(" (%s lock held by pid %ld)", lock_type, (long)pid);
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen#else
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen return "";
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen#endif
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen}
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainenconst char *file_lock_find(int lock_fd, enum file_lock_method lock_method,
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen int lock_type)
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen{
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen const char *ret;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (lock_method == FILE_LOCK_METHOD_FCNTL) {
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen ret = file_lock_find_fcntl(lock_fd, lock_type);
a341c4cdbd4b93ba479f465ad3f569dc82f57312Timo Sirainen if (ret[0] != '\0')
013e3b3942e9550fde619a0b3ce6bdd04edc4268Timo Sirainen return ret;
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen }
9293bf90039454f47e94e4ba3722a775cfa7d25cTimo Sirainen return file_lock_find_proc_locks(lock_fd);
b66d803de86bfb411165b3465b0d9ef64ecfe2a1Timo Sirainen}
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainenstatic bool err_is_lock_timeout(time_t started, unsigned int timeout_secs)
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen{
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen /* if EINTR took at least timeout_secs-1 number of seconds,
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen assume it was the alarm. otherwise log EINTR failure.
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen (We most likely don't want to retry EINTR since a signal
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen means somebody wants us to stop blocking). */
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen return errno == EINTR &&
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen (unsigned long)(time(NULL) - started + 1) >= timeout_secs;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen}
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
ab9a91eb05a54f7675e0bf861aca53f417e1980dTimo Sirainenstatic int file_lock_do(int fd, const char *path, int lock_type,
ab9a91eb05a54f7675e0bf861aca53f417e1980dTimo Sirainen enum file_lock_method lock_method,
ab9a91eb05a54f7675e0bf861aca53f417e1980dTimo Sirainen unsigned int timeout_secs, const char **error_r)
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen{
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen const char *lock_type_str;
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen time_t started = time(NULL);
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen int ret;
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen i_assert(fd != -1);
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen if (timeout_secs != 0) {
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen alarm(timeout_secs);
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen file_lock_wait_start();
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen }
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen lock_type_str = lock_type == F_UNLCK ? "unlock" :
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen (lock_type == F_RDLCK ? "read-lock" : "write-lock");
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen switch (lock_method) {
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen case FILE_LOCK_METHOD_FCNTL: {
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen#ifndef HAVE_FCNTL
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen *error_r = t_strdup_printf(
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen "Can't lock file %s: fcntl() locks not supported", path);
96308127e006bb3b1108093bcf4cc1fd9481cb7aTimo Sirainen return -1;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen#else
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen struct flock fl;
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainen fl.l_type = lock_type;
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen fl.l_whence = SEEK_SET;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen fl.l_start = 0;
efe78d3ba24fc866af1c79b9223dc0809ba26cadStephan Bosch fl.l_len = 0;
6d931bbce16786df431e9ae8201a78a95084316dTimo Sirainen
6d931bbce16786df431e9ae8201a78a95084316dTimo Sirainen ret = fcntl(fd, timeout_secs != 0 ? F_SETLKW : F_SETLK, &fl);
96308127e006bb3b1108093bcf4cc1fd9481cb7aTimo Sirainen if (timeout_secs != 0) {
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen alarm(0);
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen file_lock_wait_end(path);
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen }
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen if (ret == 0)
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen break;
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen if (timeout_secs == 0 &&
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen (errno == EACCES || errno == EAGAIN)) {
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen /* locked by another process */
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainen *error_r = t_strdup_printf(
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainen "fcntl(%s, %s, F_SETLK) locking failed: %m "
741d705983e10046f07ef372b760bcdd169b068aTimo Sirainen "(File is already locked)", path, lock_type_str);
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen return 0;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen }
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (err_is_lock_timeout(started, timeout_secs)) {
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen errno = EAGAIN;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *error_r = t_strdup_printf(
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen "fcntl(%s, %s, F_SETLKW) locking failed: "
5da1aa5197a43d83f0fb3eeb83125c7cd73d1b62Timo Sirainen "Timed out after %u seconds%s",
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen path, lock_type_str, timeout_secs,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen file_lock_find(fd, lock_method, lock_type));
return 0;
}
*error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m",
path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_SETLKW");
if (errno == EDEADLK)
i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, lock_type));
return -1;
#endif
}
case FILE_LOCK_METHOD_FLOCK: {
#ifndef HAVE_FLOCK
*error_r = t_strdup_printf(
"Can't lock file %s: flock() not supported", path);
return -1;
#else
int operation = timeout_secs != 0 ? 0 : LOCK_NB;
switch (lock_type) {
case F_RDLCK:
operation |= LOCK_SH;
break;
case F_WRLCK:
operation |= LOCK_EX;
break;
case F_UNLCK:
operation |= LOCK_UN;
break;
}
ret = flock(fd, operation);
if (timeout_secs != 0) {
alarm(0);
file_lock_wait_end(path);
}
if (ret == 0)
break;
if (timeout_secs == 0 && errno == EWOULDBLOCK) {
/* locked by another process */
*error_r = t_strdup_printf(
"flock(%s, %s) failed: %m "
"(File is already locked)", path, lock_type_str);
return 0;
}
if (err_is_lock_timeout(started, timeout_secs)) {
errno = EAGAIN;
*error_r = t_strdup_printf("flock(%s, %s) failed: "
"Timed out after %u seconds%s",
path, lock_type_str, timeout_secs,
file_lock_find(fd, lock_method, lock_type));
return 0;
}
*error_r = t_strdup_printf("flock(%s, %s) failed: %m",
path, lock_type_str);
if (errno == EDEADLK)
i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, lock_type));
return -1;
#endif
}
case FILE_LOCK_METHOD_DOTLOCK:
/* we shouldn't get here */
i_unreached();
}
return 1;
}
int file_wait_lock(int fd, const char *path, int lock_type,
enum file_lock_method lock_method,
unsigned int timeout_secs,
struct file_lock **lock_r)
{
const char *error;
int ret;
ret = file_wait_lock_error(fd, path, lock_type, lock_method,
timeout_secs, lock_r, &error);
if (ret < 0)
i_error("%s", error);
return ret;
}
int file_wait_lock_error(int fd, const char *path, int lock_type,
enum file_lock_method lock_method,
unsigned int timeout_secs,
struct file_lock **lock_r, const char **error_r)
{
struct file_lock *lock;
int ret;
ret = file_lock_do(fd, path, lock_type, lock_method, timeout_secs, error_r);
if (ret <= 0)
return ret;
lock = i_new(struct file_lock, 1);
lock->fd = fd;
lock->path = i_strdup(path);
lock->lock_type = lock_type;
lock->lock_method = lock_method;
if (gettimeofday(&lock->locked_time, NULL) < 0)
i_fatal("gettimeofday() failed: %m");
*lock_r = lock;
return 1;
}
int file_lock_try_update(struct file_lock *lock, int lock_type)
{
const char *error;
int ret;
ret = file_lock_do(lock->fd, lock->path, lock_type,
lock->lock_method, 0, &error);
if (ret <= 0)
return ret;
file_lock_log_warning_if_slow(lock);
lock->lock_type = lock_type;
return 1;
}
void file_unlock(struct file_lock **_lock)
{
struct file_lock *lock = *_lock;
const char *error;
*_lock = NULL;
if (file_lock_do(lock->fd, lock->path, F_UNLCK,
lock->lock_method, 0, &error) == 0) {
/* this shouldn't happen */
i_error("file_unlock(%s) failed: %m", lock->path);
}
file_lock_free(&lock);
}
void file_lock_free(struct file_lock **_lock)
{
struct file_lock *lock = *_lock;
*_lock = NULL;
file_lock_log_warning_if_slow(lock);
i_free(lock->path);
i_free(lock);
}
const char *file_lock_get_path(struct file_lock *lock)
{
return lock->path;
}
void file_lock_set_path(struct file_lock *lock, const char *path)
{
if (path != lock->path) {
i_free(lock->path);
lock->path = i_strdup(path);
}
}
void file_lock_wait_start(void)
{
i_assert(lock_wait_start.tv_sec == 0);
if (gettimeofday(&lock_wait_start, NULL) < 0)
i_fatal("gettimeofday() failed: %m");
}
static void file_lock_wait_init_warning(void)
{
const char *value;
i_assert(file_lock_slow_warning_usecs == -1);
value = getenv("FILE_LOCK_SLOW_WARNING_MSECS");
if (value == NULL)
file_lock_slow_warning_usecs = LLONG_MAX;
else if (str_to_llong(value, &file_lock_slow_warning_usecs) == 0 &&
file_lock_slow_warning_usecs > 0) {
file_lock_slow_warning_usecs *= 1000;
} else {
i_error("FILE_LOCK_SLOW_WARNING_MSECS: "
"Invalid value '%s' - ignoring", value);
file_lock_slow_warning_usecs = LLONG_MAX;
}
}
static void file_lock_log_warning_if_slow(struct file_lock *lock)
{
if (file_lock_slow_warning_usecs < 0)
file_lock_wait_init_warning();
if (file_lock_slow_warning_usecs == LLONG_MAX) {
/* slowness checking is disabled */
return;
}
if (lock->lock_type != F_WRLCK) {
/* some shared locks can legitimately be kept for a long time.
don't warn about them. */
return;
}
struct timeval now;
if (gettimeofday(&now, NULL) < 0)
i_fatal("gettimeofday() failed: %m");
int diff = timeval_diff_msecs(&now, &lock->locked_time);
if (diff > file_lock_slow_warning_usecs/1000) {
i_warning("Lock %s kept for %d.%03d secs", lock->path,
diff / 1000, diff % 1000);
}
}
void file_lock_wait_end(const char *lock_name)
{
struct timeval now;
i_assert(lock_wait_start.tv_sec != 0);
if (gettimeofday(&now, NULL) < 0)
i_fatal("gettimeofday() failed: %m");
long long diff = timeval_diff_usecs(&now, &lock_wait_start);
if (diff > file_lock_slow_warning_usecs) {
if (file_lock_slow_warning_usecs < 0)
file_lock_wait_init_warning();
if (diff > file_lock_slow_warning_usecs) {
int diff_msecs = (diff + 999) / 1000;
i_warning("Locking %s took %d.%03d secs", lock_name,
diff_msecs / 1000, diff_msecs % 1000);
}
}
file_lock_wait_usecs += diff;
lock_wait_start.tv_sec = 0;
}
uint64_t file_lock_wait_get_total_usecs(void)
{
return file_lock_wait_usecs;
}