github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/libsnap-confine-private/locking.c (about)

     1  /*
     2   * Copyright (C) 2017-2018 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #ifdef HAVE_CONFIG_H
    19  #include "config.h"
    20  #endif
    21  
    22  #include "locking.h"
    23  
    24  #include <errno.h>
    25  #include <fcntl.h>
    26  #include <signal.h>
    27  #include <stdarg.h>
    28  #include <sys/file.h>
    29  #include <sys/stat.h>
    30  #include <sys/types.h>
    31  #include <unistd.h>
    32  
    33  #include "../libsnap-confine-private/cleanup-funcs.h"
    34  #include "../libsnap-confine-private/string-utils.h"
    35  #include "../libsnap-confine-private/utils.h"
    36  
    37  // SANITY_TIMEOUT is the timeout in seconds that is used when
    38  // "sc_enable_sanity_timeout()" is called
    39  static const int SANITY_TIMEOUT = 30;
    40  
    41  /**
    42   * Flag indicating that a sanity timeout has expired.
    43   **/
    44  static volatile sig_atomic_t sanity_timeout_expired = 0;
    45  
    46  /**
    47   * Signal handler for SIGALRM that sets sanity_timeout_expired flag to 1.
    48   **/
    49  static void sc_SIGALRM_handler(int signum)
    50  {
    51  	sanity_timeout_expired = 1;
    52  }
    53  
    54  void sc_enable_sanity_timeout(void)
    55  {
    56  	sanity_timeout_expired = 0;
    57  	struct sigaction act = {.sa_handler = sc_SIGALRM_handler };
    58  	if (sigemptyset(&act.sa_mask) < 0) {
    59  		die("cannot initialize POSIX signal set");
    60  	}
    61  	// NOTE: we are using sigaction so that we can explicitly control signal
    62  	// flags and *not* pass the SA_RESTART flag. The intent is so that any
    63  	// system call we may be sleeping on to gets interrupted.
    64  	act.sa_flags = 0;
    65  	if (sigaction(SIGALRM, &act, NULL) < 0) {
    66  		die("cannot install signal handler for SIGALRM");
    67  	}
    68  	alarm(SANITY_TIMEOUT);
    69  	debug("sanity timeout initialized and set for %i seconds",
    70  	      SANITY_TIMEOUT);
    71  }
    72  
    73  void sc_disable_sanity_timeout(void)
    74  {
    75  	if (sanity_timeout_expired) {
    76  		die("sanity timeout expired");
    77  	}
    78  	alarm(0);
    79  	struct sigaction act = {.sa_handler = SIG_DFL };
    80  	if (sigemptyset(&act.sa_mask) < 0) {
    81  		die("cannot initialize POSIX signal set");
    82  	}
    83  	if (sigaction(SIGALRM, &act, NULL) < 0) {
    84  		die("cannot uninstall signal handler for SIGALRM");
    85  	}
    86  	debug("sanity timeout reset and disabled");
    87  }
    88  
    89  #define SC_LOCK_DIR "/run/snapd/lock"
    90  
    91  static const char *sc_lock_dir = SC_LOCK_DIR;
    92  
    93  static int get_lock_directory(void)
    94  {
    95  	// Create (if required) and open the lock directory.
    96  	debug("creating lock directory %s (if missing)", sc_lock_dir);
    97  	if (sc_nonfatal_mkpath(sc_lock_dir, 0755) < 0) {
    98  		die("cannot create lock directory %s", sc_lock_dir);
    99  	}
   100  	debug("opening lock directory %s", sc_lock_dir);
   101  	int dir_fd =
   102  	    open(sc_lock_dir, O_DIRECTORY | O_PATH | O_CLOEXEC | O_NOFOLLOW);
   103  	if (dir_fd < 0) {
   104  		die("cannot open lock directory");
   105  	}
   106  	return dir_fd;
   107  }
   108  
   109  static void get_lock_name(char *lock_fname, size_t size, const char *scope,
   110  			  uid_t uid)
   111  {
   112  	if (uid == 0) {
   113  		// The root user doesn't have a per-user mount namespace.
   114  		// Doing so would be confusing for services which use $SNAP_DATA
   115  		// as home, and not in $SNAP_USER_DATA.
   116  		sc_must_snprintf(lock_fname, size, "%s.lock", scope ? : "");
   117  	} else {
   118  		sc_must_snprintf(lock_fname, size, "%s.%d.lock",
   119  				 scope ? : "", uid);
   120  	}
   121  }
   122  
   123  static int open_lock(const char *scope, uid_t uid)
   124  {
   125  	int dir_fd SC_CLEANUP(sc_cleanup_close) = -1;
   126  	char lock_fname[PATH_MAX] = { 0 };
   127  	int lock_fd;
   128  
   129  	dir_fd = get_lock_directory();
   130  	get_lock_name(lock_fname, sizeof lock_fname, scope, uid);
   131  
   132  	// Open the lock file and acquire an exclusive lock.
   133  	debug("opening lock file: %s/%s", sc_lock_dir, lock_fname);
   134  	lock_fd = openat(dir_fd, lock_fname,
   135  			 O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600);
   136  	if (lock_fd < 0) {
   137  		die("cannot open lock file: %s/%s", sc_lock_dir, lock_fname);
   138  	}
   139  	return lock_fd;
   140  }
   141  
   142  static int sc_lock_generic(const char *scope, uid_t uid)
   143  {
   144  	int lock_fd = open_lock(scope, uid);
   145  	sc_enable_sanity_timeout();
   146  	debug("acquiring exclusive lock (scope %s, uid %d)",
   147  	      scope ? : "(global)", uid);
   148  	if (flock(lock_fd, LOCK_EX) < 0) {
   149  		sc_disable_sanity_timeout();
   150  		close(lock_fd);
   151  		die("cannot acquire exclusive lock (scope %s, uid %d)",
   152  		    scope ? : "(global)", uid);
   153  	} else {
   154  		sc_disable_sanity_timeout();
   155  	}
   156  	return lock_fd;
   157  }
   158  
   159  int sc_lock_global(void)
   160  {
   161  	return sc_lock_generic(NULL, 0);
   162  }
   163  
   164  int sc_lock_snap(const char *snap_name)
   165  {
   166  	return sc_lock_generic(snap_name, 0);
   167  }
   168  
   169  void sc_verify_snap_lock(const char *snap_name)
   170  {
   171  	int lock_fd, retval;
   172  
   173  	lock_fd = open_lock(snap_name, 0);
   174  	debug("trying to verify whether exclusive lock over snap %s is held",
   175  	      snap_name);
   176  	retval = flock(lock_fd, LOCK_EX | LOCK_NB);
   177  	if (retval == 0) {
   178  		/* We managed to grab the lock, the lock was not held! */
   179  		flock(lock_fd, LOCK_UN);
   180  		close(lock_fd);
   181  		errno = 0;
   182  		die("unexpectedly managed to acquire exclusive lock over snap %s", snap_name);
   183  	}
   184  	if (retval < 0 && errno != EWOULDBLOCK) {
   185  		die("cannot verify exclusive lock over snap %s", snap_name);
   186  	}
   187  	/* We tried but failed to grab the lock because the file is already locked.
   188  	 * Good, this is what we expected. */
   189  }
   190  
   191  int sc_lock_snap_user(const char *snap_name, uid_t uid)
   192  {
   193  	return sc_lock_generic(snap_name, uid);
   194  }
   195  
   196  void sc_unlock(int lock_fd)
   197  {
   198  	// Release the lock and finish.
   199  	debug("releasing lock %d", lock_fd);
   200  	if (flock(lock_fd, LOCK_UN) < 0) {
   201  		die("cannot release lock %d", lock_fd);
   202  	}
   203  	close(lock_fd);
   204  }