1/* 2 * Really simple exclusive file locking based on filename. 3 * No hash indexing, just a list, so only works well for < 100 files or 4 * so. But that's more than what fio needs, so should be fine. 5 */ 6#include <inttypes.h> 7#include <string.h> 8#include <unistd.h> 9#include <assert.h> 10 11#include "flist.h" 12#include "filelock.h" 13#include "smalloc.h" 14#include "mutex.h" 15#include "hash.h" 16#include "log.h" 17 18struct fio_filelock { 19 uint32_t hash; 20 struct fio_mutex lock; 21 struct flist_head list; 22 unsigned int references; 23}; 24 25#define MAX_FILELOCKS 128 26 27static struct filelock_data { 28 struct flist_head list; 29 struct fio_mutex lock; 30 31 struct flist_head free_list; 32 struct fio_filelock ffs[MAX_FILELOCKS]; 33} *fld; 34 35static void put_filelock(struct fio_filelock *ff) 36{ 37 flist_add(&ff->list, &fld->free_list); 38} 39 40static struct fio_filelock *__get_filelock(void) 41{ 42 struct fio_filelock *ff; 43 44 if (flist_empty(&fld->free_list)) 45 return NULL; 46 47 ff = flist_first_entry(&fld->free_list, struct fio_filelock, list); 48 flist_del_init(&ff->list); 49 return ff; 50} 51 52static struct fio_filelock *get_filelock(int trylock, int *retry) 53{ 54 struct fio_filelock *ff; 55 56 do { 57 ff = __get_filelock(); 58 if (ff || trylock) 59 break; 60 61 fio_mutex_up(&fld->lock); 62 usleep(1000); 63 fio_mutex_down(&fld->lock); 64 *retry = 1; 65 } while (1); 66 67 return ff; 68} 69 70int fio_filelock_init(void) 71{ 72 int i; 73 74 fld = smalloc(sizeof(*fld)); 75 if (!fld) 76 return 1; 77 78 INIT_FLIST_HEAD(&fld->list); 79 INIT_FLIST_HEAD(&fld->free_list); 80 81 if (__fio_mutex_init(&fld->lock, FIO_MUTEX_UNLOCKED)) 82 goto err; 83 84 for (i = 0; i < MAX_FILELOCKS; i++) { 85 struct fio_filelock *ff = &fld->ffs[i]; 86 87 if (__fio_mutex_init(&ff->lock, FIO_MUTEX_UNLOCKED)) 88 goto err; 89 flist_add_tail(&ff->list, &fld->free_list); 90 } 91 92 return 0; 93err: 94 fio_filelock_exit(); 95 return 1; 96} 97 98void fio_filelock_exit(void) 99{ 100 if (!fld) 101 return; 102 103 assert(flist_empty(&fld->list)); 104 __fio_mutex_remove(&fld->lock); 105 106 while (!flist_empty(&fld->free_list)) { 107 struct fio_filelock *ff; 108 109 ff = flist_first_entry(&fld->free_list, struct fio_filelock, list); 110 111 flist_del_init(&ff->list); 112 __fio_mutex_remove(&ff->lock); 113 } 114 115 sfree(fld); 116 fld = NULL; 117} 118 119static struct fio_filelock *fio_hash_find(uint32_t hash) 120{ 121 struct flist_head *entry; 122 struct fio_filelock *ff; 123 124 flist_for_each(entry, &fld->list) { 125 ff = flist_entry(entry, struct fio_filelock, list); 126 if (ff->hash == hash) 127 return ff; 128 } 129 130 return NULL; 131} 132 133static struct fio_filelock *fio_hash_get(uint32_t hash, int trylock) 134{ 135 struct fio_filelock *ff; 136 137 ff = fio_hash_find(hash); 138 if (!ff) { 139 int retry = 0; 140 141 ff = get_filelock(trylock, &retry); 142 if (!ff) 143 return NULL; 144 145 /* 146 * If we dropped the main lock, re-lookup the hash in case 147 * someone else added it meanwhile. If it's now there, 148 * just return that. 149 */ 150 if (retry) { 151 struct fio_filelock *__ff; 152 153 __ff = fio_hash_find(hash); 154 if (__ff) { 155 put_filelock(ff); 156 return __ff; 157 } 158 } 159 160 ff->hash = hash; 161 ff->references = 0; 162 flist_add(&ff->list, &fld->list); 163 } 164 165 return ff; 166} 167 168static int __fio_lock_file(const char *fname, int trylock) 169{ 170 struct fio_filelock *ff; 171 uint32_t hash; 172 173 hash = jhash(fname, strlen(fname), 0); 174 175 fio_mutex_down(&fld->lock); 176 ff = fio_hash_get(hash, trylock); 177 if (ff) 178 ff->references++; 179 fio_mutex_up(&fld->lock); 180 181 if (!ff) { 182 assert(!trylock); 183 return 1; 184 } 185 186 if (!trylock) { 187 fio_mutex_down(&ff->lock); 188 return 0; 189 } 190 191 if (!fio_mutex_down_trylock(&ff->lock)) 192 return 0; 193 194 fio_mutex_down(&fld->lock); 195 196 /* 197 * If we raced and the only reference to the lock is us, we can 198 * grab it 199 */ 200 if (ff->references != 1) { 201 ff->references--; 202 ff = NULL; 203 } 204 205 fio_mutex_up(&fld->lock); 206 207 if (ff) { 208 fio_mutex_down(&ff->lock); 209 return 0; 210 } 211 212 return 1; 213} 214 215int fio_trylock_file(const char *fname) 216{ 217 return __fio_lock_file(fname, 1); 218} 219 220void fio_lock_file(const char *fname) 221{ 222 __fio_lock_file(fname, 0); 223} 224 225void fio_unlock_file(const char *fname) 226{ 227 struct fio_filelock *ff; 228 uint32_t hash; 229 230 hash = jhash(fname, strlen(fname), 0); 231 232 fio_mutex_down(&fld->lock); 233 234 ff = fio_hash_find(hash); 235 if (ff) { 236 int refs = --ff->references; 237 fio_mutex_up(&ff->lock); 238 if (!refs) { 239 flist_del_init(&ff->list); 240 put_filelock(ff); 241 } 242 } else 243 log_err("fio: file not found for unlocking\n"); 244 245 fio_mutex_up(&fld->lock); 246} 247