]> code.delx.au - gnu-emacs/blobdiff - src/filelock.c
Fix race conditions with MS-Windows lock files by using _sopen.
[gnu-emacs] / src / filelock.c
index 2613eec4aca44917c3abda08d57ffb0496680f4c..78cd60a12e10e3a167bf3f1c10c194e7efcd2360 100644 (file)
@@ -1,6 +1,6 @@
 /* Lock files for editing.
-   Copyright (C) 1985-1987, 1993-1994, 1996, 1998-2012
-                 Free Software Foundation, Inc.
+   Copyright (C) 1985-1987, 1993-1994, 1996, 1998-2013 Free Software
+   Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -23,7 +23,6 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <sys/stat.h>
 #include <signal.h>
 #include <stdio.h>
-#include <setjmp.h>
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
@@ -40,10 +39,14 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <errno.h>
 
 #include "lisp.h"
-#include "buffer.h"
 #include "character.h"
+#include "buffer.h"
 #include "coding.h"
 #include "systime.h"
+#ifdef WINDOWSNT
+#include <share.h>
+#include "w32.h"       /* for dostounix_filename */
+#endif
 
 #ifdef CLASH_DETECTION
 
@@ -100,10 +103,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 /* Return the time of the last system boot.  */
 
 static time_t boot_time;
-static int boot_time_initialized;
+static bool boot_time_initialized;
 
 #ifdef BOOT_TIME
-static void get_boot_time_1 (const char *, int);
+static void get_boot_time_1 (const char *, bool);
 #endif
 
 static time_t
@@ -170,18 +173,18 @@ get_boot_time (void)
     {
       char cmd_string[sizeof WTMP_FILE ".19.gz"];
       Lisp_Object tempname, filename;
-      int delete_flag = 0;
+      bool delete_flag = 0;
 
       filename = Qnil;
 
-      sprintf (cmd_string, "%s.%d", WTMP_FILE, counter);
-      tempname = build_string (cmd_string);
+      tempname = make_formatted_string
+       (cmd_string, "%s.%d", WTMP_FILE, counter);
       if (! NILP (Ffile_exists_p (tempname)))
        filename = tempname;
       else
        {
-         sprintf (cmd_string, "%s.%d.gz", WTMP_FILE, counter);
-         tempname = build_string (cmd_string);
+         tempname = make_formatted_string (cmd_string, "%s.%d.gz",
+                                           WTMP_FILE, counter);
          if (! NILP (Ffile_exists_p (tempname)))
            {
              Lisp_Object args[6];
@@ -225,13 +228,13 @@ get_boot_time (void)
 
    If FILENAME is zero, use the same file as before;
    if no FILENAME has ever been specified, this is the utmp file.
-   Use the newest reboot record if NEWEST is nonzero,
+   Use the newest reboot record if NEWEST,
    the first reboot record otherwise.
    Ignore all reboot records on or before BOOT_TIME.
    Success is indicated by setting BOOT_TIME to a larger value.  */
 
 void
-get_boot_time_1 (const char *filename, int newest)
+get_boot_time_1 (const char *filename, bool newest)
 {
   struct utmp ut, *utp;
   int desc;
@@ -289,17 +292,27 @@ typedef struct
 #define FREE_LOCK_INFO(i) do { xfree ((i).user); xfree ((i).host); } while (0)
 
 
-/* Write the name of the lock file for FN into LFNAME.  Length will be
-   that of FN plus two more for the leading `.#' plus 1 for the
-   trailing period plus one for the digit after it plus one for the
-   null.  */
-#define MAKE_LOCK_NAME(lock, file) \
-  (lock = (char *) alloca (SBYTES (file) + 2 + 1 + 1 + 1), \
-   fill_in_lock_file_name (lock, (file)))
+/* Write the name of the lock file for FNAME into LOCKNAME.  Length
+   will be that of FN plus two more for the leading `.#' plus 1 for
+   the trailing period plus one for the digit after it plus one for
+   the null.  */
+#define MAKE_LOCK_NAME(LOCKNAME, FNAME) \
+  (LOCKNAME = alloca (SBYTES (FNAME) + 2 + 1 + 1 + 1), \
+   fill_in_lock_file_name (LOCKNAME, (FNAME)))
+
+#ifdef WINDOWSNT
+/* 256 chars for user, 1024 chars for host, 10 digits for each of 2 int's.  */
+#define MAX_LFINFO (256 + 1024 + 10 + 10 + 2)
+                                                    /* min size: .@PID */
+#define IS_LOCK_FILE(ST) (MAX_LFINFO >= (ST).st_size && (ST).st_size >= 3)
+#else
+#define IS_LOCK_FILE(ST) S_ISLNK ((ST).st_mode)
+#endif
 
 static void
 fill_in_lock_file_name (register char *lockfile, register Lisp_Object fn)
 {
+  ptrdiff_t length = SBYTES (fn);
   register char *p;
   struct stat st;
   int count = 0;
@@ -309,16 +322,16 @@ fill_in_lock_file_name (register char *lockfile, register Lisp_Object fn)
   /* Shift the nondirectory part of the file name (including the null)
      right two characters.  Here is one of the places where we'd have to
      do something to support 14-character-max file names.  */
-  for (p = lockfile + strlen (lockfile); p != lockfile && *p != '/'; p--)
+  for (p = lockfile + length; p != lockfile && *p != '/'; p--)
     p[2] = *p;
 
   /* Insert the `.#'.  */
   p[1] = '.';
   p[2] = '#';
 
-  p = p + strlen (p);
+  p = lockfile + length + 2;
 
-  while (lstat (lockfile, &st) == 0 && !S_ISLNK (st.st_mode))
+  while (lstat (lockfile, &st) == 0 && !IS_LOCK_FILE (st))
     {
       if (count > 9)
        {
@@ -329,48 +342,81 @@ fill_in_lock_file_name (register char *lockfile, register Lisp_Object fn)
     }
 }
 
+static int
+create_lock_file (char *lfname, char *lock_info_str, 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 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);
+
+       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;
+  }
+#else
+  err = symlink (lock_info_str, lfname);
+  if (errno == EEXIST && force)
+    {
+      unlink (lfname);
+      err = symlink (lock_info_str, lfname);
+    }
+#endif
+
+  return err;
+}
+
 /* Lock the lock file named LFNAME.
-   If FORCE is nonzero, we do so even if it is already locked.
-   Return 1 if successful, 0 if not.  */
+   If FORCE, do so even if it is already locked.
+   Return true if successful.  */
 
-static int
-lock_file_1 (char *lfname, int force)
+static bool
+lock_file_1 (char *lfname, bool force)
 {
-  register int err;
-  printmax_t boot, pid;
-  const char *user_name;
-  const char *host_name;
-  char *lock_info_str;
-  ptrdiff_t lock_info_size;
+  int err;
   int symlink_errno;
   USE_SAFE_ALLOCA;
 
   /* Call this first because it can GC.  */
-  boot = get_boot_time ();
-
-  if (STRINGP (Fuser_login_name (Qnil)))
-    user_name = SSDATA (Fuser_login_name (Qnil));
-  else
-    user_name = "";
-  if (STRINGP (Fsystem_name ()))
-    host_name = SSDATA (Fsystem_name ());
-  else
-    host_name = "";
-  lock_info_size = (strlen (user_name) + strlen (host_name)
-                   + 2 * INT_STRLEN_BOUND (printmax_t)
-                   + sizeof "@.:");
-  SAFE_ALLOCA (lock_info_str, char *, lock_info_size);
-  pid = getpid ();
+  printmax_t boot = get_boot_time ();
+
+  Lisp_Object luser_name = Fuser_login_name (Qnil);
+  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);
+  printmax_t pid = getpid ();
 
   esprintf (lock_info_str, boot ? "%s@%s.%"pMd":%"pMd : "%s@%s.%"pMd,
            user_name, host_name, pid, boot);
-
-  err = symlink (lock_info_str, lfname);
-  if (errno == EEXIST && force)
-    {
-      unlink (lfname);
-      err = symlink (lock_info_str, lfname);
-    }
+  err = create_lock_file (lfname, lock_info_str, force);
 
   symlink_errno = errno;
   SAFE_FREE ();
@@ -378,14 +424,40 @@ lock_file_1 (char *lfname, int force)
   return err == 0;
 }
 
-/* Return 1 if times A and B are no more than one second apart.  */
+/* Return true if times A and B are no more than one second apart.  */
 
-static int
+static bool
 within_one_second (time_t a, time_t b)
 {
   return (a - b >= -1 && a - b <= 1);
 }
 \f
+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;
+  char lfinfo[MAX_LFINFO + 1];
+
+  if (fd < 0)
+    return Qnil;
+
+  nbytes = emacs_read (fd, lfinfo, MAX_LFINFO);
+  emacs_close (fd);
+
+  if (nbytes > 0)
+    {
+      lfinfo[nbytes] = '\0';
+      return build_string (lfinfo);
+    }
+  else
+    return Qnil;
+#endif
+}
+
 /* Return 0 if nobody owns the lock file LFNAME or the lock is obsolete,
    1 if another process owns it (and set OWNER (if non-null) to info),
    2 if the current process owns it,
@@ -399,12 +471,14 @@ current_lock_owner (lock_info_type *owner, char *lfname)
   lock_info_type local_owner;
   intmax_t n;
   char *at, *dot, *colon;
-  char readlink_buf[READLINK_BUFSIZE];
-  char *lfinfo = emacs_readlink (lfname, readlink_buf);
+  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 (!lfinfo)
+  if (NILP (lfinfo_object))
     return errno == ENOENT ? 0 : -1;
+  lfinfo = SSDATA (lfinfo_object);
 
   /* Even if the caller doesn't want the owner info, we still have to
      read it to determine return value.  */
@@ -416,13 +490,10 @@ current_lock_owner (lock_info_type *owner, char *lfname)
   at = strrchr (lfinfo, '@');
   dot = strrchr (lfinfo, '.');
   if (!at || !dot)
-    {
-      if (lfinfo != readlink_buf)
-       xfree (lfinfo);
-      return -1;
-    }
+    return -1;
   len = at - lfinfo;
-  owner->user = (char *) xmalloc (len + 1);
+  GCPRO1 (lfinfo_object);
+  owner->user = xmalloc (len + 1);
   memcpy (owner->user, lfinfo, len);
   owner->user[len] = 0;
 
@@ -449,13 +520,12 @@ current_lock_owner (lock_info_type *owner, char *lfname)
 
   /* The host is everything in between.  */
   len = dot - at - 1;
-  owner->host = (char *) xmalloc (len + 1);
+  owner->host = xmalloc (len + 1);
   memcpy (owner->host, at + 1, len);
   owner->host[len] = 0;
 
   /* We're done looking at the link info.  */
-  if (lfinfo != readlink_buf)
-    xfree (lfinfo);
+  UNGCPRO;
 
   /* On current host?  */
   if (STRINGP (Fsystem_name ())
@@ -499,7 +569,7 @@ current_lock_owner (lock_info_type *owner, char *lfname)
 static int
 lock_if_free (lock_info_type *clasher, register char *lfname)
 {
-  while (lock_file_1 (lfname, 0) == 0)
+  while (! lock_file_1 (lfname, 0))
     {
       int locker;
 
@@ -550,6 +620,10 @@ lock_file (Lisp_Object fn)
   struct gcpro gcpro1;
   USE_SAFE_ALLOCA;
 
+  /* Don't do locking if the user has opted out.  */
+  if (! create_lockfiles)
+    return;
+
   /* Don't do locking while dumping Emacs.
      Uncompressing wtmp files uses call-process, which does not work
      in an uninitialized Emacs.  */
@@ -559,6 +633,12 @@ lock_file (Lisp_Object fn)
   orig_fn = fn;
   GCPRO1 (fn);
   fn = Fexpand_file_name (fn, Qnil);
+#ifdef WINDOWSNT
+  /* Ensure we have only '/' separators, to avoid problems with
+     looking (inside fill_in_lock_file_name) for backslashes in file
+     names encoded by some DBCS codepage.  */
+  dostounix_filename (SSDATA (fn), 1);
+#endif
   encoded_fn = ENCODE_FILE (fn);
 
   /* Create the name of the lock-file for file fn */
@@ -588,7 +668,7 @@ lock_file (Lisp_Object fn)
   locker_size = (strlen (lock_info.user) + strlen (lock_info.host)
                 + INT_STRLEN_BOUND (printmax_t)
                 + sizeof "@ (pid )");
-  SAFE_ALLOCA (locker, char *, locker_size);
+  locker = SAFE_ALLOCA (locker_size);
   pid = lock_info.pid;
   esprintf (locker, "%s@%s (pid %"pMd")",
            lock_info.user, lock_info.host, pid);
@@ -703,15 +783,6 @@ t if it is locked by you, else a string saying which user has locked it.  */)
 
   return ret;
 }
-\f
-/* Initialization functions.  */
-
-void
-init_filelock (void)
-{
-  boot_time = 0;
-  boot_time_initialized = 0;
-}
 
 #endif /* CLASH_DETECTION */
 
@@ -722,6 +793,10 @@ syms_of_filelock (void)
               doc: /* The directory for writing temporary files.  */);
   Vtemporary_file_directory = Qnil;
 
+  DEFVAR_BOOL ("create-lockfiles", create_lockfiles,
+              doc: /* Non-nil means use lockfiles to avoid editing collisions.  */);
+  create_lockfiles = 1;
+
 #ifdef CLASH_DETECTION
   defsubr (&Sunlock_buffer);
   defsubr (&Slock_buffer);