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 18 struct 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 27 static 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 35 static void put_filelock(struct fio_filelock *ff) 36 { 37 flist_add(&ff->list, &fld->free_list); 38 } 39 40 static 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 52 static 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 70 int 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; 93 err: 94 fio_filelock_exit(); 95 return 1; 96 } 97 98 void 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 119 static 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 133 static 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 168 static bool __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 true; 184 } 185 186 if (!trylock) { 187 fio_mutex_down(&ff->lock); 188 return false; 189 } 190 191 if (!fio_mutex_down_trylock(&ff->lock)) 192 return false; 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 false; 210 } 211 212 return true; 213 } 214 215 bool fio_trylock_file(const char *fname) 216 { 217 return __fio_lock_file(fname, 1); 218 } 219 220 void fio_lock_file(const char *fname) 221 { 222 __fio_lock_file(fname, 0); 223 } 224 225 void 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