/* 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.
#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
#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
/* 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;
/* 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];
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));
}
}
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;
{
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)
{
}
}
-/* 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. */
}
/* Avoid garbage. */
- if (local_owner || ret <= 0)
+ if (owner == &local_owner || ret <= 0)
{
FREE_LOCK_INFO (*owner);
}
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;
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
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 */
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 */
{
}
void
-unlock_file (fn)
- register Lisp_Object fn;
+unlock_file (register Lisp_Object fn)
{
register char *lfname;
}
void
-unlock_all_files ()
+unlock_all_files (void)
{
register Lisp_Object tail;
register struct buffer *b;
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));
}
}
}
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
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;
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) */