(root)/
util-linux-2.39/
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 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 */