1 /*
2  * Copyright (c) 2005 Proofpoint, Inc. and its suppliers.
3  *	All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  *
9  */
10 
11 #include <sm/gen.h>
12 SM_IDSTR(id, "@(#)$Id: t-lockfile.c,v 1.2 2013-11-22 20:51:50 ca Exp $")
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <sendmail.h>
16 
17 #define IOBUFSZ	64
18 char iobuf[IOBUFSZ];
19 #define FIRSTLINE	"first line\n"
20 #define LASTLINE	"last line\n"
21 static int noio, chk;
22 static pid_t pid;
23 
24 /*
25 **  OPENFILE -- open a file
26 **
27 **	Parameters:
28 **		owner -- create file?
29 **		filename -- name of file.
30 **		flags -- flags for open(2)
31 **
32 **	Returns:
33 **		>=0 fd
34 **		<0 on failure.
35 */
36 
37 static int
38 openfile(owner, filename, flags)
39 	int owner;
40 	char *filename;
41 	int flags;
42 {
43 	int fd;
44 
45 	if (owner)
46 		flags |= O_CREAT;
47 	fd = open(filename, flags, 0640);
48 	if (fd >= 0)
49 		return fd;
50 	fprintf(stderr, "%d: %ld: owner=%d, open(%s) failed\n",
51 		(int) pid, (long) time(NULL), owner, filename);
52 	return -1;
53 }
54 
55 /*
56 **  WRBUF -- write iobuf to fd
57 **
58 **	Parameters:
59 **		fd -- file descriptor.
60 **
61 **	Returns:
62 **		==0 write was ok
63 **		!=0 on failure.
64 */
65 
66 static int
67 wrbuf(fd)
68 	int fd;
69 {
70 	int r;
71 
72 	if (noio)
73 		return 0;
74 	r = write(fd, iobuf, sizeof(iobuf));
75 	if (sizeof(iobuf) == r)
76 		return 0;
77 	fprintf(stderr, "%d: %ld: owner=1, write(%s)=fail\n",
78 		(int) pid, (long) time(NULL), iobuf);
79 	return 1;
80 }
81 
82 /*
83 **  RDBUF -- read from fd
84 **
85 **	Parameters:
86 **		fd -- file descriptor.
87 **		xbuf -- expected content.
88 **
89 **	Returns:
90 **		==0 read was ok and content matches
91 **		!=0 otherwise
92 */
93 
94 static int
95 rdbuf(fd, xbuf)
96 	int fd;
97 	const char *xbuf;
98 {
99 	int r;
100 
101 	if (noio)
102 		return 0;
103 	r = read(fd, iobuf, sizeof(iobuf));
104 	if (sizeof(iobuf) != r)
105 	{
106 		fprintf(stderr, "%d: %ld: owner=0, read()=fail\n",
107 			(int) pid, (long) time(NULL));
108 		return 1;
109 	}
110 	if (strncmp(iobuf, xbuf, strlen(xbuf)))
111 	{
112 		fprintf(stderr, "%d: %ld: owner=0, read=%s expected=%s\n",
113 			(int) pid, (long) time(NULL), iobuf, xbuf);
114 		return 1;
115 	}
116 	return 0;
117 }
118 
119 /*
120 **  LOCKTESTWR -- test WR/EX file locking
121 **
122 **	Parameters:
123 **		owner -- create file?
124 **		filename -- name of file.
125 **		flags -- flags for open(2)
126 **		delay -- how long to keep file locked?
127 **
128 **	Returns:
129 **		0 on success
130 **		!= 0 on failure.
131 */
132 
133 #define DBGPRINTR(str)	\
134 	do	\
135 	{	\
136 		fprintf(stderr, "%d: %ld: owner=0, ", (int) pid,	\
137 			(long) time(NULL));	\
138 		fprintf(stderr, str, filename, shared ? "RD" : "EX");	\
139 	} while (0)
140 
141 static int
142 locktestwr(filename, flags, delay)
143 	char *filename;
144 	int flags;
145 	int delay;
146 {
147 	int fd;
148 	bool locked;
149 
150 	fd = openfile(1, filename, flags);
151 	if (fd < 0)
152 		return errno;
153 	locked = lockfile(fd, filename, "[owner]", LOCK_EX);
154 	if (!locked)
155 	{
156 		fprintf(stderr, "%d: %ld: owner=1, lock(%s) failed\n",
157 			(int) pid, (long) time(NULL), filename);
158 		return 1;
159 	}
160 	else
161 		fprintf(stderr, "%d: %ld: owner=1, lock(%s) ok\n",
162 			(int) pid, (long) time(NULL), filename);
163 
164 	sm_strlcpy(iobuf, FIRSTLINE, sizeof(iobuf));
165 	if (wrbuf(fd))
166 		return 1;
167 	if (delay > 0)
168 		sleep(delay);
169 	sm_strlcpy(iobuf, LASTLINE, sizeof(iobuf));
170 	if (wrbuf(fd))
171 		return 1;
172 	locked = lockfile(fd, filename, "[owner]", LOCK_UN);
173 	if (!locked)
174 	{
175 		fprintf(stderr, "%d: %ld: owner=1, unlock(%s) failed\n",
176 			(int) pid, (long) time(NULL), filename);
177 		return 1;
178 	}
179 	fprintf(stderr, "%d: %ld: owner=1, unlock(%s) done\n",
180 		(int) pid, (long) time(NULL), filename);
181 	if (fd > 0)
182 	{
183 		close(fd);
184 		fd = -1;
185 	}
186 	return 0;
187 }
188 
189 /*
190 **  CHKLCK -- check whether fd is locked (only for fcntl())
191 **
192 **	Parameters:
193 **		owner -- create file?
194 **		filename -- name of file.
195 **		flags -- flags for open(2)
196 **		delay -- how long to keep file locked?
197 **
198 **	Returns:
199 **		0 if not locked
200 **		>0 pid of process which holds a WR lock
201 **		<0 error
202 */
203 
204 static long
205 chklck(fd)
206 	int fd;
207 {
208 #if !HASFLOCK
209 	int action, i;
210 	struct flock lfd;
211 
212 	(void) memset(&lfd, '\0', sizeof lfd);
213 	lfd.l_type = F_RDLCK;
214 	action = F_GETLK;
215 	while ((i = fcntl(fd, action, &lfd)) < 0 && errno == EINTR)
216 		continue;
217 	if (i < 0)
218 		return (long)i;
219 	if (F_WRLCK == lfd.l_type)
220 		return (long)lfd.l_pid;
221 	return 0L;
222 #else /* !HASFLOCK */
223 	fprintf(stderr, "%d: %ld: flock(): no lock test\n",
224 		(int) pid, (long) time(NULL));
225 	return -1L;
226 #endif /* !HASFLOCK */
227 }
228 
229 /*
230 **  LOCKTESTRD -- test file locking for reading
231 **
232 **	Parameters:
233 **		filename -- name of file.
234 **		flags -- flags for open(2)
235 **		delay -- how long is file locked by owner?
236 **		shared -- LOCK_{EX/SH}
237 **
238 **	Returns:
239 **		0 on success
240 **		!= 0 on failure.
241 */
242 
243 static int
244 locktestrd(filename, flags, delay, shared)
245 	char *filename;
246 	int flags;
247 	int delay;
248 	int shared;
249 {
250 	int fd, cnt;
251 	int lt;
252 	bool locked;
253 
254 	fd = openfile(0, filename, flags);
255 	if (fd < 0)
256 		return errno;
257 	if (chk)
258 	{
259 		long locked;
260 
261 		locked = chklck(fd);
262 		if (locked > 0)
263 			fprintf(stderr, "%d: %ld: file=%s status=locked pid=%ld\n",
264 				 (int) pid, (long) time(NULL), filename, locked);
265 		else if (0 == locked)
266 			fprintf(stderr, "%d: %ld: file=%s status=not_locked\n",
267 				 (int) pid, (long) time(NULL), filename);
268 		else
269 			fprintf(stderr, "%d: %ld: file=%s status=unknown\n",
270 				 (int) pid, (long) time(NULL), filename);
271 		goto end;
272 	}
273 
274 	if (shared)
275 		lt = LOCK_SH;
276 	else
277 		lt = LOCK_EX;
278 
279 	for (cnt = 0; cnt < delay - 2; cnt++)
280 	{
281 		/* try to get lock: should fail (nonblocking) */
282 		locked = lockfile(fd, filename, "[client]", lt|LOCK_NB);
283 		if (locked)
284 		{
285 			DBGPRINTR("lock(%s)=%s succeeded\n");
286 			return 1;
287 		}
288 		sleep(1);
289 	}
290 	if (delay > 0)
291 		sleep(2);
292 	locked = lockfile(fd, filename, "[client]", lt);
293 	if (!locked)
294 	{
295 		DBGPRINTR("lock(%s)=%s failed\n");
296 		return 1;
297 	}
298 	DBGPRINTR("lock(%s)=%s ok\n");
299 	if (rdbuf(fd, FIRSTLINE))
300 		return 1;
301 	if (rdbuf(fd, LASTLINE))
302 		return 1;
303 	sleep(1);
304 	locked = lockfile(fd, filename, "[client]", LOCK_UN);
305 	if (!locked)
306 	{
307 		DBGPRINTR("unlock(%s)=%s failed\n");
308 		return 1;
309 	}
310 	DBGPRINTR("unlock(%s)=%s done\n");
311 
312   end:
313 	if (fd > 0)
314 	{
315 		close(fd);
316 		fd = -1;
317 	}
318 	return 0;
319 }
320 
321 /*
322 **  USAGE -- show usage
323 **
324 **	Parameters:
325 **		prg -- name of program
326 **
327 **	Returns:
328 **		nothing.
329 */
330 
331 static void
332 usage(prg)
333 	const char *prg;
334 {
335 	fprintf(stderr, "usage: %s [options]\n"
336 		"-f filename	use filename\n"
337 		"-i		do not perform I/O\n"
338 		"-n		do not try non-blocking locking first\n"
339 		"-R		only start reader process\n"
340 		"-r		use shared locking for reader\n"
341 		"-s delay	sleep delay seconds before unlocking\n"
342 		"-W		only start writer process\n"
343 #if !HASFLOCK
344 		"uses fcntl()\n"
345 #else
346 		"uses flock()\n"
347 #endif
348 
349 		, prg);
350 }
351 
352 int
353 main(argc, argv)
354 	int argc;
355 	char *argv[];
356 {
357 	int ch, delay, r, status, flags, shared, nb, reader, writer;
358 	char *filename;
359 	pid_t fpid;
360 	extern char *optarg;
361 
362 	delay = 5;
363 	filename = "testlock";
364 	flags = O_RDWR;
365 	shared = nb = noio = reader = writer = chk = 0;
366 #define OPTIONS	"cf:inRrs:W"
367 	while ((ch = getopt(argc, argv, OPTIONS)) != -1)
368 	{
369 		switch ((char) ch)
370 		{
371 		  case 'c':
372 			chk = 1;
373 			break;
374 
375 		  case 'f':
376 			filename = optarg;
377 			break;
378 
379 		  case 'i':
380 			noio = 1;
381 			break;
382 
383 		  case 'n':
384 			nb = 0;
385 			break;
386 
387 		  case 'R':
388 			reader = 1;
389 			break;
390 
391 		  case 'r':
392 			shared = 1;
393 			break;
394 
395 		  case 's':
396 			delay = atoi(optarg);
397 			break;
398 
399 		  case 'W':
400 			writer = 1;
401 			break;
402 
403 		  default:
404 			usage(argv[0]);
405 			exit(69);
406 			break;
407 		}
408 	}
409 
410 	fpid = -1;
411 	if (0 == reader && 0 == writer && (fpid = fork()) < 0)
412 	{
413 		perror("fork failed\n");
414 		return 1;
415 	}
416 
417 	r = 0;
418 	if (reader || fpid == 0)
419 	{
420 		/* give the parent the chance to set up data */
421 		pid = getpid();
422 		sleep(1);
423 		r = locktestrd(filename, flags, nb ? delay : 0, shared);
424 	}
425 	if (writer || fpid > 0)
426 	{
427 		fpid = getpid();
428 		r = locktestwr(filename, flags, delay);
429 		(void) wait(&status);
430 	}
431 	/* (void) unlink(filename); */
432 	return r;
433 }
434