/*
* Really simple exclusive file locking based on filename.
* No hash indexing, just a list, so only works well for < 100 files or
* so. But that's more than what fio needs, so should be fine.
*/
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "flist.h"
#include "filelock.h"
#include "smalloc.h"
#include "mutex.h"
#include "hash.h"
#include "log.h"
struct fio_filelock {
uint32_t hash;
struct fio_mutex lock;
struct flist_head list;
unsigned int references;
};
#define MAX_FILELOCKS 128
static struct filelock_data {
struct flist_head list;
struct fio_mutex lock;
struct flist_head free_list;
struct fio_filelock ffs[MAX_FILELOCKS];
} *fld;
static void put_filelock(struct fio_filelock *ff)
{
flist_add(&ff->list, &fld->free_list);
}
static struct fio_filelock *__get_filelock(void)
{
struct fio_filelock *ff;
if (flist_empty(&fld->free_list))
return NULL;
ff = flist_first_entry(&fld->free_list, struct fio_filelock, list);
flist_del_init(&ff->list);
return ff;
}
static struct fio_filelock *get_filelock(int trylock, int *retry)
{
struct fio_filelock *ff;
do {
ff = __get_filelock();
if (ff || trylock)
break;
fio_mutex_up(&fld->lock);
usleep(1000);
fio_mutex_down(&fld->lock);
*retry = 1;
} while (1);
return ff;
}
int fio_filelock_init(void)
{
int i;
fld = smalloc(sizeof(*fld));
if (!fld)
return 1;
INIT_FLIST_HEAD(&fld->list);
INIT_FLIST_HEAD(&fld->free_list);
if (__fio_mutex_init(&fld->lock, FIO_MUTEX_UNLOCKED))
goto err;
for (i = 0; i < MAX_FILELOCKS; i++) {
struct fio_filelock *ff = &fld->ffs[i];
if (__fio_mutex_init(&ff->lock, FIO_MUTEX_UNLOCKED))
goto err;
flist_add_tail(&ff->list, &fld->free_list);
}
return 0;
err:
fio_filelock_exit();
return 1;
}
void fio_filelock_exit(void)
{
if (!fld)
return;
assert(flist_empty(&fld->list));
__fio_mutex_remove(&fld->lock);
while (!flist_empty(&fld->free_list)) {
struct fio_filelock *ff;
ff = flist_first_entry(&fld->free_list, struct fio_filelock, list);
flist_del_init(&ff->list);
__fio_mutex_remove(&ff->lock);
}
sfree(fld);
fld = NULL;
}
static struct fio_filelock *fio_hash_find(uint32_t hash)
{
struct flist_head *entry;
struct fio_filelock *ff;
flist_for_each(entry, &fld->list) {
ff = flist_entry(entry, struct fio_filelock, list);
if (ff->hash == hash)
return ff;
}
return NULL;
}
static struct fio_filelock *fio_hash_get(uint32_t hash, int trylock)
{
struct fio_filelock *ff;
ff = fio_hash_find(hash);
if (!ff) {
int retry = 0;
ff = get_filelock(trylock, &retry);
if (!ff)
return NULL;
/*
* If we dropped the main lock, re-lookup the hash in case
* someone else added it meanwhile. If it's now there,
* just return that.
*/
if (retry) {
struct fio_filelock *__ff;
__ff = fio_hash_find(hash);
if (__ff) {
put_filelock(ff);
return __ff;
}
}
ff->hash = hash;
ff->references = 0;
flist_add(&ff->list, &fld->list);
}
return ff;
}
static bool __fio_lock_file(const char *fname, int trylock)
{
struct fio_filelock *ff;
uint32_t hash;
hash = jhash(fname, strlen(fname), 0);
fio_mutex_down(&fld->lock);
ff = fio_hash_get(hash, trylock);
if (ff)
ff->references++;
fio_mutex_up(&fld->lock);
if (!ff) {
assert(!trylock);
return true;
}
if (!trylock) {
fio_mutex_down(&ff->lock);
return false;
}
if (!fio_mutex_down_trylock(&ff->lock))
return false;
fio_mutex_down(&fld->lock);
/*
* If we raced and the only reference to the lock is us, we can
* grab it
*/
if (ff->references != 1) {
ff->references--;
ff = NULL;
}
fio_mutex_up(&fld->lock);
if (ff) {
fio_mutex_down(&ff->lock);
return false;
}
return true;
}
bool fio_trylock_file(const char *fname)
{
return __fio_lock_file(fname, 1);
}
void fio_lock_file(const char *fname)
{
__fio_lock_file(fname, 0);
}
void fio_unlock_file(const char *fname)
{
struct fio_filelock *ff;
uint32_t hash;
hash = jhash(fname, strlen(fname), 0);
fio_mutex_down(&fld->lock);
ff = fio_hash_find(hash);
if (ff) {
int refs = --ff->references;
fio_mutex_up(&ff->lock);
if (!refs) {
flist_del_init(&ff->list);
put_filelock(ff);
}
} else
log_err("fio: file not found for unlocking\n");
fio_mutex_up(&fld->lock);
}