file-dotlock.c revision ef5ad624ab2463cbbb58e7a616de9e6b191fcb3b
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (C) 2003 Timo Sirainen */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "lib.h"
7a60e1dc9e93ef3f7c7fe1af6385a0bfa1e31bc3Timo Sirainen#include "hostpid.h"
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen#include "write-full.h"
44cf91b7a701a9b4d9f59a990552eab4f7f64fbcTimo Sirainen#include "file-dotlock.h"
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen#include <stdlib.h>
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen#include <signal.h>
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen#include <time.h>
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen#include <sys/stat.h>
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen/* 0.1 .. 0.2msec */
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainenstruct lock_info {
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen const char *path, *lock_path;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen unsigned int stale_timeout;
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen dev_t dev;
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen ino_t ino;
6cbe2facd40ea3461620571a1c168ce9884be3b3Timo Sirainen off_t size;
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen time_t mtime;
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen off_t last_size;
7ace5117d5f2395bd66f20b09e77dac05492f7ceTimo Sirainen time_t last_mtime;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen time_t last_change;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen pid_t pid;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen time_t last_pid_check;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen};
44cf91b7a701a9b4d9f59a990552eab4f7f64fbcTimo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainenstatic pid_t read_local_pid(const char *lock_path)
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen{
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen char buf[512], *host;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen int fd;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen ssize_t ret;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen fd = open(lock_path, O_RDONLY);
44cf91b7a701a9b4d9f59a990552eab4f7f64fbcTimo Sirainen if (fd == -1)
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen return -1; /* ignore the actual error */
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen /* read line */
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen ret = read(fd, buf, sizeof(buf)-1);
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen (void)close(fd);
44cf91b7a701a9b4d9f59a990552eab4f7f64fbcTimo Sirainen if (ret <= 0)
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen return -1;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen /* fix the string */
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen if (buf[ret-1] == '\n')
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen ret--;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen buf[ret] = '\0';
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen /* it should contain pid:host */
5bb7c9863cbb62c41b13e7f42e04f1d57b4634f8Timo Sirainen host = strchr(buf, ':');
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (host == NULL)
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return -1;
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen *host++ = '\0';
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen /* host must be ours */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (strcmp(host, my_hostname) != 0)
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen return -1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (!is_numeric(buf, '\0'))
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen return -1;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen return (pid_t)strtoul(buf, NULL, 0);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen}
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainenstatic int check_lock(time_t now, struct lock_info *lock_info)
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen{
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen struct stat st;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (lstat(lock_info->lock_path, &st) < 0) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (errno != ENOENT) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen i_error("lstat(%s) failed: %m", lock_info->lock_path);
7a60e1dc9e93ef3f7c7fe1af6385a0bfa1e31bc3Timo Sirainen return -1;
3ab7783791bd46cdd46e9b9de3e98e8efcb6c6bfTimo Sirainen }
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return 1;
3ab7783791bd46cdd46e9b9de3e98e8efcb6c6bfTimo Sirainen }
3ab7783791bd46cdd46e9b9de3e98e8efcb6c6bfTimo Sirainen
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen if (lock_info->ino != st.st_ino ||
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen !CMP_DEV_T(lock_info->dev, st.st_dev) ||
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen lock_info->mtime != st.st_mtime ||
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen lock_info->size != st.st_size) {
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen /* either our first check or someone else got the lock file.
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen check if it contains a pid whose existence we can verify */
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen lock_info->dev = st.st_dev;
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen lock_info->ino = st.st_ino;
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen lock_info->mtime = st.st_mtime;
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen lock_info->size = st.st_size;
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen lock_info->pid = read_local_pid(lock_info->lock_path);
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen lock_info->last_change = now;
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen }
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen if (lock_info->pid != -1) {
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen /* we've local PID. Check if it exists. */
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen if (lock_info->last_pid_check == now)
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen return 0;
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen if (kill(lock_info->pid, 0) == 0 || errno != ESRCH)
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen return 0;
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen
553308791c097219e8eb31cbd03a29e9e1333848Timo Sirainen /* doesn't exist - go ahead and delete */
24d7c5fc9fa1cb1f49402ec796654113199ba4e6Timo Sirainen if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen i_error("unlink(%s) failed: %m", lock_info->lock_path);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return -1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return 1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen /* see if the file we're locking is being modified */
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen if (stat(lock_info->path, &st) < 0) {
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen if (errno == ENOENT) {
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen /* file doesn't exist. treat it as if
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen it hasn't changed */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen } else {
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen i_error("stat(%s) failed: %m", lock_info->path);
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen return -1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen } else if (lock_info->last_size != st.st_size ||
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen lock_info->last_mtime != st.st_mtime) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen lock_info->last_change = now;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen lock_info->last_size = st.st_size;
90cf976e328e093da91a8332d96182201f4ef6c1Timo Sirainen lock_info->last_mtime = st.st_mtime;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (now > lock_info->last_change + (time_t)lock_info->stale_timeout) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* no changes for a while, assume stale lock */
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) {
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen i_error("unlink(%s) failed: %m", lock_info->lock_path);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return -1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return 1;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen }
7a60e1dc9e93ef3f7c7fe1af6385a0bfa1e31bc3Timo Sirainen
553308791c097219e8eb31cbd03a29e9e1333848Timo Sirainen return 0;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen}
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen
7dcb5545370faa9d4ff83b3ede65a69fc3dd4b65Timo Sirainenstatic int try_create_lock(const char *lock_path, struct dotlock *dotlock_r)
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen{
cc0495b3bbe3c3e41c512274b302d6f0fa028187Timo Sirainen const char *str;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen struct stat st;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen int fd;
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen fd = open(lock_path, O_WRONLY | O_EXCL | O_CREAT, 0644);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (fd == -1)
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen return -1;
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* write our pid and host, if possible */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen str = t_strdup_printf("%s:%s", my_pid, my_hostname);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (write_full(fd, str, strlen(str)) < 0) {
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen /* failed, leave it empty then */
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen if (ftruncate(fd, 0) < 0) {
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen i_error("ftruncate(%s) failed: %m", lock_path);
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen (void)unlink(lock_path);
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen (void)close(fd);
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen return -1;
31a12066e4cd9310d64091c81b59fb8eb1986023Timo Sirainen }
61618d4c58080570f689614fec204ae14e90cef2Timo Sirainen }
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* save the inode info after writing */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen if (fstat(fd, &st) < 0) {
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen i_error("fstat(%s) failed: %m", lock_path);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen (void)close(fd);
8ef7c24178fd798c3e0301c5b8afa1a9bdedd27fTimo Sirainen return -1;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen }
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen
dotlock_r->dev = st.st_dev;
dotlock_r->ino = st.st_ino;
dotlock_r->mtime = st.st_mtime;
if (close(fd) < 0) {
i_error("close(%s) failed: %m", lock_path);
(void)unlink(lock_path);
return -1;
}
return 1;
}
int file_lock_dotlock(const char *path, int checkonly,
unsigned int timeout, unsigned int stale_timeout,
int (*callback)(unsigned int secs_left, int stale,
void *context),
void *context, struct dotlock *dotlock_r)
{
const char *lock_path;
struct lock_info lock_info;
unsigned int stale_notify_threshold;
time_t now, max_wait_time, last_notify;
now = time(NULL);
lock_path = t_strconcat(path, ".lock", NULL);
stale_notify_threshold = stale_timeout / 2;
max_wait_time = now + timeout;
/* There's two ways to do this:
a) Rely on O_EXCL. Historically this hasn't always worked with NFS.
b) Create temp file and link() it to the file we want.
We now use a). It's easier to do and it never leaves temporary files
lying around. Also Postfix relies on it too, so I guess it's safe
enough nowadays.
*/
memset(&lock_info, 0, sizeof(lock_info));
lock_info.path = path;
lock_info.lock_path = lock_path;
lock_info.stale_timeout = stale_timeout;
lock_info.last_change = now;
last_notify = 0;
do {
switch (check_lock(now, &lock_info)) {
case -1:
return -1;
case 0:
if (last_notify != now && callback != NULL) {
unsigned int change_secs;
unsigned int wait_left;
last_notify = now;
change_secs = now - lock_info.last_change;
wait_left = max_wait_time - now;
if (change_secs >= stale_notify_threshold &&
change_secs <= wait_left) {
if (!callback(stale_timeout -
change_secs,
TRUE, context)) {
/* we don't want to override */
lock_info.last_change = now;
}
} else {
(void)callback(wait_left, FALSE,
context);
}
}
usleep(LOCK_RANDOM_USLEEP_TIME);
break;
default:
if (checkonly ||
try_create_lock(lock_path, dotlock_r) > 0)
return 1;
if (errno != EEXIST) {
i_error("open(%s) failed: %m", lock_path);
return -1;
}
break;
}
now = time(NULL);
} while (now < max_wait_time);
errno = EAGAIN;
return 0;
}
int file_unlock_dotlock(const char *path, const struct dotlock *dotlock)
{
const char *lock_path;
struct stat st;
lock_path = t_strconcat(path, ".lock", NULL);
if (lstat(lock_path, &st) < 0) {
if (errno == ENOENT) {
i_warning("Our dotlock file %s was deleted", lock_path);
return 0;
}
i_error("lstat(%s) failed: %m", lock_path);
return -1;
}
if (dotlock->ino != st.st_ino ||
!CMP_DEV_T(dotlock->dev, st.st_dev)) {
i_warning("Our dotlock file %s was overridden", lock_path);
return 0;
}
if (dotlock->mtime != st.st_mtime) {
i_warning("Our dotlock file %s was modified (%s vs %s), "
"assuming it wasn't overridden", lock_path,
dec2str(dotlock->mtime), dec2str(st.st_mtime));
}
if (unlink(lock_path) < 0) {
if (errno == ENOENT) {
i_warning("Our dotlock file %s was deleted", lock_path);
return 0;
}
i_error("unlink(%s) failed: %m", lock_path);
return -1;
}
return 1;
}