]> code.delx.au - gnu-emacs/commitdiff
Implement CLASH_DETECTION for MS-Windows.
authorEli Zaretskii <eliz@gnu.org>
Mon, 25 Feb 2013 17:36:03 +0000 (19:36 +0200)
committerEli Zaretskii <eliz@gnu.org>
Mon, 25 Feb 2013 17:36:03 +0000 (19:36 +0200)
 src/filelock.c [WINDOWSNT]: Include w32.h.
 (MAKE_LOCK_NAME): Don't use 'lock', it clashes with MS runtime
 function of that name.  Up-case the macro arguments.
 (IS_LOCK_FILE): New macro.
 (fill_in_lock_file_name): Use IS_LOCK_FILE instead of S_ISLNK.
 (create_lock_file): New function, with body extracted from
 lock_file_1.
 [WINDOWSNT]: Implement lock files by writing a regular file with
 the lock information as its contents.
 (read_lock_data): New function, on Posix platforms just calls
 emacs_readlinkat.
 [WINDOWSNT]: Read the lock info from the file.
 (current_lock_owner): Call read_lock_data instead of calling
 emacs_readlinkat directly.
 (lock_file) [WINDOWSNT]: Run the file name through
 dostounix_filename.
 src/w32proc.c (sys_kill): Support the case of SIG = 0, in which case
 just check if the process by that PID exists.
 src/w32.c (sys_open): Don't reset the _O_CREAT flag if _O_EXCL is
 also present, as doing so will fail to error out if the file
 already exists.
 src/makefile.w32-in ($(BLD)/filelock.$(O)): Depend on src/w32.h.

 nt/inc/ms-w32.h (BOOT_TIME_FILE): Define.
 nt/config.nt (CLASH_DETECTION): Define to 1.

 lisp/emacs-lisp/bytecomp.el (byte-recompile-directory): Reject files
 that match "\`\.#", to avoid compiling lock files, even if they
 are readable (as they are on MS-Windows).

 doc/emacs/files.texi (Interlocking): Don't refer to symlinks as the
 exclusive means of locking files.

 etc/NEWS: Mention support for lock files on MS-Windows.

13 files changed:
doc/emacs/ChangeLog
doc/emacs/files.texi
etc/NEWS
lisp/ChangeLog
lisp/emacs-lisp/bytecomp.el
nt/ChangeLog
nt/config.nt
nt/inc/ms-w32.h
src/ChangeLog
src/filelock.c
src/makefile.w32-in
src/w32.c
src/w32proc.c

index 33f530cbadc42dec86fd13b1516b322910357bcb..442454417910ad4e88ea2e4856179339367fb02b 100644 (file)
@@ -1,3 +1,8 @@
+2013-02-25  Eli Zaretskii  <eliz@gnu.org>
+
+       * files.texi (Interlocking): Don't refer to symlinks as the
+       exclusive means of locking files.
+
 2013-02-22  Glenn Morris  <rgm@gnu.org>
 
        * ack.texi (Acknowledgments):
index 7f7ae483cd5aab841f0b4afef0de8030aec4fcea..1f78747eaa6fbcdcccb98f6d8b9cf2ec7aec23fd 100644 (file)
@@ -734,10 +734,10 @@ file.
 @cindex locking files
   When you make the first modification in an Emacs buffer that is
 visiting a file, Emacs records that the file is @dfn{locked} by you.
-(It does this by creating a specially-named symbolic link in the same
-directory.)  Emacs removes the lock when you save the changes.  The
-idea is that the file is locked whenever an Emacs buffer visiting it
-has unsaved changes.
+(It does this by creating a specially-named symbolic link or regular
+file with special contents in the same directory.)  Emacs removes the
+lock when you save the changes.  The idea is that the file is locked
+whenever an Emacs buffer visiting it has unsaved changes.
 
 @vindex create-lockfiles
   You can prevent the creation of lock files by setting the variable
@@ -774,14 +774,14 @@ multiple names, Emacs does not prevent two users from editing it
 simultaneously under different names.
 
   A lock file cannot be written in some circumstances, e.g., if Emacs
-lacks the system permissions or the system does not support symbolic
-links.  In these cases, Emacs can still detect the collision when you
-try to save a file, by checking the file's last-modification date.  If
-the file has changed since the last time Emacs visited or saved it,
-that implies that changes have been made in some other way, and will
-be lost if Emacs proceeds with saving.  Emacs then displays a warning
-message and asks for confirmation before saving; answer @kbd{yes} to
-save, and @kbd{no} or @kbd{C-g} cancel the save.
+lacks the system permissions or cannot create lock files for some
+other reason.  In these cases, Emacs can still detect the collision
+when you try to save a file, by checking the file's last-modification
+date.  If the file has changed since the last time Emacs visited or
+saved it, that implies that changes have been made in some other way,
+and will be lost if Emacs proceeds with saving.  Emacs then displays a
+warning message and asks for confirmation before saving; answer
+@kbd{yes} to save, and @kbd{no} or @kbd{C-g} cancel the save.
 
   If you are notified that simultaneous editing has already taken
 place, one way to compare the buffer to its file is the @kbd{M-x
index 07f52adb2b17e84815ab0a1821aba67bf74209da..7d1110d5dc6c7d4f9f247453aee099de89d7e75a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -348,6 +348,13 @@ Setting it has no effect, and %t in the mode-line format is ignored.
 Likewise, `file-name-buffer-file-type-alist' is now obsolete, and
 modifying it has no effect.
 
+---
+** Lock files now work on MS-Windows.
+This allows to avoid losing your edits if the same file is being
+edited in another Emacs session or by another user.  See the node
+"Interlocking" in the Emacs User Manual for the details.  To disable
+file locking, customize `create-lockfiles' to nil.
+
 ** Improved fullscreen support on Mac OS X.
 Both native (>= OSX 10.7) and "old style" fullscreen are supported.
 Customize `ns-use-native-fullscreen' to change style.  For >= 10.7
index 07c3df9e279a5de18897ad37e6fd7fc18fee0aba..c8fb65e89e8190ff856698d3036635e65b2ad95d 100644 (file)
@@ -1,3 +1,9 @@
+2013-02-25  Eli Zaretskii  <eliz@gnu.org>
+
+       * emacs-lisp/bytecomp.el (byte-recompile-directory): Reject files
+       that match "\`\.#", to avoid compiling lock files, even if they
+       are readable (as they are on MS-Windows).
+
 2013-02-25  Stefan Monnier  <monnier@iro.umontreal.ca>
 
        * files.el (basic-save-buffer): Remove redundant directory-creation.
index f5861550c9a79082fbdd44933c07bab53accfc41..e0837033c7471e973bad41ff21cd4fa88f49bfc3 100644 (file)
@@ -1594,7 +1594,9 @@ that already has a `.elc' file."
                    (setq directories (nconc directories (list source))))
                ;; It is an ordinary file.  Decide whether to compile it.
                (if (and (string-match emacs-lisp-file-regexp source)
+                       ;; The next 2 tests avoid compiling lock files
                         (file-readable-p source)
+                       (not (string-match "\\`\\.#" file))
                         (not (auto-save-file-name-p source))
                         (not (string-equal dir-locals-file
                                            (file-name-nondirectory source))))
index a8bb8c74c9158779c3fd603036e5dbdb4f7f989d..fab143fcddcaead9e97704d0b6a58aa41a3b21d7 100644 (file)
@@ -1,3 +1,9 @@
+2013-02-25  Eli Zaretskii  <eliz@gnu.org>
+
+       * inc/ms-w32.h (BOOT_TIME_FILE): Define.
+
+       * config.nt (CLASH_DETECTION): Define to 1.
+
 2013-02-16  Eli Zaretskii  <eliz@gnu.org>
 
        * inc/ms-w32.h (__STDC__): Fiddle with value only for MSVC.
index 61e56174bb41094b24ad341e209592ce9e884d18..cc4e91d9af03cbb1788151dce31c70ed3993516b 100644 (file)
@@ -75,7 +75,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 /* Define if you want lock files to be written, so that Emacs can tell
    instantly when you try to modify a file that someone else has modified in
    his/her Emacs. */
-#undef CLASH_DETECTION
+#define CLASH_DETECTION 1
 
 /* Short copyright string for this version of Emacs. */
 #define COPYRIGHT "Copyright (C) 2013 Free Software Foundation, Inc."
index 66f586a4f7685b12dc0237cf6b585fd1e1e52f46..9473fbe3ca64b9577a8f8b6ebc9f1e43853bb927 100644 (file)
@@ -70,6 +70,18 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #define HAVE___BUILTIN_UNWIND_INIT 1
 #endif
 
+/* This isn't perfect, as some systems might have the page file in
+   another place.  Also, I suspect that the time stamp of that file
+   might also change when Windows enlarges the file due to
+   insufficient VM.  Still, this seems to be the most reliable way;
+   the alternative (of using GetSystemTimes) won't work on laptops
+   that hibernate, because the system clock is stopped then.  Other
+   possibility would be to run "net statistics workstation" and parse
+   the output, but that's gross.  So this should do; if the file is
+   not there, the boot time will be returned as zero, and filelock.c
+   already handles that.  */
+#define BOOT_TIME_FILE "C:/pagefile.sys"
+
 /* ============================================================ */
 
 /* Here, add any special hacks needed to make Emacs work on this
index dc9b97c3c0377e6b1d12bdbf3aec091437d4f21d..135d4d48b419bdb8a26a8374439979a859efc6c5 100644 (file)
@@ -1,5 +1,33 @@
 2013-02-25  Eli Zaretskii  <eliz@gnu.org>
 
+       Implement CLASH_DETECTION for MS-Windows.
+
+       * filelock.c [WINDOWSNT]: Include w32.h.
+       (MAKE_LOCK_NAME): Don't use 'lock', it clashes with MS runtime
+       function of that name.  Up-case the macro arguments.
+       (IS_LOCK_FILE): New macro.
+       (fill_in_lock_file_name): Use IS_LOCK_FILE instead of S_ISLNK.
+       (create_lock_file): New function, with body extracted from
+       lock_file_1.
+       [WINDOWSNT]: Implement lock files by writing a regular file with
+       the lock information as its contents.
+       (read_lock_data): New function, on Posix platforms just calls
+       emacs_readlinkat.
+       [WINDOWSNT]: Read the lock info from the file.
+       (current_lock_owner): Call read_lock_data instead of calling
+       emacs_readlinkat directly.
+       (lock_file) [WINDOWSNT]: Run the file name through
+       dostounix_filename.
+
+       * w32proc.c (sys_kill): Support the case of SIG = 0, in which case
+       just check if the process by that PID exists.
+
+       * w32.c (sys_open): Don't reset the _O_CREAT flag if _O_EXCL is
+       also present, as doing so will fail to error out if the file
+       already exists.
+
+       * makefile.w32-in ($(BLD)/filelock.$(O)): Depend on src/w32.h.
+
        * textprop.c (Fadd_text_properties, Fremove_text_properties)
        (Fremove_list_of_text_properties): Skip all of the intervals in
        the region between START and END that already have resp. don't
index cd2cd2e53a2386f38df24ace2aab82289604c81e..4d556de245481eeec0b88b5689207799d405948f 100644 (file)
@@ -43,6 +43,9 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "buffer.h"
 #include "coding.h"
 #include "systime.h"
+#ifdef WINDOWSNT
+#include "w32.h"       /* for dostounix_filename */
+#endif
 
 #ifdef CLASH_DETECTION
 
@@ -288,13 +291,22 @@ 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 = 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)
@@ -318,7 +330,7 @@ fill_in_lock_file_name (register char *lockfile, register Lisp_Object fn)
 
   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,6 +341,49 @@ 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.  */
+  {
+    int fd = emacs_open (lfname, O_WRONLY | O_BINARY | O_CREAT | O_EXCL,
+                        S_IREAD | S_IWRITE);
+
+    if (fd < 0 && errno == EEXIST && force)
+      fd = emacs_open (lfname, O_WRONLY | O_BINARY | O_TRUNC,
+                      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.  */
@@ -355,13 +410,7 @@ lock_file_1 (char *lfname, bool force)
 
   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 ();
@@ -377,6 +426,32 @@ 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,
@@ -390,7 +465,7 @@ current_lock_owner (lock_info_type *owner, char *lfname)
   lock_info_type local_owner;
   intmax_t n;
   char *at, *dot, *colon;
-  Lisp_Object lfinfo_object = emacs_readlinkat (AT_FDCWD, lfname);
+  Lisp_Object lfinfo_object = read_lock_data (lfname);
   char *lfinfo;
   struct gcpro gcpro1;
 
@@ -552,6 +627,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 */
index d60331198db9930251eed65094eeadb12bd255f9..93f12900dded956825492c73a90dcc4642ca67a8 100644 (file)
@@ -864,6 +864,7 @@ $(BLD)/fileio.$(O) : \
 
 $(BLD)/filelock.$(O) : \
        $(SRC)/filelock.c \
+       $(SRC)/w32.h \
        $(NT_INC)/pwd.h \
        $(NT_INC)/sys/file.h \
        $(NT_INC)/sys/stat.h \
index 5011642adf27c9c87f678f0a39260ea89af43707..aff9771e4bbbaa15e7ca453551029204e395131e 100644 (file)
--- a/src/w32.c
+++ b/src/w32.c
@@ -3402,10 +3402,13 @@ int
 sys_open (const char * path, int oflag, int mode)
 {
   const char* mpath = map_w32_filename (path, NULL);
-  /* Try to open file without _O_CREAT, to be able to write to hidden
-     and system files. Force all file handles to be
-     non-inheritable. */
-  int res = _open (mpath, (oflag & ~_O_CREAT) | _O_NOINHERIT, mode);
+  int res = -1;
+
+  /* If possible, try to open file without _O_CREAT, to be able to
+     write to existing hidden and system files.  Force all file
+     handles to be non-inheritable. */
+  if ((oflag & (_O_CREAT | _O_EXCL)) != (_O_CREAT | _O_EXCL))
+    res = _open (mpath, (oflag & ~_O_CREAT) | _O_NOINHERIT, mode);
   if (res < 0)
     res = _open (mpath, oflag | _O_NOINHERIT, mode);
   if (res >= 0 && res < MAXDESC)
index 961791a40ed97739637f460e32c57566863d7dfb..84589388cd7047647dc1fdef263532656f5481fd 100644 (file)
@@ -2263,12 +2263,42 @@ sys_kill (pid_t pid, int sig)
     pid = -pid;
 
   /* Only handle signals that will result in the process dying */
-  if (sig != SIGINT && sig != SIGKILL && sig != SIGQUIT && sig != SIGHUP)
+  if (sig != 0
+      && sig != SIGINT && sig != SIGKILL && sig != SIGQUIT && sig != SIGHUP)
     {
       errno = EINVAL;
       return -1;
     }
 
+  if (sig == 0)
+    {
+      /* It will take _some_ time before PID 4 or less on Windows will
+        be Emacs...  */
+      if (pid <= 4)
+       {
+         errno = EPERM;
+         return -1;
+       }
+      proc_hand = OpenProcess (PROCESS_QUERY_INFORMATION, 0, pid);
+      if (proc_hand == NULL)
+        {
+         DWORD err = GetLastError ();
+
+         switch (err)
+           {
+           case ERROR_ACCESS_DENIED: /* existing process, but access denied */
+             errno = EPERM;
+             return -1;
+           case ERROR_INVALID_PARAMETER: /* process PID does not exist */
+             errno = ESRCH;
+             return -1;
+           }
+       }
+      else
+       CloseHandle (proc_hand);
+      return 0;
+    }
+
   cp = find_child_pid (pid);
   if (cp == NULL)
     {