]> 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 12bcacff1a436b228b9128844d146c4274aa66df..78cd60a12e10e3a167bf3f1c10c194e7efcd2360 100644 (file)
@@ -1,7 +1,6 @@
 /* Lock files for editing.
-   Copyright (C) 1985, 1986, 1987, 1993, 1994, 1996, 1998, 1999, 2000, 2001,
-                 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 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.
 
@@ -24,42 +23,30 @@ 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>
 #endif
 
 #include <sys/file.h>
-#ifdef HAVE_FCNTL_H
 #include <fcntl.h>
-#endif
-#ifdef HAVE_STRING_H
-#include <string.h>
-#endif
-
-#ifdef HAVE_UNISTD_H
 #include <unistd.h>
-#endif
 
 #ifdef __FreeBSD__
 #include <sys/sysctl.h>
 #endif /* __FreeBSD__ */
 
 #include <errno.h>
-#ifndef errno
-extern int errno;
-#endif
 
 #include "lisp.h"
-#include "buffer.h"
 #include "character.h"
+#include "buffer.h"
 #include "coding.h"
 #include "systime.h"
-
-/* The directory for writing temporary files.  */
-
-Lisp_Object Vtemporary_file_directory;
+#ifdef WINDOWSNT
+#include <share.h>
+#include "w32.h"       /* for dostounix_filename */
+#endif
 
 #ifdef CLASH_DETECTION
 
@@ -67,10 +54,6 @@ Lisp_Object Vtemporary_file_directory;
 #include <utmp.h>
 #endif
 
-#if !defined (S_ISLNK) && defined (S_IFLNK)
-#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
-#endif
-
 /* A file whose last-modified time is just after the most recent boot.
    Define this to be NULL to disable checking for this file.  */
 #ifndef BOOT_TIME_FILE
@@ -120,16 +103,14 @@ Lisp_Object Vtemporary_file_directory;
 /* Return the time of the last system boot.  */
 
 static time_t boot_time;
-static int boot_time_initialized;
-
-extern Lisp_Object Vshell_file_name;
+static bool boot_time_initialized;
 
 #ifdef BOOT_TIME
-static void get_boot_time_1 P_ ((char *, int));
+static void get_boot_time_1 (const char *, bool);
 #endif
 
 static time_t
-get_boot_time ()
+get_boot_time (void)
 {
 #if defined (BOOT_TIME)
   int counter;
@@ -190,20 +171,20 @@ get_boot_time ()
   /* If we did not find a boot time in wtmp, look at wtmp, and so on.  */
   for (counter = 0; counter < 20 && ! boot_time; counter++)
     {
-      char cmd_string[100];
+      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];
@@ -213,28 +194,25 @@ get_boot_time ()
                 character long prefix, and call make_temp_file with
                 second arg non-zero, so that it will add not more
                 than 6 characters to the prefix.  */
-             tempname = Fexpand_file_name (build_string ("wt"),
+             filename = Fexpand_file_name (build_string ("wt"),
                                            Vtemporary_file_directory);
-             tempname = make_temp_name (tempname, 1);
-             args[0] = Vshell_file_name;
+             filename = make_temp_name (filename, 1);
+             args[0] = build_string ("gzip");
              args[1] = Qnil;
-             args[2] = Qnil;
+             args[2] = list2 (QCfile, filename);
              args[3] = Qnil;
-             args[4] = build_string ("-c");
-             sprintf (cmd_string, "gunzip < %s.%d.gz > %s",
-                      WTMP_FILE, counter, SDATA (tempname));
-             args[5] = build_string (cmd_string);
+             args[4] = build_string ("-cd");
+             args[5] = tempname;
              Fcall_process (6, args);
-             filename = tempname;
              delete_flag = 1;
            }
        }
 
       if (! NILP (filename))
        {
-         get_boot_time_1 (SDATA (filename), 1);
+         get_boot_time_1 (SSDATA (filename), 1);
          if (delete_flag)
-           unlink (SDATA (filename));
+           unlink (SSDATA (filename));
        }
     }
 
@@ -250,15 +228,13 @@ get_boot_time ()
 
    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 (filename, newest)
-     char *filename;
-     int newest;
+get_boot_time_1 (const char *filename, bool newest)
 {
   struct utmp ut, *utp;
   int desc;
@@ -308,50 +284,54 @@ typedef struct
 {
   char *user;
   char *host;
-  unsigned long pid;
+  pid_t pid;
   time_t boot_time;
 } lock_info_type;
 
-/* When we read the info back, we might need this much more,
-   enough for decimal representation plus null.  */
-#define LOCK_PID_MAX (4 * sizeof (unsigned long))
-
 /* Free the two dynamically-allocated pieces in PTR.  */
 #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 (lockfile, fn)
-     register char *lockfile;
-     register Lisp_Object fn;
+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;
 
-  strcpy (lockfile, SDATA (fn));
+  strcpy (lockfile, SSDATA (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)
        {
@@ -362,150 +342,194 @@ fill_in_lock_file_name (lockfile, fn)
     }
 }
 
-/* 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.  */
-
 static int
-lock_file_1 (lfname, force)
-     char *lfname;
-     int force;
+create_lock_file (char *lfname, char *lock_info_str, bool force)
 {
-  register int err;
-  time_t boot_time;
-  char *user_name;
-  char *host_name;
-  char *lock_info_str;
-
-  /* Call this first because it can GC.  */
-  boot_time = get_boot_time ();
-
-  if (STRINGP (Fuser_login_name (Qnil)))
-    user_name = (char *)SDATA (Fuser_login_name (Qnil));
-  else
-    user_name = "";
-  if (STRINGP (Fsystem_name ()))
-    host_name = (char *)SDATA (Fsystem_name ());
-  else
-    host_name = "";
-  lock_info_str = (char *)alloca (strlen (user_name) + strlen (host_name)
-                                 + LOCK_PID_MAX + 30);
-
-  if (boot_time)
-    sprintf (lock_info_str, "%s@%s.%lu:%lu", user_name, host_name,
-            (unsigned long) getpid (), (unsigned long) boot_time);
-  else
-    sprintf (lock_info_str, "%s@%s.%lu", user_name, host_name,
-            (unsigned long) getpid ());
+  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, do so even if it is already locked.
+   Return true if successful.  */
+
+static bool
+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 ();
+
+  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 = create_lock_file (lfname, lock_info_str, force);
+
+  symlink_errno = errno;
+  SAFE_FREE ();
+  errno = symlink_errno;
   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.  */
 
-int
-within_one_second (a, b)
-     time_t a, b;
+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,
    or -1 if something is wrong with the locking mechanism.  */
 
 static int
-current_lock_owner (owner, lfname)
-     lock_info_type *owner;
-     char *lfname;
+current_lock_owner (lock_info_type *owner, char *lfname)
 {
-#ifndef index
-  extern char *rindex (), *index ();
-#endif
-  int len, ret;
-  int local_owner = 0;
+  int ret;
+  ptrdiff_t len;
+  lock_info_type local_owner;
+  intmax_t n;
   char *at, *dot, *colon;
-  char *lfinfo = 0;
-  int bufsize = 50;
-  /* Read arbitrarily-long contents of symlink.  Similar code in
-     file-symlink-p in fileio.c.  */
-  do
-    {
-      bufsize *= 2;
-      lfinfo = (char *) xrealloc (lfinfo, bufsize);
-      errno = 0;
-      len = readlink (lfname, lfinfo, bufsize);
-#ifdef ERANGE
-      /* HP-UX reports ERANGE if the buffer is too small.  */
-      if (len == -1 && errno == ERANGE)
-       len = bufsize;
-#endif
-    }
-  while (len >= bufsize);
+  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 (len == -1)
-    {
-      xfree (lfinfo);
-      return errno == ENOENT ? 0 : -1;
-    }
-
-  /* Link info exists, so `len' is its length.  Null terminate.  */
-  lfinfo[len] = 0;
+  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, so allocate it.  */
+     read it to determine return value.  */
   if (!owner)
-    {
-      owner = (lock_info_type *) alloca (sizeof (lock_info_type));
-      local_owner = 1;
-    }
+    owner = &local_owner;
 
   /* Parse USER@HOST.PID:BOOT_TIME.  If can't parse, return -1.  */
   /* The USER is everything before the last @.  */
-  at = rindex (lfinfo, '@');
-  dot = rindex (lfinfo, '.');
+  at = strrchr (lfinfo, '@');
+  dot = strrchr (lfinfo, '.');
   if (!at || !dot)
-    {
-      xfree (lfinfo);
-      return -1;
-    }
+    return -1;
   len = at - lfinfo;
-  owner->user = (char *) xmalloc (len + 1);
-  strncpy (owner->user, lfinfo, len);
+  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 `:'.  */
-  owner->pid = atoi (dot + 1);
-  colon = dot;
-  while (*colon && *colon != ':')
-    colon++;
+  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);
+
+  colon = strchr (dot + 1, ':');
   /* After the `:', if there is one, comes the boot time.  */
-  if (*colon == ':')
-    owner->boot_time = atoi (colon + 1);
-  else
-    owner->boot_time = 0;
+  n = 0;
+  if (colon)
+    {
+      errno = 0;
+      n = strtoimax (colon + 1, NULL, 10);
+    }
+  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 = (char *) xmalloc (len + 1);
-  strncpy (owner->host, at + 1, len);
+  owner->host = xmalloc (len + 1);
+  memcpy (owner->host, at + 1, len);
   owner->host[len] = 0;
 
   /* We're done looking at the link info.  */
-  xfree (lfinfo);
+  UNGCPRO;
 
   /* On current host?  */
   if (STRINGP (Fsystem_name ())
-      && strcmp (owner->host, SDATA (Fsystem_name ())) == 0)
+      && strcmp (owner->host, SSDATA (Fsystem_name ())) == 0)
     {
       if (owner->pid == getpid ())
         ret = 2; /* We own it.  */
@@ -528,7 +552,7 @@ current_lock_owner (owner, lfname)
     }
 
   /* Avoid garbage.  */
-  if (local_owner || ret <= 0)
+  if (owner == &local_owner || ret <= 0)
     {
       FREE_LOCK_INFO (*owner);
     }
@@ -543,11 +567,9 @@ current_lock_owner (owner, lfname)
    Return -1 if cannot lock for any other reason.  */
 
 static int
-lock_if_free (clasher, lfname)
-     lock_info_type *clasher;
-     register char *lfname;
+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;
 
@@ -588,13 +610,19 @@ lock_if_free (clasher, lfname)
    take away the lock, or return nil meaning ignore the lock.  */
 
 void
-lock_file (fn)
-     Lisp_Object fn;
+lock_file (Lisp_Object fn)
 {
   register Lisp_Object attack, orig_fn, encoded_fn;
   register char *lfname, *locker;
+  ptrdiff_t locker_size;
   lock_info_type lock_info;
+  printmax_t pid;
   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
@@ -605,6 +633,12 @@ lock_file (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 */
@@ -631,13 +665,17 @@ lock_file (fn)
     return;
 
   /* Else consider breaking the lock */
-  locker = (char *) alloca (strlen (lock_info.user) + strlen (lock_info.host)
-                           + LOCK_PID_MAX + 9);
-  sprintf (locker, "%s@%s (pid %lu)", lock_info.user, lock_info.host,
-           lock_info.pid);
+  locker_size = (strlen (lock_info.user) + strlen (lock_info.host)
+                + INT_STRLEN_BOUND (printmax_t)
+                + sizeof "@ (pid )");
+  locker = SAFE_ALLOCA (locker_size);
+  pid = lock_info.pid;
+  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));
+  SAFE_FREE ();
   if (!NILP (attack))
     /* User says take the lock */
     {
@@ -648,8 +686,7 @@ lock_file (fn)
 }
 
 void
-unlock_file (fn)
-     register Lisp_Object fn;
+unlock_file (register Lisp_Object fn)
 {
   register char *lfname;
 
@@ -663,7 +700,7 @@ unlock_file (fn)
 }
 
 void
-unlock_all_files ()
+unlock_all_files (void)
 {
   register Lisp_Object tail;
   register struct buffer *b;
@@ -671,9 +708,9 @@ unlock_all_files ()
   for (tail = Vbuffer_alist; CONSP (tail); tail = XCDR (tail))
     {
       b = XBUFFER (XCDR (XCAR (tail)));
-      if (STRINGP (b->file_truename) && BUF_SAVE_MODIFF (b) < BUF_MODIFF (b))
+      if (STRINGP (BVAR (b, file_truename)) && BUF_SAVE_MODIFF (b) < BUF_MODIFF (b))
        {
-         unlock_file(b->file_truename);
+         unlock_file (BVAR (b, file_truename));
        }
     }
 }
@@ -683,11 +720,10 @@ DEFUN ("lock-buffer", Flock_buffer, Slock_buffer,
        doc: /* Lock FILE, if current buffer is modified.
 FILE defaults to current buffer's visited file,
 or else nothing is done if current buffer isn't visiting a file.  */)
-     (file)
-     Lisp_Object file;
+  (Lisp_Object file)
 {
   if (NILP (file))
-    file = current_buffer->file_truename;
+    file = BVAR (current_buffer, file_truename);
   else
     CHECK_STRING (file);
   if (SAVE_MODIFF < MODIFF
@@ -701,31 +737,29 @@ DEFUN ("unlock-buffer", Funlock_buffer, Sunlock_buffer,
        doc: /* Unlock the file visited in the current buffer.
 If the buffer is not modified, this does nothing because the file
 should not be locked in that case.  */)
-     ()
+  (void)
 {
   if (SAVE_MODIFF < MODIFF
-      && STRINGP (current_buffer->file_truename))
-    unlock_file (current_buffer->file_truename);
+      && STRINGP (BVAR (current_buffer, file_truename)))
+    unlock_file (BVAR (current_buffer, file_truename));
   return Qnil;
 }
 
 /* Unlock the file visited in buffer BUFFER.  */
 
 void
-unlock_buffer (buffer)
-     struct buffer *buffer;
+unlock_buffer (struct buffer *buffer)
 {
   if (BUF_SAVE_MODIFF (buffer) < BUF_MODIFF (buffer)
-      && STRINGP (buffer->file_truename))
-    unlock_file (buffer->file_truename);
+      && STRINGP (BVAR (buffer, file_truename)))
+    unlock_file (BVAR (buffer, file_truename));
 }
 
 DEFUN ("file-locked-p", Ffile_locked_p, Sfile_locked_p, 1, 1, 0,
        doc: /* Return a value indicating whether FILENAME is locked.
 The value is nil if the FILENAME is not locked,
 t if it is locked by you, else a string saying which user has locked it.  */)
-     (filename)
-     Lisp_Object filename;
+  (Lisp_Object filename)
 {
   Lisp_Object ret;
   register char *lfname;
@@ -749,29 +783,23 @@ t if it is locked by you, else a string saying which user has locked it.  */)
 
   return ret;
 }
-\f
-/* Initialization functions.  */
 
-void
-init_filelock ()
-{
-  boot_time = 0;
-  boot_time_initialized = 0;
-}
+#endif /* CLASH_DETECTION */
 
 void
-syms_of_filelock ()
+syms_of_filelock (void)
 {
-  DEFVAR_LISP ("temporary-file-directory", &Vtemporary_file_directory,
+  DEFVAR_LISP ("temporary-file-directory", Vtemporary_file_directory,
               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);
   defsubr (&Sfile_locked_p);
+#endif
 }
-
-#endif /* CLASH_DETECTION */
-
-/* arch-tag: e062676d-50b2-4be0-ab96-197c81b181a1
-   (do not change this comment) */