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 
put_filelock(struct fio_filelock * ff)35 static void put_filelock(struct fio_filelock *ff)
36 {
37 	flist_add(&ff->list, &fld->free_list);
38 }
39 
__get_filelock(void)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 
get_filelock(int trylock,int * retry)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 
fio_filelock_init(void)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 
fio_filelock_exit(void)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 
fio_hash_find(uint32_t hash)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 
fio_hash_get(uint32_t hash,int trylock)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 
__fio_lock_file(const char * fname,int trylock)168 static 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 
fio_trylock_file(const char * fname)215 int fio_trylock_file(const char *fname)
216 {
217 	return __fio_lock_file(fname, 1);
218 }
219 
fio_lock_file(const char * fname)220 void fio_lock_file(const char *fname)
221 {
222 	__fio_lock_file(fname, 0);
223 }
224 
fio_unlock_file(const char * fname)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