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 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
215bool 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