From 343a2aefb528ce3c978ba2145705b9e37bfbe02a Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 25 Feb 2013 19:36:03 +0200 Subject: [PATCH] Implement CLASH_DETECTION for MS-Windows. 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. --- doc/emacs/ChangeLog | 5 ++ doc/emacs/files.texi | 24 ++++---- etc/NEWS | 7 +++ lisp/ChangeLog | 6 ++ lisp/emacs-lisp/bytecomp.el | 2 + nt/ChangeLog | 6 ++ nt/config.nt | 2 +- nt/inc/ms-w32.h | 12 ++++ src/ChangeLog | 28 +++++++++ src/filelock.c | 113 +++++++++++++++++++++++++++++++----- src/makefile.w32-in | 1 + src/w32.c | 11 ++-- src/w32proc.c | 32 +++++++++- 13 files changed, 215 insertions(+), 34 deletions(-) diff --git a/doc/emacs/ChangeLog b/doc/emacs/ChangeLog index 33f530cbadc..44245441791 100644 --- a/doc/emacs/ChangeLog +++ b/doc/emacs/ChangeLog @@ -1,3 +1,8 @@ +2013-02-25 Eli Zaretskii + + * files.texi (Interlocking): Don't refer to symlinks as the + exclusive means of locking files. + 2013-02-22 Glenn Morris * ack.texi (Acknowledgments): diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 7f7ae483cd5..1f78747eaa6 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 07f52adb2b1..7d1110d5dc6 100644 --- 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 diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 07c3df9e279..c8fb65e89e8 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,9 @@ +2013-02-25 Eli Zaretskii + + * 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 * files.el (basic-save-buffer): Remove redundant directory-creation. diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index f5861550c9a..e0837033c74 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -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)))) diff --git a/nt/ChangeLog b/nt/ChangeLog index a8bb8c74c91..fab143fcddc 100644 --- a/nt/ChangeLog +++ b/nt/ChangeLog @@ -1,3 +1,9 @@ +2013-02-25 Eli Zaretskii + + * inc/ms-w32.h (BOOT_TIME_FILE): Define. + + * config.nt (CLASH_DETECTION): Define to 1. + 2013-02-16 Eli Zaretskii * inc/ms-w32.h (__STDC__): Fiddle with value only for MSVC. diff --git a/nt/config.nt b/nt/config.nt index 61e56174bb4..cc4e91d9af0 100644 --- a/nt/config.nt +++ b/nt/config.nt @@ -75,7 +75,7 @@ along with GNU Emacs. If not, see . */ /* 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." diff --git a/nt/inc/ms-w32.h b/nt/inc/ms-w32.h index 66f586a4f76..9473fbe3ca6 100644 --- a/nt/inc/ms-w32.h +++ b/nt/inc/ms-w32.h @@ -70,6 +70,18 @@ along with GNU Emacs. If not, see . */ #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 diff --git a/src/ChangeLog b/src/ChangeLog index dc9b97c3c03..135d4d48b41 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,5 +1,33 @@ 2013-02-25 Eli Zaretskii + 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 diff --git a/src/filelock.c b/src/filelock.c index cd2cd2e53a2..4d556de2454 100644 --- a/src/filelock.c +++ b/src/filelock.c @@ -43,6 +43,9 @@ along with GNU Emacs. If not, see . */ #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); } +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 */ diff --git a/src/makefile.w32-in b/src/makefile.w32-in index d60331198db..93f12900dde 100644 --- a/src/makefile.w32-in +++ b/src/makefile.w32-in @@ -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 \ diff --git a/src/w32.c b/src/w32.c index 5011642adf2..aff9771e4bb 100644 --- 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) diff --git a/src/w32proc.c b/src/w32proc.c index 961791a40ed..84589388cd7 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -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) { -- 2.39.2