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 nto 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 char *lockfile; /* path to lock file (e.g. /etc/mtab~) */
40 int lockfile_fd; /* lock file descriptor */
41
42 unsigned int locked :1, /* do we own the lock? */
43 sigblock :1; /* block signals when locked */
44
45 sigset_t oldsigmask;
46 };
47
48
49 /**
50 * mnt_new_lock:
51 * @datafile: the file that should be covered by the lock
52 * @id: ignored by library
53 *
54 * Returns: newly allocated lock handler or NULL on case of error.
55 */
56 struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id __attribute__((__unused__)))
57 {
58 struct libmnt_lock *ml = NULL;
59 char *lo = NULL;
60 size_t losz;
61
62 if (!datafile)
63 return NULL;
64
65 losz = strlen(datafile) + sizeof(".lock");
66 lo = malloc(losz);
67 if (!lo)
68 goto err;
69
70 snprintf(lo, losz, "%s.lock", datafile);
71
72 ml = calloc(1, sizeof(*ml) );
73 if (!ml)
74 goto err;
75
76 ml->lockfile_fd = -1;
77 ml->lockfile = lo;
78
79 DBG(LOCKS, ul_debugobj(ml, "alloc: lockfile=%s", lo));
80 return ml;
81 err:
82 free(lo);
83 free(ml);
84 return NULL;
85 }
86
87
88 /**
89 * mnt_free_lock:
90 * @ml: struct libmnt_lock handler
91 *
92 * Deallocates mnt_lock.
93 */
94 void mnt_free_lock(struct libmnt_lock *ml)
95 {
96 if (!ml)
97 return;
98 DBG(LOCKS, ul_debugobj(ml, "free%s", ml->locked ? " !!! LOCKED !!!" : ""));
99 free(ml->lockfile);
100 free(ml);
101 }
102
103 /**
104 * mnt_lock_block_signals:
105 * @ml: struct libmnt_lock handler
106 * @enable: TRUE/FALSE
107 *
108 * Block/unblock signals when the lock is locked, the signals are not blocked
109 * by default.
110 *
111 * Returns: <0 on error, 0 on success.
112 */
113 int mnt_lock_block_signals(struct libmnt_lock *ml, int enable)
114 {
115 if (!ml)
116 return -EINVAL;
117 DBG(LOCKS, ul_debugobj(ml, "signals: %s", enable ? "BLOCKED" : "UNBLOCKED"));
118 ml->sigblock = enable ? 1 : 0;
119 return 0;
120 }
121
122 /*
123 * Returns path to lockfile.
124 */
125 static const char *mnt_lock_get_lockfile(struct libmnt_lock *ml)
126 {
127 return ml ? ml->lockfile : NULL;
128 }
129
130 /*
131 * Simple flocking
132 */
133 static void unlock_simplelock(struct libmnt_lock *ml)
134 {
135 assert(ml);
136
137 if (ml->lockfile_fd >= 0) {
138 DBG(LOCKS, ul_debugobj(ml, "%s: unflocking",
139 mnt_lock_get_lockfile(ml)));
140 close(ml->lockfile_fd);
141 }
142 }
143
144 static int lock_simplelock(struct libmnt_lock *ml)
145 {
146 const char *lfile;
147 int rc;
148 struct stat sb;
149 const mode_t lock_mask = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
150
151 assert(ml);
152
153 lfile = mnt_lock_get_lockfile(ml);
154
155 DBG(LOCKS, ul_debugobj(ml, "%s: locking", lfile));
156
157 if (ml->sigblock) {
158 sigset_t sigs;
159 sigemptyset(&ml->oldsigmask);
160 sigfillset(&sigs);
161 sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask);
162 }
163
164 ml->lockfile_fd = open(lfile, O_RDONLY|O_CREAT|O_CLOEXEC,
165 S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
166 if (ml->lockfile_fd < 0) {
167 rc = -errno;
168 goto err;
169 }
170
171 rc = fstat(ml->lockfile_fd, &sb);
172 if (rc < 0) {
173 rc = -errno;
174 goto err;
175 }
176
177 if ((sb.st_mode & lock_mask) != lock_mask) {
178 rc = fchmod(ml->lockfile_fd, lock_mask);
179 if (rc < 0) {
180 rc = -errno;
181 goto err;
182 }
183 }
184
185 while (flock(ml->lockfile_fd, LOCK_EX) < 0) {
186 int errsv;
187 if ((errno == EAGAIN) || (errno == EINTR))
188 continue;
189 errsv = errno;
190 close(ml->lockfile_fd);
191 ml->lockfile_fd = -1;
192 rc = -errsv;
193 goto err;
194 }
195 ml->locked = 1;
196 return 0;
197 err:
198 if (ml->sigblock)
199 sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
200 return rc;
201 }
202
203 /**
204 * mnt_lock_file
205 * @ml: pointer to struct libmnt_lock instance
206 *
207 * Creates a lock file.
208 *
209 * Note that when the lock is used by mnt_update_table() interface then libmount
210 * uses flock() for private library file /run/mount/utab.
211 *
212 * Returns: 0 on success or negative number in case of error (-ETIMEOUT is case
213 * of stale lock file).
214 */
215 int mnt_lock_file(struct libmnt_lock *ml)
216 {
217 if (!ml)
218 return -EINVAL;
219
220 return lock_simplelock(ml);
221 }
222
223 /**
224 * mnt_unlock_file:
225 * @ml: lock struct
226 *
227 * Unlocks the file. The function could be called independently of the
228 * lock status (for example from exit(3)).
229 */
230 void mnt_unlock_file(struct libmnt_lock *ml)
231 {
232 if (!ml)
233 return;
234
235 DBG(LOCKS, ul_debugobj(ml, "(%d) %s", getpid(),
236 ml->locked ? "unlocking" : "cleaning"));
237
238 unlock_simplelock(ml);
239
240 ml->locked = 0;
241 ml->lockfile_fd = -1;
242
243 if (ml->sigblock) {
244 DBG(LOCKS, ul_debugobj(ml, "restoring sigmask"));
245 sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
246 }
247 }
248
249 #ifdef TEST_PROGRAM
250
251 static struct libmnt_lock *lock;
252
253 /*
254 * read number from @filename, increment the number and
255 * write the number back to the file
256 */
257 static void increment_data(const char *filename, int verbose, int loopno)
258 {
259 long num;
260 FILE *f;
261 char buf[256];
262
263 if (!(f = fopen(filename, "r" UL_CLOEXECSTR)))
264 err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
265
266 if (!fgets(buf, sizeof(buf), f))
267 err(EXIT_FAILURE, "%d failed read: %s", getpid(), filename);
268
269 fclose(f);
270 num = atol(buf) + 1;
271
272 if (!(f = fopen(filename, "w" UL_CLOEXECSTR)))
273 err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
274
275 fprintf(f, "%ld", num);
276
277 if (close_stream(f) != 0)
278 err(EXIT_FAILURE, "write failed: %s", filename);
279
280 if (verbose)
281 fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(),
282 filename, num - 1, num, loopno);
283 }
284
285 static void clean_lock(void)
286 {
287 if (!lock)
288 return;
289 mnt_unlock_file(lock);
290 mnt_free_lock(lock);
291 }
292
293 static void __attribute__((__noreturn__)) sig_handler(int sig)
294 {
295 errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig));
296 }
297
298 static int test_lock(struct libmnt_test *ts, int argc, char *argv[])
299 {
300 time_t synctime = 0;
301 unsigned int usecs;
302 const char *datafile = NULL;
303 int verbose = 0, loops = 0, l, idx = 1;
304
305 if (argc < 3)
306 return -EINVAL;
307
308 if (strcmp(argv[idx], "--synctime") == 0) {
309 synctime = (time_t) atol(argv[idx + 1]);
310 idx += 2;
311 }
312 if (idx < argc && strcmp(argv[idx], "--verbose") == 0) {
313 verbose = 1;
314 idx++;
315 }
316
317 if (idx < argc)
318 datafile = argv[idx++];
319 if (idx < argc)
320 loops = atoi(argv[idx++]);
321
322 if (!datafile || !loops)
323 return -EINVAL;
324
325 if (verbose)
326 fprintf(stderr, "%d: start: synctime=%u, datafile=%s, loops=%d\n",
327 getpid(), (int) synctime, datafile, loops);
328
329 atexit(clean_lock);
330
331 /* be paranoid and call exit() (=clean_lock()) for all signals */
332 {
333 int sig = 0;
334 struct sigaction sa;
335
336 sa.sa_handler = sig_handler;
337 sa.sa_flags = 0;
338 sigfillset(&sa.sa_mask);
339
340 while (sigismember(&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD)
341 sigaction (sig, &sa, (struct sigaction *) 0);
342 }
343
344 /* start the test in exactly defined time */
345 if (synctime) {
346 struct timeval tv;
347
348 gettimeofday(&tv, NULL);
349 if (synctime && synctime - tv.tv_sec > 1) {
350 usecs = ((synctime - tv.tv_sec) * 1000000UL) -
351 (1000000UL - tv.tv_usec);
352 xusleep(usecs);
353 }
354 }
355
356 for (l = 0; l < loops; l++) {
357 lock = mnt_new_lock(datafile, 0);
358 if (!lock)
359 return -1;
360
361 if (mnt_lock_file(lock) != 0) {
362 fprintf(stderr, "%d: failed to lock %s file\n",
363 getpid(), datafile);
364 return -1;
365 }
366
367 increment_data(datafile, verbose, l);
368
369 mnt_unlock_file(lock);
370 mnt_free_lock(lock);
371 lock = NULL;
372
373 /* The mount command usually finishes after a mtab update. We
374 * simulate this via short sleep -- it's also enough to make
375 * concurrent processes happy.
376 */
377 if (synctime)
378 xusleep(25000);
379 }
380
381 return 0;
382 }
383
384 /*
385 * Note that this test should be executed from a script that creates many
386 * parallel processes, otherwise this test does not make sense.
387 */
388 int main(int argc, char *argv[])
389 {
390 struct libmnt_test tss[] = {
391 { "--lock", test_lock, " [--synctime <time_t>] [--verbose] <datafile> <loops> "
392 "increment a number in datafile" },
393 { NULL }
394 };
395
396 return mnt_run_test(tss, argc, argv);
397 }
398
399 #endif /* TEST_PROGRAM */