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 }