X-Git-Url: https://git.hcoop.net/bpt/emacs.git/blobdiff_plain/28b6b84d4e91cf39c85f194c29b8e044ae6bac87..aa30fa6f3a4273adcfa1cba183a74a5aeb47b463:/src/filelock.c diff --git a/src/filelock.c b/src/filelock.c index 14b9d4aaca..df72eff595 100644 --- a/src/filelock.c +++ b/src/filelock.c @@ -38,6 +38,8 @@ along with GNU Emacs. If not, see . */ #include +#include + #include "lisp.h" #include "character.h" #include "buffer.h" @@ -45,6 +47,7 @@ along with GNU Emacs. If not, see . */ #include "systime.h" #ifdef WINDOWSNT #include +#include /* for fcntl */ #include "w32.h" /* for dostounix_filename */ #endif @@ -64,7 +67,7 @@ along with GNU Emacs. If not, see . */ #define WTMP_FILE "/var/log/wtmp" #endif -/* On non-MS-Windows systems, use a symbolic link to represent a lock. +/* Normally use a symbolic link to represent a lock. The strategy: to lock a file FN, create a symlink .#FN in FN's directory, with link data `user@host.pid'. This avoids a single mount (== failure) point for lock files. @@ -100,10 +103,21 @@ along with GNU Emacs. If not, see . */ --karl@cs.umb.edu/karl@hq.ileaf.com. - On MS-Windows, symbolic links do not work well, so instead of a - symlink .#FN -> 'user@host.pid', the lock is a regular file .#-FN - with contents 'user@host.pid'. MS-Windows and non-MS-Windows - versions of Emacs ignore each other's locks. */ + On some file systems, notably those of MS-Windows, symbolic links + do not work well, so instead of a symlink .#FN -> 'user@host.pid', + the lock is a regular file .#FN with contents 'user@host.pid'. To + establish a lock, a nonce file is created and then renamed to .#FN. + On MS-Windows this renaming is atomic unless the lock is forcibly + acquired. On other systems the renaming is atomic if the lock is + forcibly acquired; if not, the renaming is done via hard links, + which is good enough for lock-file purposes. + + To summarize, race conditions can occur with either: + + * Forced locks on MS-Windows systems. + + * Non-forced locks on non-MS-Windows systems that support neither + hard nor symbolic links. */ /* Return the time of the last system boot. */ @@ -167,7 +181,7 @@ get_boot_time (void) since utmp is typically much smaller than wtmp. Passing a null pointer causes get_boot_time_1 to inspect the default file, namely utmp. */ - get_boot_time_1 ((char *) 0, 0); + get_boot_time_1 (0, 0); if (boot_time) return boot_time; @@ -243,18 +257,14 @@ void get_boot_time_1 (const char *filename, bool newest) { struct utmp ut, *utp; - int desc; if (filename) { /* On some versions of IRIX, opening a nonexistent file name is likely to crash in the utmp routines. */ - desc = emacs_open (filename, O_RDONLY, 0); - if (desc < 0) + if (faccessat (AT_FDCWD, filename, R_OK, AT_EACCESS) != 0) return; - emacs_close (desc); - utmpname (filename); } @@ -284,30 +294,31 @@ get_boot_time_1 (const char *filename, bool newest) } #endif /* BOOT_TIME */ +/* An arbitrary limit on lock contents length. 8 K should be plenty + big enough in practice. */ +enum { MAX_LFINFO = 8 * 1024 }; + /* Here is the structure that stores information about a lock. */ typedef struct { - char *user; - char *host; - pid_t pid; - time_t boot_time; -} lock_info_type; - -/* Free the two dynamically-allocated pieces in PTR. */ -#define FREE_LOCK_INFO(i) do { xfree ((i).user); xfree ((i).host); } while (0) + /* Location of '@', '.', ':' in USER. If there's no colon, COLON + points to the end of USER. */ + char *at, *dot, *colon; -#ifdef WINDOWSNT -enum { defined_WINDOWSNT = 1 }; -#else -enum { defined_WINDOWSNT = 0 }; -#endif + /* Lock file contents USER@HOST.PID with an optional :BOOT_TIME + appended. This memory is used as a lock file contents buffer, so + it needs room for MAX_LFINFO + 1 bytes. A string " (pid NNNN)" + may be appended to the USER@HOST while generating a diagnostic, + so make room for its extra bytes (as opposed to ".NNNN") too. */ + char user[MAX_LFINFO + 1 + sizeof " (pid )" - sizeof "."]; +} lock_info_type; /* Write the name of the lock file for FNAME into LOCKNAME. Length - will be that of FNAME plus two more for the leading ".#", - plus one for "-" if MS-Windows, plus one for the null. */ + will be that of FNAME plus two more for the leading ".#", plus one + for the null. */ #define MAKE_LOCK_NAME(lockname, fname) \ - (lockname = SAFE_ALLOCA (SBYTES (fname) + 2 + defined_WINDOWSNT + 1), \ + (lockname = SAFE_ALLOCA (SBYTES (fname) + 2 + 1), \ fill_in_lock_file_name (lockname, fname)) static void @@ -319,70 +330,125 @@ fill_in_lock_file_name (char *lockfile, Lisp_Object fn) memcpy (lockfile, SSDATA (fn), dirlen); lockfile[dirlen] = '.'; lockfile[dirlen + 1] = '#'; - if (defined_WINDOWSNT) - lockfile[dirlen + 2] = '-'; - strcpy (lockfile + dirlen + 2 + defined_WINDOWSNT, base); + strcpy (lockfile + dirlen + 2, base); } +/* For some reason Linux kernels return EPERM on file systems that do + not support hard or symbolic links. This symbol documents the quirk. + There is no way to tell whether a symlink call fails due to + permissions issues or because links are not supported, but luckily + the lock file code should work either way. */ +enum { LINKS_MIGHT_NOT_WORK = EPERM }; + +/* Rename OLD to NEW. If FORCE, replace any existing NEW. + It is OK if there are temporarily two hard links to OLD. + Return 0 if successful, -1 (setting errno) otherwise. */ static int -create_lock_file (char *lfname, char *lock_info_str, bool force) +rename_lock_file (char const *old, char const *new, bool force) { - int err; - #ifdef WINDOWSNT - /* Symlinks are supported only by latest versions of Windows, and - creating them is a privileged operation that often triggers UAC - elevation prompts. Therefore, instead of using symlinks, we - create a regular file with the lock info written as its - contents. */ - { - /* Deny everybody else any kind of access to the file until we are - done writing it and close the handle. This makes the entire - open/write/close operation atomic, as far as other WINDOWSNT - processes are concerned. */ - int fd = _sopen (lfname, - _O_WRONLY | _O_BINARY | _O_CREAT | _O_EXCL | _O_NOINHERIT, - _SH_DENYRW, S_IREAD | S_IWRITE); - - if (fd < 0 && errno == EEXIST && force) - fd = _sopen (lfname, _O_WRONLY | _O_BINARY | _O_TRUNC |_O_NOINHERIT, - _SH_DENYRW, S_IREAD | S_IWRITE); - if (fd >= 0) - { - ssize_t lock_info_len = strlen (lock_info_str); + return sys_rename_replace (old, new, force); +#else + if (! force) + { + struct stat st; - err = 0; - if (emacs_write (fd, lock_info_str, lock_info_len) != lock_info_len) - err = -1; - if (emacs_close (fd)) - err = -1; - } - else - err = -1; - } + if (link (old, new) == 0) + return unlink (old) == 0 || errno == ENOENT ? 0 : -1; + if (errno != ENOSYS && errno != LINKS_MIGHT_NOT_WORK) + return -1; + + /* 'link' does not work on this file system. This can occur on + a GNU/Linux host mounting a FAT32 file system. Fall back on + 'rename' after checking that NEW does not exist. There is a + potential race condition since some other process may create + NEW immediately after the existence check, but it's the best + we can portably do here. */ + if (lstat (new, &st) == 0 || errno == EOVERFLOW) + { + errno = EEXIST; + return -1; + } + if (errno != ENOENT) + return -1; + } + + return rename (old, new); +#endif +} + +/* Create the lock file LFNAME with contents LOCK_INFO_STR. Return 0 if + successful, an errno value on failure. If FORCE, remove any + existing LFNAME if necessary. */ + +static int +create_lock_file (char *lfname, char *lock_info_str, bool force) +{ +#ifdef WINDOWSNT + /* Symlinks are supported only by later versions of Windows, and + creating them is a privileged operation that often triggers + User Account Control elevation prompts. Avoid the problem by + pretending that 'symlink' does not work. */ + int err = ENOSYS; #else - err = symlink (lock_info_str, lfname); - if (err != 0 && errno == EEXIST && force) + int err = symlink (lock_info_str, lfname) == 0 ? 0 : errno; +#endif + + if (err == EEXIST && force) { unlink (lfname); - err = symlink (lock_info_str, lfname); + err = symlink (lock_info_str, lfname) == 0 ? 0 : errno; + } + + if (err == ENOSYS || err == LINKS_MIGHT_NOT_WORK || err == ENAMETOOLONG) + { + static char const nonce_base[] = ".#-emacsXXXXXX"; + char *last_slash = strrchr (lfname, '/'); + ptrdiff_t lfdirlen = last_slash + 1 - lfname; + USE_SAFE_ALLOCA; + char *nonce = SAFE_ALLOCA (lfdirlen + sizeof nonce_base); + int fd; + memcpy (nonce, lfname, lfdirlen); + strcpy (nonce + lfdirlen, nonce_base); + + fd = mkostemp (nonce, O_BINARY | O_CLOEXEC); + if (fd < 0) + err = errno; + else + { + ptrdiff_t lock_info_len; + if (! O_CLOEXEC) + fcntl (fd, F_SETFD, FD_CLOEXEC); + lock_info_len = strlen (lock_info_str); + err = 0; + /* Use 'write', not 'emacs_write', as garbage collection + might signal an error, which would leak FD. */ + if (write (fd, lock_info_str, lock_info_len) != lock_info_len + || fchmod (fd, S_IRUSR | S_IRGRP | S_IROTH) != 0) + err = errno; + /* There is no need to call fsync here, as the contents of + the lock file need not survive system crashes. */ + if (emacs_close (fd) != 0) + err = errno; + if (!err && rename_lock_file (nonce, lfname, force) != 0) + err = errno; + if (err) + unlink (nonce); + } + + SAFE_FREE (); } -#endif return err; } /* Lock the lock file named LFNAME. If FORCE, do so even if it is already locked. - Return true if successful. */ + Return 0 if successful, an error number on failure. */ -static bool +static int lock_file_1 (char *lfname, bool force) { - int err; - int symlink_errno; - USE_SAFE_ALLOCA; - /* Call this first because it can GC. */ printmax_t boot = get_boot_time (); @@ -390,20 +456,16 @@ lock_file_1 (char *lfname, bool force) char const *user_name = STRINGP (luser_name) ? SSDATA (luser_name) : ""; Lisp_Object lhost_name = Fsystem_name (); char const *host_name = STRINGP (lhost_name) ? SSDATA (lhost_name) : ""; - ptrdiff_t lock_info_size = (strlen (user_name) + strlen (host_name) - + 2 * INT_STRLEN_BOUND (printmax_t) - + sizeof "@.:"); - char *lock_info_str = SAFE_ALLOCA (lock_info_size); + char lock_info_str[MAX_LFINFO + 1]; printmax_t pid = getpid (); - esprintf (lock_info_str, boot ? "%s@%s.%"pMd":%"pMd : "%s@%s.%"pMd, - user_name, host_name, pid, boot); - err = create_lock_file (lfname, lock_info_str, force); + if (sizeof lock_info_str + <= snprintf (lock_info_str, sizeof lock_info_str, + boot ? "%s@%s.%"pMd":%"pMd : "%s@%s.%"pMd, + user_name, host_name, pid, boot)) + return ENAMETOOLONG; - symlink_errno = errno; - SAFE_FREE (); - errno = symlink_errno; - return err == 0; + return create_lock_file (lfname, lock_info_str, force); } /* Return true if times A and B are no more than one second apart. */ @@ -414,32 +476,45 @@ within_one_second (time_t a, time_t b) return (a - b >= -1 && a - b <= 1); } -static Lisp_Object -read_lock_data (char *lfname) -{ -#ifndef WINDOWSNT - return emacs_readlinkat (AT_FDCWD, lfname); -#else - int fd = emacs_open (lfname, O_RDONLY | O_BINARY, S_IREAD); - ssize_t nbytes; - /* 256 chars for user, 1024 chars for host, 10 digits for each of 2 int's. */ - enum { MAX_LFINFO = 256 + 1024 + 10 + 10 + 2 }; - char lfinfo[MAX_LFINFO + 1]; +/* On systems lacking ELOOP, test for an errno value that shouldn't occur. */ +#ifndef ELOOP +# define ELOOP (-1) +#endif - if (fd < 0) - return Qnil; +/* Read the data for the lock file LFNAME into LFINFO. Read at most + MAX_LFINFO + 1 bytes. Return the number of bytes read, or -1 + (setting errno) on error. */ - nbytes = emacs_read (fd, lfinfo, MAX_LFINFO); - emacs_close (fd); +static ptrdiff_t +read_lock_data (char *lfname, char lfinfo[MAX_LFINFO + 1]) +{ + ptrdiff_t nbytes; - if (nbytes > 0) + while ((nbytes = readlinkat (AT_FDCWD, lfname, lfinfo, MAX_LFINFO + 1)) < 0 + && errno == EINVAL) { - lfinfo[nbytes] = '\0'; - return build_string (lfinfo); + int fd = emacs_open (lfname, O_RDONLY | O_BINARY | O_NOFOLLOW, 0); + if (0 <= fd) + { + /* Use read, not emacs_read, since FD isn't unwind-protected. */ + ptrdiff_t read_bytes = read (fd, lfinfo, MAX_LFINFO + 1); + int read_errno = errno; + if (emacs_close (fd) != 0) + return -1; + errno = read_errno; + return read_bytes; + } + + if (errno != ELOOP) + return -1; + + /* readlinkat saw a non-symlink, but emacs_open saw a symlink. + The former must have been removed and replaced by the latter. + Try again. */ + QUIT; } - else - return Qnil; -#endif + + return nbytes; } /* Return 0 if nobody owns the lock file LFNAME or the lock is obsolete, @@ -451,83 +526,78 @@ static int current_lock_owner (lock_info_type *owner, char *lfname) { int ret; - ptrdiff_t len; lock_info_type local_owner; - intmax_t n; - char *at, *dot, *colon; - Lisp_Object lfinfo_object = read_lock_data (lfname); - char *lfinfo; - struct gcpro gcpro1; - - /* If nonexistent lock file, all is well; otherwise, got strange error. */ - if (NILP (lfinfo_object)) - return errno == ENOENT ? 0 : -1; - lfinfo = SSDATA (lfinfo_object); + ptrdiff_t lfinfolen; + intmax_t pid, boot_time; + char *at, *dot, *lfinfo_end; /* Even if the caller doesn't want the owner info, we still have to read it to determine return value. */ if (!owner) owner = &local_owner; + /* If nonexistent lock file, all is well; otherwise, got strange error. */ + lfinfolen = read_lock_data (lfname, owner->user); + if (lfinfolen < 0) + return errno == ENOENT ? 0 : -1; + if (MAX_LFINFO < lfinfolen) + return -1; + owner->user[lfinfolen] = 0; + /* Parse USER@HOST.PID:BOOT_TIME. If can't parse, return -1. */ /* The USER is everything before the last @. */ - at = strrchr (lfinfo, '@'); - dot = strrchr (lfinfo, '.'); - if (!at || !dot) + owner->at = at = memrchr (owner->user, '@', lfinfolen); + if (!at) + return -1; + owner->dot = dot = strrchr (at, '.'); + if (!dot) return -1; - len = at - lfinfo; - GCPRO1 (lfinfo_object); - owner->user = xmalloc (len + 1); - memcpy (owner->user, lfinfo, len); - owner->user[len] = 0; /* The PID is everything from the last `.' to the `:'. */ + if (! c_isdigit (dot[1])) + return -1; errno = 0; - n = strtoimax (dot + 1, NULL, 10); - owner->pid = - ((0 <= n && n <= TYPE_MAXIMUM (pid_t) - && (TYPE_MAXIMUM (pid_t) < INTMAX_MAX || errno != ERANGE)) - ? n : 0); + pid = strtoimax (dot + 1, &owner->colon, 10); + if (errno == ERANGE) + pid = -1; - colon = strchr (dot + 1, ':'); /* After the `:', if there is one, comes the boot time. */ - n = 0; - if (colon) + switch (owner->colon[0]) { - errno = 0; - n = strtoimax (colon + 1, NULL, 10); + case 0: + boot_time = 0; + lfinfo_end = owner->colon; + break; + + case ':': + if (! c_isdigit (owner->colon[1])) + return -1; + boot_time = strtoimax (owner->colon + 1, &lfinfo_end, 10); + break; + + default: + return -1; } - owner->boot_time = - ((0 <= n && n <= TYPE_MAXIMUM (time_t) - && (TYPE_MAXIMUM (time_t) < INTMAX_MAX || errno != ERANGE)) - ? n : 0); - - /* The host is everything in between. */ - len = dot - at - 1; - owner->host = xmalloc (len + 1); - memcpy (owner->host, at + 1, len); - owner->host[len] = 0; - - /* We're done looking at the link info. */ - UNGCPRO; + if (lfinfo_end != owner->user + lfinfolen) + return -1; /* On current host? */ - if (STRINGP (Fsystem_name ()) - && strcmp (owner->host, SSDATA (Fsystem_name ())) == 0) + if (STRINGP (Vsystem_name) + && dot - (at + 1) == SBYTES (Vsystem_name) + && memcmp (at + 1, SSDATA (Vsystem_name), SBYTES (Vsystem_name)) == 0) { - if (owner->pid == getpid ()) + if (pid == getpid ()) ret = 2; /* We own it. */ - else if (owner->pid > 0 - && (kill (owner->pid, 0) >= 0 || errno == EPERM) - && (owner->boot_time == 0 - || within_one_second (owner->boot_time, get_boot_time ()))) + else if (0 < pid && pid <= TYPE_MAXIMUM (pid_t) + && (kill (pid, 0) >= 0 || errno == EPERM) + && (boot_time == 0 + || (boot_time <= TYPE_MAXIMUM (time_t) + && within_one_second (boot_time, get_boot_time ())))) ret = 1; /* An existing process on this machine owns it. */ - /* The owner process is dead or has a strange pid (<=0), so try to + /* The owner process is dead or has a strange pid, so try to zap the lockfile. */ - else if (unlink (lfname) < 0) - ret = -1; else - ret = 0; + return unlink (lfname); } else { /* If we wanted to support the check for stale locks on remote machines, @@ -535,11 +605,6 @@ current_lock_owner (lock_info_type *owner, char *lfname) ret = 1; } - /* Avoid garbage. */ - if (owner == &local_owner || ret <= 0) - { - FREE_LOCK_INFO (*owner); - } return ret; } @@ -551,29 +616,25 @@ current_lock_owner (lock_info_type *owner, char *lfname) Return -1 if cannot lock for any other reason. */ static int -lock_if_free (lock_info_type *clasher, register char *lfname) +lock_if_free (lock_info_type *clasher, char *lfname) { - while (! lock_file_1 (lfname, 0)) + int err; + while ((err = lock_file_1 (lfname, 0)) == EEXIST) { - int locker; - - if (errno != EEXIST) - return -1; - - locker = current_lock_owner (clasher, lfname); - if (locker == 2) - { - FREE_LOCK_INFO (*clasher); - return 0; /* We ourselves locked it. */ - } - else if (locker == 1) - return 1; /* Someone else has it. */ - else if (locker == -1) - return -1; /* current_lock_owner returned strange error. */ + switch (current_lock_owner (clasher, lfname)) + { + case 2: + return 0; /* We ourselves locked it. */ + case 1: + return 1; /* Someone else has it. */ + case -1: + return -1; /* current_lock_owner returned strange error. */ + } /* We deleted a stale lock; try again to lock the file. */ } - return 0; + + return err ? -1 : 0; } /* lock_file locks file FN, @@ -645,17 +706,16 @@ lock_file (Lisp_Object fn) if (0 < lock_if_free (&lock_info, lfname)) { /* Someone else has the lock. Consider breaking it. */ - ptrdiff_t locker_size = (strlen (lock_info.user) + strlen (lock_info.host) - + INT_STRLEN_BOUND (printmax_t) - + sizeof "@ (pid )"); - char *locker = SAFE_ALLOCA (locker_size); - printmax_t pid = lock_info.pid; Lisp_Object attack; - esprintf (locker, "%s@%s (pid %"pMd")", - lock_info.user, lock_info.host, pid); - FREE_LOCK_INFO (lock_info); - - attack = call2 (intern ("ask-user-about-lock"), fn, build_string (locker)); + char *dot = lock_info.dot; + ptrdiff_t pidlen = lock_info.colon - (dot + 1); + static char const replacement[] = " (pid "; + int replacementlen = sizeof replacement - 1; + memmove (dot + replacementlen, dot + 1, pidlen); + strcpy (dot + replacementlen + pidlen, ")"); + memcpy (dot, replacement, replacementlen); + attack = call2 (intern ("ask-user-about-lock"), fn, + build_string (lock_info.user)); /* Take the lock if the user said so. */ if (!NILP (attack)) lock_file_1 (lfname, 1); @@ -685,16 +745,15 @@ unlock_file (Lisp_Object fn) void unlock_all_files (void) { - register Lisp_Object tail; + register Lisp_Object tail, buf; register struct buffer *b; - for (tail = Vbuffer_alist; CONSP (tail); tail = XCDR (tail)) + FOR_EACH_LIVE_BUFFER (tail, buf) { - b = XBUFFER (XCDR (XCAR (tail))); - if (STRINGP (BVAR (b, file_truename)) && BUF_SAVE_MODIFF (b) < BUF_MODIFF (b)) - { - unlock_file (BVAR (b, file_truename)); - } + b = XBUFFER (buf); + if (STRINGP (BVAR (b, file_truename)) + && BUF_SAVE_MODIFF (b) < BUF_MODIFF (b)) + unlock_file (BVAR (b, file_truename)); } } @@ -760,10 +819,7 @@ t if it is locked by you, else a string saying which user has locked it. */) else if (owner == 2) ret = Qt; else - ret = build_string (locker.user); - - if (owner > 0) - FREE_LOCK_INFO (locker); + ret = make_string (locker.user, locker.at - locker.user); SAFE_FREE (); return ret;