file-lock.c (dovecot-2.3.16) | : | file-lock.c (dovecot-2.3.17) | ||
---|---|---|---|---|
skipping to change at line 16 | skipping to change at line 16 | |||
#include "file-dotlock.h" | #include "file-dotlock.h" | |||
#include "time-util.h" | #include "time-util.h" | |||
#include <time.h> | #include <time.h> | |||
#include <sys/stat.h> | #include <sys/stat.h> | |||
#ifdef HAVE_FLOCK | #ifdef HAVE_FLOCK | |||
# include <sys/file.h> | # include <sys/file.h> | |||
#endif | #endif | |||
struct file_lock { | struct file_lock { | |||
struct file_lock_settings set; | ||||
int fd; | int fd; | |||
char *path; | char *path; | |||
struct dotlock *dotlock; | struct dotlock *dotlock; | |||
struct timeval locked_time; | struct timeval locked_time; | |||
int lock_type; | int lock_type; | |||
enum file_lock_method lock_method; | ||||
bool unlink_on_free; | ||||
bool close_on_free; | ||||
}; | }; | |||
static struct timeval lock_wait_start; | static struct timeval lock_wait_start; | |||
static uint64_t file_lock_wait_usecs = 0; | static uint64_t file_lock_wait_usecs = 0; | |||
static long long file_lock_slow_warning_usecs = -1; | static long long file_lock_slow_warning_usecs = -1; | |||
static void file_lock_log_warning_if_slow(struct file_lock *lock); | static void file_lock_log_warning_if_slow(struct file_lock *lock); | |||
bool file_lock_method_parse(const char *name, enum file_lock_method *method_r) | bool file_lock_method_parse(const char *name, enum file_lock_method *method_r) | |||
{ | { | |||
skipping to change at line 60 | skipping to change at line 59 | |||
return "fcntl"; | return "fcntl"; | |||
case FILE_LOCK_METHOD_FLOCK: | case FILE_LOCK_METHOD_FLOCK: | |||
return "flock"; | return "flock"; | |||
case FILE_LOCK_METHOD_DOTLOCK: | case FILE_LOCK_METHOD_DOTLOCK: | |||
return "dotlock"; | return "dotlock"; | |||
} | } | |||
i_unreached(); | i_unreached(); | |||
} | } | |||
int file_try_lock(int fd, const char *path, int lock_type, | int file_try_lock(int fd, const char *path, int lock_type, | |||
enum file_lock_method lock_method, | const struct file_lock_settings *set, | |||
struct file_lock **lock_r) | struct file_lock **lock_r, const char **error_r) | |||
{ | ||||
return file_wait_lock(fd, path, lock_type, lock_method, 0, lock_r); | ||||
} | ||||
int file_try_lock_error(int fd, const char *path, int lock_type, | ||||
enum file_lock_method lock_method, | ||||
struct file_lock **lock_r, const char **error_r) | ||||
{ | { | |||
return file_wait_lock_error(fd, path, lock_type, lock_method, 0, | return file_wait_lock(fd, path, lock_type, set, 0, lock_r, error_r); | |||
lock_r, error_r); | ||||
} | } | |||
static const char * | static const char * | |||
file_lock_find_fcntl(int lock_fd, int lock_type) | file_lock_find_fcntl(int lock_fd, int lock_type) | |||
{ | { | |||
struct flock fl; | struct flock fl; | |||
i_zero(&fl); | i_zero(&fl); | |||
fl.l_type = lock_type; | fl.l_type = lock_type; | |||
fl.l_whence = SEEK_SET; | fl.l_whence = SEEK_SET; | |||
skipping to change at line 171 | skipping to change at line 162 | |||
{ | { | |||
/* if EINTR took at least timeout_secs-1 number of seconds, | /* if EINTR took at least timeout_secs-1 number of seconds, | |||
assume it was the alarm. otherwise log EINTR failure. | assume it was the alarm. otherwise log EINTR failure. | |||
(We most likely don't want to retry EINTR since a signal | (We most likely don't want to retry EINTR since a signal | |||
means somebody wants us to stop blocking). */ | means somebody wants us to stop blocking). */ | |||
return errno == EINTR && | return errno == EINTR && | |||
(unsigned long)(time(NULL) - started + 1) >= timeout_secs; | (unsigned long)(time(NULL) - started + 1) >= timeout_secs; | |||
} | } | |||
static int file_lock_do(int fd, const char *path, int lock_type, | static int file_lock_do(int fd, const char *path, int lock_type, | |||
enum file_lock_method lock_method, | const struct file_lock_settings *set, | |||
unsigned int timeout_secs, const char **error_r) | unsigned int timeout_secs, const char **error_r) | |||
{ | { | |||
const char *lock_type_str; | const char *lock_type_str; | |||
time_t started = time(NULL); | time_t started = time(NULL); | |||
int ret; | int ret; | |||
i_assert(fd != -1); | i_assert(fd != -1); | |||
if (timeout_secs != 0) { | if (timeout_secs != 0) { | |||
alarm(timeout_secs); | alarm(timeout_secs); | |||
file_lock_wait_start(); | file_lock_wait_start(); | |||
} | } | |||
lock_type_str = lock_type == F_UNLCK ? "unlock" : | lock_type_str = lock_type == F_UNLCK ? "unlock" : | |||
(lock_type == F_RDLCK ? "read-lock" : "write-lock"); | (lock_type == F_RDLCK ? "read-lock" : "write-lock"); | |||
switch (lock_method) { | switch (set->lock_method) { | |||
case FILE_LOCK_METHOD_FCNTL: { | case FILE_LOCK_METHOD_FCNTL: { | |||
#ifndef HAVE_FCNTL | #ifndef HAVE_FCNTL | |||
*error_r = t_strdup_printf( | *error_r = t_strdup_printf( | |||
"Can't lock file %s: fcntl() locks not supported", path); | "Can't lock file %s: fcntl() locks not supported", path); | |||
return -1; | return -1; | |||
#else | #else | |||
struct flock fl; | struct flock fl; | |||
fl.l_type = lock_type; | fl.l_type = lock_type; | |||
fl.l_whence = SEEK_SET; | fl.l_whence = SEEK_SET; | |||
skipping to change at line 226 | skipping to change at line 217 | |||
"(File is already locked)", path, lock_type_str); | "(File is already locked)", path, lock_type_str); | |||
return 0; | return 0; | |||
} | } | |||
if (err_is_lock_timeout(started, timeout_secs)) { | if (err_is_lock_timeout(started, timeout_secs)) { | |||
errno = EAGAIN; | errno = EAGAIN; | |||
*error_r = t_strdup_printf( | *error_r = t_strdup_printf( | |||
"fcntl(%s, %s, F_SETLKW) locking failed: " | "fcntl(%s, %s, F_SETLKW) locking failed: " | |||
"Timed out after %u seconds%s", | "Timed out after %u seconds%s", | |||
path, lock_type_str, timeout_secs, | path, lock_type_str, timeout_secs, | |||
file_lock_find(fd, lock_method, lock_type)); | file_lock_find(fd, set->lock_method, | |||
lock_type)); | ||||
return 0; | return 0; | |||
} | } | |||
*error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m" , | *error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m" , | |||
path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_S ETLKW"); | path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_S ETLKW"); | |||
if (errno == EDEADLK) | if (errno == EDEADLK && !set->allow_deadlock) { | |||
i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, | i_panic("%s%s", *error_r, | |||
lock_type)); | file_lock_find(fd, set->lock_method, | |||
lock_type)); | ||||
} | ||||
return -1; | return -1; | |||
#endif | #endif | |||
} | } | |||
case FILE_LOCK_METHOD_FLOCK: { | case FILE_LOCK_METHOD_FLOCK: { | |||
#ifndef HAVE_FLOCK | #ifndef HAVE_FLOCK | |||
*error_r = t_strdup_printf( | *error_r = t_strdup_printf( | |||
"Can't lock file %s: flock() not supported", path); | "Can't lock file %s: flock() not supported", path); | |||
return -1; | return -1; | |||
#else | #else | |||
int operation = timeout_secs != 0 ? 0 : LOCK_NB; | int operation = timeout_secs != 0 ? 0 : LOCK_NB; | |||
skipping to change at line 277 | skipping to change at line 272 | |||
*error_r = t_strdup_printf( | *error_r = t_strdup_printf( | |||
"flock(%s, %s) failed: %m " | "flock(%s, %s) failed: %m " | |||
"(File is already locked)", path, lock_type_str); | "(File is already locked)", path, lock_type_str); | |||
return 0; | return 0; | |||
} | } | |||
if (err_is_lock_timeout(started, timeout_secs)) { | if (err_is_lock_timeout(started, timeout_secs)) { | |||
errno = EAGAIN; | errno = EAGAIN; | |||
*error_r = t_strdup_printf("flock(%s, %s) failed: " | *error_r = t_strdup_printf("flock(%s, %s) failed: " | |||
"Timed out after %u seconds%s", | "Timed out after %u seconds%s", | |||
path, lock_type_str, timeout_secs, | path, lock_type_str, timeout_secs, | |||
file_lock_find(fd, lock_method, lock_type)); | file_lock_find(fd, set->lock_method, | |||
lock_type)); | ||||
return 0; | return 0; | |||
} | } | |||
*error_r = t_strdup_printf("flock(%s, %s) failed: %m", | *error_r = t_strdup_printf("flock(%s, %s) failed: %m", | |||
path, lock_type_str); | path, lock_type_str); | |||
if (errno == EDEADLK) | if (errno == EDEADLK && !set->allow_deadlock) { | |||
i_panic("%s%s", *error_r, file_lock_find(fd, lock_method, | i_panic("%s%s", *error_r, | |||
lock_type)); | file_lock_find(fd, set->lock_method, | |||
lock_type)); | ||||
} | ||||
return -1; | return -1; | |||
#endif | #endif | |||
} | } | |||
case FILE_LOCK_METHOD_DOTLOCK: | case FILE_LOCK_METHOD_DOTLOCK: | |||
/* we shouldn't get here */ | /* we shouldn't get here */ | |||
i_unreached(); | i_unreached(); | |||
} | } | |||
return 1; | return 1; | |||
} | } | |||
int file_wait_lock(int fd, const char *path, int lock_type, | int file_wait_lock(int fd, const char *path, int lock_type, | |||
enum file_lock_method lock_method, | const struct file_lock_settings *set, | |||
unsigned int timeout_secs, | unsigned int timeout_secs, | |||
struct file_lock **lock_r) | struct file_lock **lock_r, const char **error_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; | struct file_lock *lock; | |||
int ret; | int ret; | |||
ret = file_lock_do(fd, path, lock_type, lock_method, timeout_secs, error_ r); | ret = file_lock_do(fd, path, lock_type, set, timeout_secs, error_r); | |||
if (ret <= 0) | if (ret <= 0) | |||
return ret; | return ret; | |||
lock = i_new(struct file_lock, 1); | lock = i_new(struct file_lock, 1); | |||
lock->set = *set; | ||||
lock->fd = fd; | lock->fd = fd; | |||
lock->path = i_strdup(path); | lock->path = i_strdup(path); | |||
lock->lock_type = lock_type; | lock->lock_type = lock_type; | |||
lock->lock_method = lock_method; | ||||
i_gettimeofday(&lock->locked_time); | i_gettimeofday(&lock->locked_time); | |||
*lock_r = lock; | *lock_r = lock; | |||
return 1; | return 1; | |||
} | } | |||
int file_lock_try_update(struct file_lock *lock, int lock_type) | int file_lock_try_update(struct file_lock *lock, int lock_type) | |||
{ | { | |||
const char *error; | const char *error; | |||
int ret; | int ret; | |||
ret = file_lock_do(lock->fd, lock->path, lock_type, | ret = file_lock_do(lock->fd, lock->path, lock_type, &lock->set, 0, | |||
lock->lock_method, 0, &error); | &error); | |||
if (ret <= 0) | if (ret <= 0) | |||
return ret; | return ret; | |||
file_lock_log_warning_if_slow(lock); | file_lock_log_warning_if_slow(lock); | |||
lock->lock_type = lock_type; | lock->lock_type = lock_type; | |||
return 1; | return 1; | |||
} | } | |||
void file_lock_set_unlink_on_free(struct file_lock *lock, bool set) | void file_lock_set_unlink_on_free(struct file_lock *lock, bool set) | |||
{ | { | |||
lock->unlink_on_free = set; | lock->set.unlink_on_free = set; | |||
} | } | |||
void file_lock_set_close_on_free(struct file_lock *lock, bool set) | void file_lock_set_close_on_free(struct file_lock *lock, bool set) | |||
{ | { | |||
lock->close_on_free = set; | lock->set.close_on_free = set; | |||
} | } | |||
struct file_lock *file_lock_from_dotlock(struct dotlock **dotlock) | struct file_lock *file_lock_from_dotlock(struct dotlock **dotlock) | |||
{ | { | |||
struct file_lock *lock; | struct file_lock *lock; | |||
lock = i_new(struct file_lock, 1); | lock = i_new(struct file_lock, 1); | |||
lock->set.lock_method = FILE_LOCK_METHOD_DOTLOCK; | ||||
lock->fd = -1; | lock->fd = -1; | |||
lock->path = i_strdup(file_dotlock_get_lock_path(*dotlock)); | lock->path = i_strdup(file_dotlock_get_lock_path(*dotlock)); | |||
lock->lock_type = F_WRLCK; | lock->lock_type = F_WRLCK; | |||
lock->lock_method = FILE_LOCK_METHOD_DOTLOCK; | ||||
i_gettimeofday(&lock->locked_time); | i_gettimeofday(&lock->locked_time); | |||
lock->dotlock = *dotlock; | lock->dotlock = *dotlock; | |||
*dotlock = NULL; | *dotlock = NULL; | |||
return lock; | return lock; | |||
} | } | |||
static void file_unlock_real(struct file_lock *lock) | static void file_unlock_real(struct file_lock *lock) | |||
{ | { | |||
const char *error; | const char *error; | |||
if (file_lock_do(lock->fd, lock->path, F_UNLCK, | if (file_lock_do(lock->fd, lock->path, F_UNLCK, &lock->set, 0, | |||
lock->lock_method, 0, &error) == 0) { | &error) == 0) { | |||
/* this shouldn't happen */ | /* this shouldn't happen */ | |||
i_error("file_unlock(%s) failed: %m", lock->path); | i_error("file_unlock(%s) failed: %m", lock->path); | |||
} | } | |||
} | } | |||
void file_unlock(struct file_lock **_lock) | void file_unlock(struct file_lock **_lock) | |||
{ | { | |||
struct file_lock *lock = *_lock; | struct file_lock *lock = *_lock; | |||
*_lock = NULL; | *_lock = NULL; | |||
/* unlocking is unnecessary when the file is unlinked. or alternatively | /* unlocking is unnecessary when the file is unlinked. or alternatively | |||
the unlink() must be done before unlocking, because otherwise it | the unlink() must be done before unlocking, because otherwise it | |||
could be deleting the new lock. */ | could be deleting the new lock. */ | |||
i_assert(!lock->unlink_on_free); | i_assert(!lock->set.unlink_on_free); | |||
if (lock->dotlock == NULL) | if (lock->dotlock == NULL) | |||
file_unlock_real(lock); | file_unlock_real(lock); | |||
file_lock_free(&lock); | file_lock_free(&lock); | |||
} | } | |||
static void file_try_unlink_locked(struct file_lock *lock) | static void file_try_unlink_locked(struct file_lock *lock) | |||
{ | { | |||
struct file_lock *temp_lock = NULL; | struct file_lock *temp_lock = NULL; | |||
struct file_lock_settings temp_set = lock->set; | ||||
struct stat st1, st2; | struct stat st1, st2; | |||
const char *error; | const char *error; | |||
int ret; | int ret; | |||
temp_set.close_on_free = FALSE; | ||||
temp_set.unlink_on_free = FALSE; | ||||
file_unlock_real(lock); | file_unlock_real(lock); | |||
ret = file_try_lock_error(lock->fd, lock->path, F_WRLCK, | ret = file_try_lock(lock->fd, lock->path, F_WRLCK, &temp_set, | |||
lock->lock_method, &temp_lock, &error); | &temp_lock, &error); | |||
if (ret < 0) { | if (ret < 0) { | |||
i_error("file_lock_free(): Unexpectedly failed to retry locking % s: %s", | i_error("file_lock_free(): Unexpectedly failed to retry locking % s: %s", | |||
lock->path, error); | lock->path, error); | |||
} else if (ret == 0) { | } else if (ret == 0) { | |||
/* already locked by someone else */ | /* already locked by someone else */ | |||
} else if (fstat(lock->fd, &st1) < 0) { | } else if (fstat(lock->fd, &st1) < 0) { | |||
/* not expected to happen */ | /* not expected to happen */ | |||
i_error("file_lock_free(): fstat(%s) failed: %m", lock->path); | i_error("file_lock_free(): fstat(%s) failed: %m", lock->path); | |||
} else if (stat(lock->path, &st2) < 0) { | } else if (stat(lock->path, &st2) < 0) { | |||
if (errno != ENOENT) | if (errno != ENOENT) | |||
skipping to change at line 441 | skipping to change at line 429 | |||
{ | { | |||
struct file_lock *lock = *_lock; | struct file_lock *lock = *_lock; | |||
if (lock == NULL) | if (lock == NULL) | |||
return; | return; | |||
*_lock = NULL; | *_lock = NULL; | |||
if (lock->dotlock != NULL) | if (lock->dotlock != NULL) | |||
file_dotlock_delete(&lock->dotlock); | file_dotlock_delete(&lock->dotlock); | |||
if (lock->unlink_on_free) | if (lock->set.unlink_on_free) | |||
file_try_unlink_locked(lock); | file_try_unlink_locked(lock); | |||
if (lock->close_on_free) | if (lock->set.close_on_free) | |||
i_close_fd(&lock->fd); | i_close_fd(&lock->fd); | |||
file_lock_log_warning_if_slow(lock); | file_lock_log_warning_if_slow(lock); | |||
i_free(lock->path); | i_free(lock->path); | |||
i_free(lock); | i_free(lock); | |||
} | } | |||
const char *file_lock_get_path(struct file_lock *lock) | const char *file_lock_get_path(struct file_lock *lock) | |||
{ | { | |||
return lock->path; | return lock->path; | |||
End of changes. 27 change blocks. | ||||
55 lines changed or deleted | 41 lines changed or added |