File: util_lockfile.c

package info (click to toggle)
s390-tools 2.40.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,288 kB
  • sloc: ansic: 187,079; sh: 12,157; cpp: 5,049; makefile: 2,812; perl: 2,541; asm: 1,097; python: 697; xml: 29
file content (378 lines) | stat: -rw-r--r-- 10,142 bytes parent folder | download | duplicates (2)
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
/*
 * util - Utility function library
 *
 * Created file-based locks
 *
 * Copyright IBM Corp. 2022
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "lib/util_libc.h"
#include "lib/util_lockfile.h"
#include "lib/util_panic.h"

#define WAITPID 120 /* Time to wait for pid to be written */
#define DEF_WAITINC_US 5000000   /* Additional time to wait each retry */
#define DEF_MAXWAIT_US 60000000  /* Maximum wait between retries */
#define BUFSIZE 40  /* Buffer must be large enough to fit pid string */

/**
 * Check if there is a process that exists with the specified identifier.
 *
 * @param[in]      pid        Process Identifier to check
 *
 * @retval         true       Process exists or we lack privelege to check
 * @retval         false      Process does not exist
 */
static bool pid_exists(int pid)
{
	int rc;

	/* Use sig 0 to determine if the owner is still alive */
	rc = kill(pid, 0);
	if (rc != 0) {
		switch (errno) {
		case EPERM:
			/* Privilege issue, just assume PID exists */
			break;
		case ESRCH:
			/* PID does not exist, this lock is stale */
			return false;
		default:
			util_assert(false, "Unexpected return from kill: %d\n", rc);
			break;
		}
	}

	return true;
}

/**
 * Check for an existing lock that is deemed stale (either the associated PID
 * is gone or no PID has been written to the file in a reasonable timeframe).
 * In the case a stale lock is found, remove it.
 *
 * @param[in]      lockfile   Path to the lock file
 *
 * @retval         0          Either no lock or stale lock was found and removed
 * @retval         1          Lock is held by another PID and is not stale
 */
static int handle_stale_lock(char *lockfile)
{
	int fd, rc, pid, len;
	struct stat info;
	char buf[BUFSIZE];
	time_t curr;

	fd = open(lockfile, O_RDONLY);

	if (fd >= 0) {
		/* Lock exists, see who owns it */
		len = read(fd, buf, sizeof(buf));
		if (len > 0) {
			buf[len] = 0; /* Ensure null terminated string */
			pid = atoi(buf);
			if (!pid_exists(pid)) {
				/* Stale lock detected unlink and retry now */
				close(fd);
				unlink(lockfile);
				return 0;
			} else if (pid != 0) {
				/* Lock is held by an active pid, delay */
				close(fd);
				return 1;
			}
			/*
			 * If we reach this point, the PID was 0 which is
			 * unexpected.  Proceed under the assumption that the
			 * proper PID hasn't been written yet.
			 */
		}
		/*
		 * PID hasn't been written yet?  Either a bad lock or a very
		 * new one.
		 */
		time(&curr);
		rc = fstat(fd, &info);
		close(fd);
		if (rc != 0) {
			/* Can't read file anymore, retry now */
			return 0;
		}
		if (curr > info.st_mtime + WAITPID) {
			/*
			 * PID should be in the file within 2 minutes,
			 * something went wrong.  Treat as stale.
			 */
			unlink(lockfile);
			return 0;
		}
		/* Otherwise, assume file was newly created and delay */
		return 1;
	}

	/* Couldn't open, try again immediately */
	return 0;
}

/**
 * Attempt to create a lockfile at the specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      retries    Number of times to retry if lock fails initially
 * @param[in]      waitinc    How many micro-seconds to extend wait time before
 *                            additional retry
 * @param[in]      maxwait    Maximum wait time before retry
 * @param[in]      pid        PID to use for lock ownership
 *
 * @retval         0          Lock created with PID as owner
 * @retval         !=0        Lock was not created
 */
static int do_lockfile_lock(char *lockfile, unsigned int retries, int pid,
			    unsigned int waitinc, unsigned int maxwait)
{
	unsigned int tries = retries + 1;
	int fd, plen, len, rc = 0;
	unsigned int snooze = 0;
	char buf[BUFSIZE];
	char *tpath;

	if (!lockfile)
		return UTIL_LOCKFILE_ERR;

	plen = snprintf(buf, sizeof(buf), "%d\n", pid);
	if (plen < 0 || (plen > ((int)sizeof(buf) - 1)))
		return UTIL_LOCKFILE_ERR;

	/* Allocate temporary lock file with a sufficiently unique path */
	len = util_asprintf(&tpath, "%s%05d%02x", lockfile, getpid(),
			    (unsigned int)time(NULL) & 255);
	if (len < 0)
		return UTIL_LOCKFILE_ERR;

	/* Open the temporary lockfile, write the specified pid */
	fd = open(tpath, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
	if (fd < 0) {
		rc = UTIL_LOCKFILE_ERR;
		goto out;
	}

	len = write(fd, buf, plen);
	if (close(fd) != 0) {
		rc = UTIL_LOCKFILE_ERR;
		goto cleanup;
	}
	if (len != plen) {
		/* Failed to write the temp lockfile, bail out */
		rc = UTIL_LOCKFILE_ERR;
		goto cleanup;
	}

	/* Link the temprorary file to the real path */
	do {
		rc = link(tpath, lockfile);
		if (rc == 0) {
			/* Lock successfully acquired */
			rc = UTIL_LOCKFILE_OK;
			goto cleanup;
		}
		/* Lock already held - check for stale lock */
		rc = handle_stale_lock(lockfile);
		/* Only wait if the lock was not stale */
		if (rc != 0) {
			tries--;
			if (tries > 0) {
				snooze += waitinc;
				snooze = (snooze > maxwait) ? maxwait : snooze;
				usleep(snooze);
			}
		}
	} while (tries > 0);

	/* Exhausted specified number of retries, exit on failure */
	rc = UTIL_LOCKFILE_LOCK_FAIL;

cleanup:
	unlink(tpath);
	free(tpath);
out:
	return rc;
}

/**
 * Attempt to release a lockfile at the specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      pid        PID that should own the lock
 *
 * @retval         0          Lock released
 * @retval         !=0        Lock was not released or did not exist
 */
static int do_lockfile_release(char *lockfile, int pid)
{
	int fd, len, lpid;
	char buf[BUFSIZE];

	if (!lockfile)
		return UTIL_LOCKFILE_ERR;

	/* Open lockfile, read the owning pid if it exists */
	fd = open(lockfile, O_RDONLY);
	if (fd < 0)
		return UTIL_LOCKFILE_RELEASE_NONE;
	len = read(fd, buf, sizeof(buf));
	close(fd);
	if (len <= 0)
		return UTIL_LOCKFILE_RELEASE_FAIL;
	buf[len] = 0;
	lpid = atoi(buf);

	/* Only release the lock if its held by the right pid */
	if (pid != lpid)
		return UTIL_LOCKFILE_RELEASE_FAIL;

	unlink(lockfile);

	return 0;
}

/**
 * Attempt to create a lockfile owned by this process at the specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      retries    Number of times to retry if lock fails initially
 *
 * @retval         0          Lock created
 * @retval         !=0        Lock was not created
 */
int util_lockfile_lock(char *lockfile, int retries)
{
	return do_lockfile_lock(lockfile, retries, getpid(), DEF_WAITINC_US,
				DEF_MAXWAIT_US);
}

/**
 * Attempt to create a lockfile owned by this process at the specified path
 * using a custom wait/retry time.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      retries    Number of times to retry if lock fails initially
 * @param[in]      waitinc    How many micro-seconds to extend wait time before
 *                            additional retry
 * @param[in]      maxwait    Maximum wait time before retry
 *
 * @retval         0          Lock created
 * @retval         !=0        Lock was not created
 */
int util_lockfile_lock_cw(char *lockfile, int retries, unsigned int waitinc,
			  unsigned int maxwait)
{
	return do_lockfile_lock(lockfile, retries, getpid(), waitinc, maxwait);
}

/**
 * Attempt to create a lockfile owned by the parent of this process at the
 * specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      retries    Number of times to retry if lock fails initially
 *
 * @retval         0          Lock created
 * @retval         !=0        Lock was not created
 */
int util_lockfile_parent_lock(char *lockfile, int retries)
{
	return do_lockfile_lock(lockfile, retries, getppid(), DEF_WAITINC_US,
				DEF_MAXWAIT_US);
}

/**
 * Attempt to create a lockfile owned by the parent of this process at the
 * specified path using a custom wait/retry time.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in]      retries    Number of times to retry if lock fails initially
 * @param[in]      waitinc    How many micro-seconds to extend wait time before
 *                            additional retry
 * @param[in]      maxwait    Maximum wait time before retry
 *
 * @retval         0          Lock created
 * @retval         !=0        Lock was not created
 */
int util_lockfile_parent_lock_cw(char *lockfile, int retries,
				 unsigned int waitinc, unsigned int maxwait)
{
	return do_lockfile_lock(lockfile, retries, getppid(), waitinc, maxwait);
}

/**
 * Attempt to release a lockfile owned by this process at the specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 *
 * @retval         0          Lock released
 * @retval         !=0        Lock was not released or did not exist
 */
int util_lockfile_release(char *lockfile)
{
	return do_lockfile_release(lockfile, getpid());
}

/**
 * Attempt to release a lockfile owned by the parent of this process at the
 * specified path.
 *
 * @param[in]      lockfile   Path to the lock file
 *
 * @retval         0          Lock released
 * @retval         !=0        Lock was not released or did not exist
 */
int util_lockfile_parent_release(char *lockfile)
{
	return do_lockfile_release(lockfile, getppid());
}

/**
 * Return the pid that owns the specified lockfile.
 *
 * @param[in]      lockfile   Path to the lock file
 * @param[in,out]  pid        Buffer to place owning pid
 *
 * @retval         0          pid provided in buffer
 * @retval         !=0        Error, no pid provided
 */
int util_lockfile_peek_owner(char *lockfile, int *pid)
{
	char buf[BUFSIZE];
	int fd, len;

	if (!lockfile || !pid)
		return UTIL_LOCKFILE_ERR;

	/* Open lockfile, read the owning pid if it exists */
	fd = open(lockfile, O_RDONLY);
	if (fd < 0)
		return UTIL_LOCKFILE_ERR;

	len = read(fd, buf, sizeof(buf));
	close(fd);
	if (len <= 0)
		return UTIL_LOCKFILE_ERR;
	buf[len] = 0;
	*pid = atoi(buf);

	return 0;
}