]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/lock.c
28ff8b788b6c3b2417da1e07dcafae2a00f8b206
[thirdparty/util-linux.git] / libmount / src / lock.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 */
12
13 /**
14 * SECTION: lock
15 * @title: Locking
16 * @short_description: locking methods for utab or another libmount files
17 *
18 * Since v2.39 libmount does not support classic mtab locking. Now all is based
19 * on flock only.
20 *
21 */
22 #include <sys/time.h>
23 #include <time.h>
24 #include <signal.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <sys/file.h>
28
29 #include "strutils.h"
30 #include "closestream.h"
31 #include "pathnames.h"
32 #include "mountP.h"
33 #include "monotonic.h"
34
35 /*
36 * lock handler
37 */
38 struct libmnt_lock {
39 int refcount; /* reference counter */
40 char *lockfile; /* path to lock file (e.g. /etc/mtab~) */
41 int lockfile_fd; /* lock file descriptor */
42
43 bool locked, /* do we own the lock? */
44 sigblock; /* block signals when locked */
45
46 sigset_t oldsigmask;
47 };
48
49
50 /**
51 * mnt_new_lock:
52 * @datafile: the file that should be covered by the lock
53 * @id: ignored by library
54 *
55 * Returns: newly allocated lock handler or NULL on case of error.
56 */
57 struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id __attribute__((__unused__)))
58 {
59 struct libmnt_lock *ml = NULL;
60 char *lo = NULL;
61 size_t losz;
62
63 if (!datafile)
64 return NULL;
65
66 losz = strlen(datafile) + sizeof(".lock");
67 lo = malloc(losz);
68 if (!lo)
69 goto err;
70
71 snprintf(lo, losz, "%s.lock", datafile);
72
73 ml = calloc(1, sizeof(*ml) );
74 if (!ml)
75 goto err;
76
77 ml->refcount = 1;
78 ml->lockfile_fd = -1;
79 ml->lockfile = lo;
80
81 DBG(LOCKS, ul_debugobj(ml, "alloc: lockfile=%s", lo));
82 return ml;
83 err:
84 free(lo);
85 free(ml);
86 return NULL;
87 }
88
89
90 /**
91 * mnt_free_lock:
92 * @ml: struct libmnt_lock handler
93 *
94 * Deallocates libmnt_lock. This function does not care about reference count. Don't
95 * use this function directly -- it's better to use mnt_unref_lock().
96 *
97 * The reference counting is supported since util-linux v2.40.
98 */
99 void mnt_free_lock(struct libmnt_lock *ml)
100 {
101 if (!ml)
102 return;
103
104 DBG(LOCKS, ul_debugobj(ml, "free%s [refcount=%d]",
105 ml->locked ? " !!! LOCKED !!!" : "",
106 ml->refcount));
107 free(ml->lockfile);
108 free(ml);
109 }
110
111 /**
112 * mnt_ref_lock:
113 * @ml: lock pointer
114 *
115 * Increments reference counter.
116 *
117 * Since: 2.40
118 */
119 void mnt_ref_lock(struct libmnt_lock *ml)
120 {
121 if (ml) {
122 ml->refcount++;
123 /*DBG(FS, ul_debugobj(fs, "ref=%d", ml->refcount));*/
124 }
125 }
126
127 /**
128 * mnt_unref_lock:
129 * @ml: lock pointer
130 *
131 * De-increments reference counter, on zero the @ml is automatically
132 * deallocated by mnt_free_lock).
133 */
134 void mnt_unref_lock(struct libmnt_lock *ml)
135 {
136 if (ml) {
137 ml->refcount--;
138 /*DBG(FS, ul_debugobj(fs, "unref=%d", ml->refcount));*/
139 if (ml->refcount <= 0)
140 mnt_free_lock(ml);
141 }
142 }
143
144 /**
145 * mnt_lock_block_signals:
146 * @ml: struct libmnt_lock handler
147 * @enable: TRUE/FALSE
148 *
149 * Block/unblock signals when the lock is locked, the signals are not blocked
150 * by default.
151 *
152 * Returns: <0 on error, 0 on success.
153 */
154 int mnt_lock_block_signals(struct libmnt_lock *ml, int enable)
155 {
156 if (!ml)
157 return -EINVAL;
158 DBG(LOCKS, ul_debugobj(ml, "signals: %s", enable ? "BLOCKED" : "UNBLOCKED"));
159 ml->sigblock = enable ? 1 : 0;
160 return 0;
161 }
162
163 /*
164 * Returns path to lockfile.
165 */
166 static const char *mnt_lock_get_lockfile(struct libmnt_lock *ml)
167 {
168 return ml ? ml->lockfile : NULL;
169 }
170
171 /*
172 * Simple flocking
173 */
174 static void unlock_simplelock(struct libmnt_lock *ml)
175 {
176 assert(ml);
177
178 if (ml->lockfile_fd >= 0) {
179 DBG(LOCKS, ul_debugobj(ml, "%s: unflocking",
180 mnt_lock_get_lockfile(ml)));
181 close(ml->lockfile_fd);
182 }
183 }
184
185 static int lock_simplelock(struct libmnt_lock *ml)
186 {
187 const char *lfile;
188 int rc;
189 struct stat sb;
190 const mode_t lock_mask = S_IRUSR|S_IWUSR;
191
192 assert(ml);
193
194 lfile = mnt_lock_get_lockfile(ml);
195
196 DBG(LOCKS, ul_debugobj(ml, "%s: locking", lfile));
197
198 if (ml->sigblock) {
199 sigset_t sigs;
200 sigemptyset(&ml->oldsigmask);
201 sigfillset(&sigs);
202 sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask);
203 }
204
205 ml->lockfile_fd = open(lfile, O_RDONLY|O_CREAT|O_CLOEXEC, lock_mask);
206 if (ml->lockfile_fd < 0) {
207 rc = -errno;
208 goto err;
209 }
210
211 rc = fstat(ml->lockfile_fd, &sb);
212 if (rc < 0) {
213 rc = -errno;
214 goto err;
215 }
216
217 if ((sb.st_mode & lock_mask) != lock_mask) {
218 rc = fchmod(ml->lockfile_fd, lock_mask);
219 if (rc < 0) {
220 rc = -errno;
221 goto err;
222 }
223 }
224
225 while (flock(ml->lockfile_fd, LOCK_EX) < 0) {
226 int errsv;
227 if ((errno == EAGAIN) || (errno == EINTR))
228 continue;
229 errsv = errno;
230 close(ml->lockfile_fd);
231 ml->lockfile_fd = -1;
232 rc = -errsv;
233 goto err;
234 }
235 ml->locked = 1;
236 return 0;
237 err:
238 if (ml->sigblock)
239 sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
240 return rc;
241 }
242
243 /**
244 * mnt_lock_file
245 * @ml: pointer to struct libmnt_lock instance
246 *
247 * Creates a lock file.
248 *
249 * Note that when the lock is used by mnt_update_table() interface then libmount
250 * uses flock() for private library file /run/mount/utab.
251 *
252 * Returns: 0 on success or negative number in case of error (-ETIMEOUT is case
253 * of stale lock file).
254 */
255 int mnt_lock_file(struct libmnt_lock *ml)
256 {
257 if (!ml)
258 return -EINVAL;
259
260 return lock_simplelock(ml);
261 }
262
263 /**
264 * mnt_unlock_file:
265 * @ml: lock struct
266 *
267 * Unlocks the file. The function could be called independently of the
268 * lock status (for example from exit(3)).
269 */
270 void mnt_unlock_file(struct libmnt_lock *ml)
271 {
272 if (!ml)
273 return;
274
275 DBG(LOCKS, ul_debugobj(ml, "(%d) %s", getpid(),
276 ml->locked ? "unlocking" : "cleaning"));
277
278 unlock_simplelock(ml);
279
280 ml->locked = 0;
281 ml->lockfile_fd = -1;
282
283 if (ml->sigblock) {
284 DBG(LOCKS, ul_debugobj(ml, "restoring sigmask"));
285 sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
286 }
287 }
288
289 #ifdef TEST_PROGRAM
290
291 static struct libmnt_lock *lock;
292
293 /*
294 * read number from @filename, increment the number and
295 * write the number back to the file
296 */
297 static void increment_data(const char *filename, int verbose, int loopno)
298 {
299 long num;
300 FILE *f;
301 char buf[256];
302
303 if (!(f = fopen(filename, "r" UL_CLOEXECSTR)))
304 err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
305
306 if (!fgets(buf, sizeof(buf), f))
307 err(EXIT_FAILURE, "%d failed read: %s", getpid(), filename);
308
309 fclose(f);
310 num = atol(buf) + 1;
311
312 if (!(f = fopen(filename, "w" UL_CLOEXECSTR)))
313 err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
314
315 fprintf(f, "%ld", num);
316
317 if (close_stream(f) != 0)
318 err(EXIT_FAILURE, "write failed: %s", filename);
319
320 if (verbose)
321 fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(),
322 filename, num - 1, num, loopno);
323 }
324
325 static void clean_lock(void)
326 {
327 if (!lock)
328 return;
329 mnt_unlock_file(lock);
330 mnt_unref_lock(lock);
331 }
332
333 static void __attribute__((__noreturn__)) sig_handler(int sig)
334 {
335 errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig));
336 }
337
338 static int test_lock(struct libmnt_test *ts __attribute__((unused)),
339 int argc, char *argv[])
340 {
341 time_t synctime = 0;
342 unsigned int usecs;
343 const char *datafile = NULL;
344 int verbose = 0, loops = 0, l, idx = 1;
345
346 if (argc < 3)
347 return -EINVAL;
348
349 if (strcmp(argv[idx], "--synctime") == 0) {
350 synctime = (time_t) atol(argv[idx + 1]);
351 idx += 2;
352 }
353 if (idx < argc && strcmp(argv[idx], "--verbose") == 0) {
354 verbose = 1;
355 idx++;
356 }
357
358 if (idx < argc)
359 datafile = argv[idx++];
360 if (idx < argc)
361 loops = atoi(argv[idx++]);
362
363 if (!datafile || !loops)
364 return -EINVAL;
365
366 if (verbose)
367 fprintf(stderr, "%d: start: synctime=%u, datafile=%s, loops=%d\n",
368 getpid(), (int) synctime, datafile, loops);
369
370 atexit(clean_lock);
371
372 /* be paranoid and call exit() (=clean_lock()) for all signals */
373 {
374 int sig = 0;
375 struct sigaction sa;
376
377 sa.sa_handler = sig_handler;
378 sa.sa_flags = 0;
379 sigfillset(&sa.sa_mask);
380
381 while (sigismember(&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD)
382 sigaction (sig, &sa, (struct sigaction *) 0);
383 }
384
385 /* start the test in exactly defined time */
386 if (synctime) {
387 struct timeval tv;
388
389 gettimeofday(&tv, NULL);
390 if (synctime && synctime - tv.tv_sec > 1) {
391 usecs = ((synctime - tv.tv_sec) * 1000000UL) -
392 (1000000UL - tv.tv_usec);
393 xusleep(usecs);
394 }
395 }
396
397 for (l = 0; l < loops; l++) {
398 lock = mnt_new_lock(datafile, 0);
399 if (!lock)
400 return -1;
401
402 if (mnt_lock_file(lock) != 0) {
403 fprintf(stderr, "%d: failed to lock %s file\n",
404 getpid(), datafile);
405 return -1;
406 }
407
408 increment_data(datafile, verbose, l);
409
410 mnt_unlock_file(lock);
411 mnt_unref_lock(lock);
412 lock = NULL;
413
414 /* The mount command usually finishes after a mtab update. We
415 * simulate this via short sleep -- it's also enough to make
416 * concurrent processes happy.
417 */
418 if (synctime)
419 xusleep(25000);
420 }
421
422 return 0;
423 }
424
425 /*
426 * Note that this test should be executed from a script that creates many
427 * parallel processes, otherwise this test does not make sense.
428 */
429 int main(int argc, char *argv[])
430 {
431 struct libmnt_test tss[] = {
432 { "--lock", test_lock, " [--synctime <time_t>] [--verbose] <datafile> <loops> "
433 "increment a number in datafile" },
434 { NULL }
435 };
436
437 return mnt_run_test(tss, argc, argv);
438 }
439
440 #endif /* TEST_PROGRAM */