summaryrefslogtreecommitdiff
path: root/src/t_ofd_locks.c
blob: e77f2659b6bd8b58e2cf96ff423cab06725a227a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sched.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>

/*
 * In distributions that do not have these macros ready in glibc-headers,
 * compilation fails. Adding them here to avoid build errors, relevant tests
 * would fail at the helper which requires OFD locks support and notrun if the
 * kernel does not support OFD locks. If the kernel does support OFD locks, we
 * are good to go.
 */
#ifndef F_OFD_GETLK
#define F_OFD_GETLK    36
#endif

#ifndef F_OFD_SETLK
#define F_OFD_SETLK    37
#endif

#ifndef F_OFD_SETLKW
#define F_OFD_SETLKW   38
#endif

/*
 * Usually we run getlk routine after running setlk routine
 * in background. However, getlk could be executed before setlk
 * sometimes, which is invalid for our tests. So we use semaphore
 * to synchronize between getlk and setlk.
 *
 * setlk routine:				 * getlk routine:
 *						 *
 *   start					 *   start
 *     |					 *     |
 *  open file					 *  open file
 *     |					 *     |
 *  init sem					 *     |
 *     |					 *     |
 * wait init sem done				 * wait init sem done
 *     |					 *     |
 *   setlk					 *     |
 *     |					 *     |
 *     |------------clone()--------|		 *     |
 *     |                           |		 *     |
 *     |(parent)            (child)|		 *     |
 *     |                           |		 *     |
 *     |                      close fd		 *     |
 *     |                           |		 *     |
 *     |                     set sem0=0          * wait sem0==0
 *     |                           |		 *     |
 *     |                           |		 *   getlk
 *     |                           |		 *     |
 *  wait sem1==0                   |    	 *  set sem1=0
 *     |                           |		 *     |
 *   wait child                    |    	 *     |
 *     |                           |		 *  check result
 *     |                           |		 *     |
 *    exit                       exit		 *    exit
 */

static int fd;
static int semid;

/* This is required by semctl to set semaphore value */
union semun {
       int              val;    /* Value for SETVAL */
       struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
       unsigned short  *array;  /* Array for GETALL, SETALL */
       struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                   (Linux-specific) */
};

static void err_exit(char *op, int errn)
{
	fprintf(stderr, "%s: %s\n", op, strerror(errn));
	if (fd > 0)
		close(fd);
	if (semid > 0 && semctl(semid, 2, IPC_RMID) == -1)
		perror("exit rmid");
	exit(errn);
}

/*
 * Flags that used to specify operation details.
 * They can be specified via command line options.
 *
 * option: -P
 * posix : 1 <--> test posix lock
 *	   0 <--> test OFD lock (default)
 *
 * option: -s/-g
 * lock_cmd : 1 <--> setlk (default)
 *	      0 <--> getlk
 *
 * option: -r/-w
 * lock_rw : 1 <--> set/get wrlck (default)
 *	     0 <--> set/get rdlck
 *
 * option: -o num
 * lock_start : l_start to getlk
 *
 * option: -F
 * clone_fs : clone with CLONE_FILES
 *
 * option: -d
 * use_dup : dup and close to setup condition in setlk
 *
 * option: -R/-W
 * open_rw : 1 <--> open file RDWR (default)
 *	     0 <--> open file RDONLY
 *
 * This option is for _require_ofd_locks helper, just do
 * fcntl setlk then return errno.
 * option: -t
 * testrun : 1 <--> this is a testrun, return after setlk
 *	     0 <--> this is not a testrun, run as usual
 */

static void usage(char *arg0)
{
	printf("Usage: %s [-sgrwo:l:RWPtFd] filename\n", arg0);
	printf("\t-s/-g : to setlk or to getlk\n");
	printf("\t-P : POSIX locks\n");
	printf("\t-F : clone with CLONE_FILES in setlk to setup test condition\n");
	printf("\t-d : dup and close in setlk\n");
	printf("\twithout both -F/d, use clone without CLONE_FILES\n");
	printf("\t-r/-w : set/get rdlck/wrlck\n");
	printf("\t-o num : offset start to lock, default 0\n");
	printf("\t-l num : lock length, default 10\n");
	printf("\t-R/-W : open file RDONLY/RDWR\n\n");
	printf("\tUsually we run a setlk routine in background and then\n");
	printf("\trun a getlk routine to check. They must be paired, or\n");
	printf("\ttest will hang.\n\n");
	exit(0);
}

#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE] __attribute__((aligned));

static int child_fn(void* p)
{
	union semun semu;
	int cfd = *(int *)p;

	/* close relative fd */
	if (cfd > 0 && close(cfd) == -1)
		perror("close in child");

	/* set sem0 = 0 (setlk and close fd done) */
	semu.val = 0;
	if (semctl(semid, 0, SETVAL, semu) == -1)
		err_exit("set sem0 0", errno);

	return 0;
}

int main(int argc, char **argv)
{
	int posix = 0;
	int lock_cmd = 1;
	int lock_rw = 1;
	int lock_start = 0;
	int lock_l = 10;
	int open_rw = 1;
	int clone_fs = 0;
	int use_dup = 0;
	int testrun = 0;
	int setlk_macro = F_OFD_SETLKW;
	int getlk_macro = F_OFD_GETLK;
	struct timespec ts;
	key_t semkey;
	unsigned short vals[2];
	union semun semu;
	struct semid_ds sem_ds;
	struct sembuf sop;
	int opt, ret, retry;

	//avoid libcap errno bug
	errno = 0;
	while((opt = getopt(argc, argv, "sgrwo:l:PRWtFd")) != -1) {
		switch(opt) {
		case 's':
			lock_cmd = 1;
			break;
		case 'g':
			lock_cmd = 0;
			break;
		case 'r':
			lock_rw = 0;
			break;
		case 'w':
			lock_rw = 1;
			break;
		case 'o':
			lock_start = atoi(optarg);
			break;
		case 'l':
			lock_l = atoi(optarg);
			break;
		case 'P':
			posix = 1;
			break;
		case 'R':
			open_rw = 0;
			break;
		case 'W':
			open_rw = 1;
			break;
		case 't':
			testrun = 1;
			break;
		case 'F':
			clone_fs = 1;
			break;
		case 'd':
			use_dup = 1;
			break;
		default:
			usage(argv[0]);
			return -1;
		}
	}

	if (optind >= argc) {
		usage(argv[0]);
		return -1;
	}

	struct flock flk = {
		.l_whence = SEEK_SET,
		.l_start = lock_start,
		.l_len = lock_l,
		.l_type = F_RDLCK,
	};

	if (posix == 0) {
		/* OFD lock requires l_pid to be zero */
		flk.l_pid = 0;
		setlk_macro = F_OFD_SETLKW;
		getlk_macro = F_OFD_GETLK;
	} else {
		setlk_macro = F_SETLKW;
		getlk_macro = F_GETLK;
	}

	if (lock_rw == 1)
		flk.l_type = F_WRLCK;
	else
		flk.l_type = F_RDLCK;

	if (open_rw == 0)
		fd = open(argv[optind], O_RDONLY);
	else
		fd = open(argv[optind], O_RDWR);
	if (fd == -1)
		err_exit("open", errno);

	/*
	 * In a testun, we do a fcntl getlk call and exit
	 * immediately no matter it succeeds or not.
	 */
	if (testrun == 1) {
		fcntl(fd, F_OFD_GETLK, &flk);
		err_exit("test_ofd_getlk", errno);
	}

	if((semkey = ftok(argv[optind], 255)) == -1)
		err_exit("ftok", errno);

	/* setlk, and always init the semaphore at setlk time */
	if (lock_cmd == 1) {
		/*
		 * Init the semaphore, with a key related to the testfile.
		 * getlk routine will wait untill this sem has been created and
		 * iniialized.
		 *
		 * We must make sure the semaphore set is newly created, rather
		 * then the one left from last run. In which case getlk will
		 * exit immediately and left setlk routine waiting forever.
		 * Also because newly created semaphore has zero sem_otime,
		 * which is used here to sync with getlk routine.
		 */
		retry = 0;
		do {
			semid = semget(semkey, 2, IPC_CREAT|IPC_EXCL);
			if (semid < 0 && errno == EEXIST) {
				/* remove sem set after one round of test */
				if (semctl(semid, 2, IPC_RMID, semu) == -1)
					err_exit("rmid 0", errno);
				retry++;
			} else if (semid < 0)
				err_exit("semget", errno);
			else
				retry = 10;
		} while (retry < 5);
		/* We can't create a new semaphore set in 5 tries */
		if (retry == 5)
			err_exit("semget", errno);

		/* Init both new sem to 1 */
		vals[0] = 1;
		vals[1] = 1;
		semu.array = vals;
		if (semctl(semid, 2, SETALL, semu) == -1)
			err_exit("init sem", errno);
		/* Inc both new sem to 2 */
		sop.sem_num = 0;
		sop.sem_op = 1;
		sop.sem_flg = 0;
		ts.tv_sec = 5;
		ts.tv_nsec = 0;
		if (semtimedop(semid, &sop, 1, &ts) == -1)
			err_exit("inc sem0 2", errno);
		sop.sem_num = 1;
		sop.sem_op = 1;
		sop.sem_flg = 0;
		ts.tv_sec = 5;
		ts.tv_nsec = 0;
		if (semtimedop(semid, &sop, 1, &ts) == -1)
			err_exit("inc sem1 2", errno);

		/*
		 * Wait initialization complete. semctl(2) only update
		 * sem_ctime, semop(2) will update sem_otime.
		 */
		ret = -1;
		do {
			memset(&sem_ds, 0, sizeof(sem_ds));
			semu.buf = &sem_ds;
			ret = semctl(semid, 0, IPC_STAT, semu);
		} while (!(ret == 0 && sem_ds.sem_otime != 0));

		/* place the lock */
		if (fcntl(fd, setlk_macro, &flk) < 0)
			err_exit("setlkw", errno);

		if (use_dup == 1) {
			/* dup fd and close the newfd */
			int dfd = dup(fd);
			if (dfd == -1)
				err_exit("dup", errno);
			close(dfd);
			/* set sem0 = 0 (setlk and close fd done) */
			semu.val = 0;
			if (semctl(semid, 0, SETVAL, semu) == -1)
				err_exit("set sem0 0", errno);
		} else {
			/*
			 * clone a child to close the fd then tell getlk to go;
			 * in parent we keep holding the lock till getlk done.
			 */
			pid_t child_pid = 0;
			if (clone_fs)
				child_pid = clone(child_fn, child_stack+STACK_SIZE,
					CLONE_FILES|CLONE_SYSVSEM|SIGCHLD, &fd);
			else
				child_pid = clone(child_fn, child_stack+STACK_SIZE,
					CLONE_SYSVSEM|SIGCHLD, &fd);
			if (child_pid == -1)
				err_exit("clone", errno);
			/* wait child done */
			waitpid(child_pid, NULL, 0);
		}

		/* "hold" lock and wait sem1 == 0 (getlk done) */
		sop.sem_num = 1;
		sop.sem_op = 0;
		sop.sem_flg = 0;
		ts.tv_sec = 5;
		ts.tv_nsec = 0;
		if (semtimedop(semid, &sop, 1, &ts) == -1)
			err_exit("wait sem1 0", errno);

		/* remove sem set after one round of test */
		if (semctl(semid, 2, IPC_RMID, semu) == -1)
			err_exit("rmid", errno);
		close(fd);
		exit(0);
	}

	/* getlck */
	if (lock_cmd == 0) {
		/* wait sem created and initialized */
		retry = 5;
		do {
			semid = semget(semkey, 2, 0);
			if (semid != -1)
				break;
			if (errno == ENOENT && retry) {
				sleep(1);
				retry--;
				continue;
			} else {
				err_exit("getlk_semget", errno);
			}
		} while (1);
		do {
			memset(&sem_ds, 0, sizeof(sem_ds));
			semu.buf = &sem_ds;
			ret = semctl(semid, 0, IPC_STAT, semu);
		} while (!(ret == 0 && sem_ds.sem_otime != 0));

		/* wait sem0 == 0 (setlk and close fd done) */
		sop.sem_num = 0;
		sop.sem_op = 0;
		sop.sem_flg = 0;
		ts.tv_sec = 5;
		ts.tv_nsec = 0;
		if (semtimedop(semid, &sop, 1, &ts) == -1)
			err_exit("wait sem0 0", errno);

		if (fcntl(fd, getlk_macro, &flk) < 0)
			err_exit("getlk", errno);

		/* set sem1 = 0 (getlk done) */
		semu.val = 0;
		if (semctl(semid, 1, SETVAL, semu) == -1)
			err_exit("set sem1 0", errno);

		/* check result */
		switch (flk.l_type) {
		case F_UNLCK:
			printf("lock could be placed\n");
			break;
		case F_RDLCK:
			printf("get rdlck\n");
			break;
		case F_WRLCK:
			printf("get wrlck\n");
			break;
		default:
			printf("unknown lock type\n");
			break;
		}
		close(fd);
	}
	return 0;
}