]> code.delx.au - gnu-emacs/blobdiff - src/fileio.c
[!SYSTEM_MALLOC] (MEMMOVE_MISSING): Defined.
[gnu-emacs] / src / fileio.c
index 0a534287c8c8d787f08f42f7732da46117c258ef..09313252a3a105d2e9bd56a822c959c647fe252e 100644 (file)
@@ -1,5 +1,5 @@
 /* File IO for GNU Emacs.
-   Copyright (C) 1985, 1986, 1987, 1988, 1992 Free Software Foundation, Inc.
+   Copyright (C) 1985, 1986, 1987, 1988, 1993 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -17,11 +17,19 @@ You should have received a copy of the GNU General Public License
 along with GNU Emacs; see the file COPYING.  If not, write to
 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
 
-#include "config.h"
+#include <config.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#if !defined (S_ISLNK) && defined (S_IFLNK)
+#  define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+
+#if !defined (S_ISREG) && defined (S_IFREG)
+#  define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+
 #ifdef VMS
 #include "vms-pwd.h"
 #else
@@ -51,6 +59,14 @@ extern int sys_nerr;
 #include <sys/time.h>
 #endif
 
+#ifndef USG
+#ifndef VMS
+#ifndef BSD4_1
+#define HAVE_FSYNC
+#endif
+#endif
+#endif
+
 #include "lisp.h"
 #include "intervals.h"
 #include "buffer.h"
@@ -68,9 +84,11 @@ extern int sys_nerr;
 #ifdef HPUX
 #include <netio.h>
 #ifndef HPUX8
+#ifndef HPUX9
 #include <errnet.h>
 #endif
 #endif
+#endif
 
 #ifndef O_WRONLY
 #define O_WRONLY 1
@@ -90,6 +108,12 @@ int auto_save_mode_bits;
    whose I/O is done with a special handler.  */
 Lisp_Object Vfile_name_handler_alist;
 
+/* Functions to be called to process text properties in inserted file.  */
+Lisp_Object Vafter_insert_file_functions;
+
+/* Functions to be called to create text property annotations for file.  */
+Lisp_Object Vwrite_region_annotate_functions;
+
 /* Nonzero means, when reading a filename in the minibuffer,
  start out by inserting the default directory into the minibuffer. */
 int insert_default_directory;
@@ -102,6 +126,8 @@ Lisp_Object Qfile_error, Qfile_already_exists;
 
 Lisp_Object Qfile_name_history;
 
+Lisp_Object Qcar_less_than_car;
+
 report_file_error (string, data)
      char *string;
      Lisp_Object data;
@@ -155,6 +181,7 @@ Lisp_Object Qfile_newer_than_file_p;
 Lisp_Object Qinsert_file_contents;
 Lisp_Object Qwrite_region;
 Lisp_Object Qverify_visited_file_modtime;
+Lisp_Object Qset_visited_file_modtime;
 
 DEFUN ("find-file-name-handler", Ffind_file_name_handler, Sfind_file_name_handler, 1, 1, 0,
   "Return FILENAME's handler function, if its syntax is handled specially.\n\
@@ -165,8 +192,10 @@ A file name is handled if one of the regular expressions in\n\
     Lisp_Object filename;
 {
   /* This function must not munge the match data.  */
-
   Lisp_Object chain;
+
+  CHECK_STRING (filename, 0);
+
   for (chain = Vfile_name_handler_alist; XTYPE (chain) == Lisp_Cons;
        chain = XCONS (chain)->cdr)
     {
@@ -615,6 +644,11 @@ See also the function `substitute-in-file-name'.")
   if (!NILP (handler))
     return call3 (handler, Qexpand_file_name, name, defalt);
 
+  /* Use the buffer's default-directory if DEFALT is omitted.  */
+  if (NILP (defalt))
+    defalt = current_buffer->directory;
+  CHECK_STRING (defalt, 1);
+
   /* Make sure DEFALT is properly expanded.
      It would be better to do this down below where we actually use
      defalt.  Unfortunately, calling Fexpand_file_name recursively
@@ -622,8 +656,12 @@ See also the function `substitute-in-file-name'.")
      be annoying because we have pointers into strings lying around
      that would need adjusting, and people would add new pointers to
      the code and forget to adjust them, resulting in intermittent bugs.
-     Putting this call here avoids all that crud.  */
-  if (! NILP (defalt)) 
+     Putting this call here avoids all that crud.
+
+     The EQ test avoids infinite recursion.  */
+  if (! NILP (defalt) && !EQ (defalt, name)
+      /* This saves time in a common case.  */
+      && XSTRING (defalt)->data[0] != '/')
     {
       struct gcpro gcpro1;
 
@@ -823,9 +861,6 @@ See also the function `substitute-in-file-name'.")
 #endif /* not VMS */
       && !newdir)
     {
-      if (NILP (defalt))
-       defalt = current_buffer->directory;
-      CHECK_STRING (defalt, 1);
       newdir = XSTRING (defalt)->data;
     }
 
@@ -833,6 +868,9 @@ See also the function `substitute-in-file-name'.")
     {
       /* Get rid of any slash at the end of newdir.  */
       int length = strlen (newdir);
+      /* Adding `length > 1 &&' makes ~ expand into / when homedir
+        is the root dir.  People disagree about whether that is right.
+        Anyway, we can't take the risk of this change now.  */
       if (newdir[length - 1] == '/')
        {
          unsigned char *temp = (unsigned char *) alloca (length);
@@ -1484,11 +1522,7 @@ duplicates what `expand-file-name' does.")
 }
 \f
 /* A slightly faster and more convenient way to get
-   (directory-file-name (expand-file-name FOO)).  The return value may
-   have had its last character zapped with a '\0' character, meaning
-   that it is acceptable to system calls, but not to other lisp
-   functions.  Callers should make sure that the return value doesn't
-   escape.  */
+   (directory-file-name (expand-file-name FOO)).  */
 
 Lisp_Object
 expand_and_dir_to_file (filename, defdir)
@@ -1508,11 +1542,8 @@ expand_and_dir_to_file (filename, defdir)
      stat behaves differently depending!  */
   if (XSTRING (abspath)->size > 1
       && XSTRING (abspath)->data[XSTRING (abspath)->size - 1] == '/')
-    {
-      if (EQ (abspath, filename))
-       abspath = Fcopy_sequence (abspath);
-      XSTRING (abspath)->data[XSTRING (abspath)->size - 1] = 0;
-    }
+    /* We cannot take shortcuts; they might be wrong for magic file names.  */
+    abspath = Fdirectory_file_name (abspath);
 #endif
   return abspath;
 }
@@ -1562,6 +1593,8 @@ A prefix arg makes KEEP-TIME non-nil.")
   Lisp_Object handler;
   struct gcpro gcpro1, gcpro2;
   int count = specpdl_ptr - specpdl;
+  Lisp_Object args[6];
+  int input_file_statable_p;
 
   GCPRO2 (filename, newname);
   CHECK_STRING (filename, 0);
@@ -1572,12 +1605,12 @@ A prefix arg makes KEEP-TIME non-nil.")
   /* If the input file name has special constructs in it,
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (filename);
-  if (!NILP (handler))
-    return call3 (handler, Qcopy_file, filename, newname);
   /* Likewise for output file name.  */
-  handler = Ffind_file_name_handler (newname);
+  if (NILP (handler))
+    handler = Ffind_file_name_handler (newname);
   if (!NILP (handler))
-    return call3 (handler, Qcopy_file, filename, newname);
+    return call5 (handler, Qcopy_file, filename, newname,
+                 ok_if_already_exists, keep_date);
 
   if (NILP (ok_if_already_exists)
       || XTYPE (ok_if_already_exists) == Lisp_Int)
@@ -1590,6 +1623,24 @@ A prefix arg makes KEEP-TIME non-nil.")
 
   record_unwind_protect (close_file_unwind, make_number (ifd));
 
+  /* We can only copy regular files and symbolic links.  Other files are not
+     copyable by us. */
+  input_file_statable_p = (fstat (ifd, &st) >= 0);
+
+#if defined (S_ISREG) && defined (S_ISLNK)
+  if (input_file_statable_p)
+    {
+      if (!(S_ISREG (st.st_mode)) && !(S_ISLNK (st.st_mode)))
+       {
+#if defined (EISDIR)
+         /* Get a better looking error message. */
+         errno = EISDIR;
+#endif /* EISDIR */
+       report_file_error ("Non-regular file", Fcons (filename, Qnil));
+       }
+    }
+#endif /* S_ISREG && S_ISLNK */
+
 #ifdef VMS
   /* Create the copy file with the same record format as the input file */
   ofd = sys_creat (XSTRING (newname)->data, 0666, ifd);
@@ -1608,7 +1659,7 @@ A prefix arg makes KEEP-TIME non-nil.")
        report_file_error ("I/O error", Fcons (newname, Qnil));
   immediate_quit = 0;
 
-  if (fstat (ifd, &st) >= 0)
+  if (input_file_statable_p)
     {
       if (!NILP (keep_date))
        {
@@ -1725,8 +1776,11 @@ This is what happens in interactive use with M-x.")
   /* If the file name has special constructs in it,
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (filename);
+  if (NILP (handler))
+    handler = Ffind_file_name_handler (newname);
   if (!NILP (handler))
-    return call3 (handler, Qrename_file, filename, newname);
+    return call4 (handler, Qrename_file,
+                 filename, newname, ok_if_already_exists);
 
   if (NILP (ok_if_already_exists)
       || XTYPE (ok_if_already_exists) == Lisp_Int)
@@ -1741,7 +1795,10 @@ This is what happens in interactive use with M-x.")
     {
       if (errno == EXDEV)
        {
-         Fcopy_file (filename, newname, ok_if_already_exists, Qt);
+         Fcopy_file (filename, newname,
+                     /* We have already prompted if it was an integer,
+                        so don't have copy-file prompt again.  */
+                     NILP (ok_if_already_exists) ? Qnil : Qt, Qt);
          Fdelete_file (filename);
        }
       else
@@ -1785,7 +1842,8 @@ This is what happens in interactive use with M-x.")
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (filename);
   if (!NILP (handler))
-    return call3 (handler, Qadd_name_to_file, filename, newname);
+    return call4 (handler, Qadd_name_to_file, filename, newname,
+                 ok_if_already_exists);
 
   if (NILP (ok_if_already_exists)
       || XTYPE (ok_if_already_exists) == Lisp_Int)
@@ -1836,7 +1894,8 @@ This happens for interactive use with M-x.")
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (filename);
   if (!NILP (handler))
-    return call3 (handler, Qmake_symbolic_link, filename, linkname);
+    return call4 (handler, Qmake_symbolic_link, filename, linkname,
+                 ok_if_already_exists);
 
   if (NILP (ok_if_already_exists)
       || XTYPE (ok_if_already_exists) == Lisp_Int)
@@ -1847,7 +1906,7 @@ This happens for interactive use with M-x.")
       /* If we didn't complain already, silently delete existing file.  */
       if (errno == EEXIST)
        {
-         unlink (XSTRING (filename)->data);
+         unlink (XSTRING (linkname)->data);
          if (0 <= symlink (XSTRING (filename)->data, XSTRING (linkname)->data))
            return Qnil;
        }
@@ -2032,22 +2091,46 @@ Otherwise returns NIL.")
       valsize = readlink (XSTRING (filename)->data, buf, bufsize);
       if (valsize < bufsize) break;
       /* Buffer was not long enough */
-      free (buf);
+      xfree (buf);
       bufsize *= 2;
     }
   if (valsize == -1)
     {
-      free (buf);
+      xfree (buf);
       return Qnil;
     }
   val = make_string (buf, valsize);
-  free (buf);
+  xfree (buf);
   return val;
 #else /* not S_IFLNK */
   return Qnil;
 #endif /* not S_IFLNK */
 }
 
+#ifdef SOLARIS_BROKEN_ACCESS
+/* In Solaris 2.1, the readonly-ness of the filesystem is not
+   considered by the access system call.  This is Sun's bug, but we
+   still have to make Emacs work.  */
+
+#include <sys/statvfs.h>
+
+static int
+ro_fsys (path)
+    char *path;
+{
+    struct statvfs statvfsb;
+
+    if (statvfs(path, &statvfsb))
+      return 1;  /* error from statvfs, be conservative and say not wrtable */
+    else
+      /* Otherwise, fsys is ro if bit is set.  */
+      return statvfsb.f_flag & ST_RDONLY;
+}
+#else
+/* But on every other os, access has already done the right thing.  */
+#define ro_fsys(path) 0
+#endif
+
 /* Having this before file-symlink-p mysteriously caused it to be forgotten
    on the RT/PC.  */
 DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0,
@@ -2068,13 +2151,16 @@ DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0,
     return call2 (handler, Qfile_writable_p, abspath);
 
   if (access (XSTRING (abspath)->data, 0) >= 0)
-    return (access (XSTRING (abspath)->data, 2) >= 0) ? Qt : Qnil;
+    return ((access (XSTRING (abspath)->data, 2) >= 0
+            && ! ro_fsys ((char *) XSTRING (abspath)->data))
+           ? Qt : Qnil);
   dir = Ffile_name_directory (abspath);
 #ifdef VMS
   if (!NILP (dir))
     dir = Fdirectory_file_name (dir);
 #endif /* VMS */
-  return (access (!NILP (dir) ? (char *) XSTRING (dir)->data : "", 2) >= 0
+  return ((access (!NILP (dir) ? (char *) XSTRING (dir)->data : "", 2) >= 0
+          && ! ro_fsys ((char *) XSTRING (dir)->data))
          ? Qt : Qnil);
 }
 
@@ -2267,6 +2353,8 @@ otherwise, if FILE2 does not exist, the answer is t.")
   /* If the file name has special constructs in it,
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (abspath1);
+  if (NILP (handler))
+    handler = Ffind_file_name_handler (abspath2);
   if (!NILP (handler))
     return call3 (handler, Qfile_newer_than_file_p, abspath1, abspath2);
 
@@ -2282,27 +2370,33 @@ otherwise, if FILE2 does not exist, the answer is t.")
 }
 \f
 DEFUN ("insert-file-contents", Finsert_file_contents, Sinsert_file_contents,
-  1, 2, 0,
+  1, 4, 0,
   "Insert contents of file FILENAME after point.\n\
-Returns list of absolute pathname and length of data inserted.\n\
+Returns list of absolute file name and length of data inserted.\n\
 If second argument VISIT is non-nil, the buffer's visited filename\n\
 and last save file modtime are set, and it is marked unmodified.\n\
 If visiting and the file does not exist, visiting is completed\n\
-before the error is signaled.")
-  (filename, visit)
-     Lisp_Object filename, visit;
+before the error is signaled.\n\n\
+The optional third and fourth arguments BEG and END\n\
+specify what portion of the file to insert.\n\
+If VISIT is non-nil, BEG and END must be nil.")
+  (filename, visit, beg, end)
+     Lisp_Object filename, visit, beg, end;
 {
   struct stat st;
   register int fd;
   register int inserted = 0;
   register int how_much;
   int count = specpdl_ptr - specpdl;
-  struct gcpro gcpro1;
-  Lisp_Object handler, val;
+  struct gcpro gcpro1, gcpro2;
+  Lisp_Object handler, val, insval;
+  Lisp_Object p;
+  int total;
 
   val = Qnil;
+  p = Qnil;
 
-  GCPRO1 (filename);
+  GCPRO2 (filename, p);
   if (!NILP (current_buffer->read_only))
     Fbarf_if_buffer_read_only();
 
@@ -2314,7 +2408,7 @@ before the error is signaled.")
   handler = Ffind_file_name_handler (filename);
   if (!NILP (handler))
     {
-      val = call3 (handler, Qinsert_file_contents, filename, visit);
+      val = call5 (handler, Qinsert_file_contents, filename, visit, beg, end);
       st.st_mtime = 0;
       goto handled;
     }
@@ -2353,25 +2447,51 @@ before the error is signaled.")
   if (st.st_size < 0)
     error ("File size is negative");
 
+  if (!NILP (beg) || !NILP (end))
+    if (!NILP (visit))
+      error ("Attempt to visit less than an entire file");
+
+  if (!NILP (beg))
+    CHECK_NUMBER (beg, 0);
+  else
+    XFASTINT (beg) = 0;
+
+  if (!NILP (end))
+    CHECK_NUMBER (end, 0);
+  else
+    {
+      XSETINT (end, st.st_size);
+      if (XINT (end) != st.st_size)
+       error ("maximum buffer size exceeded");
+    }
+
+  total = XINT (end) - XINT (beg);
+
   {
     register Lisp_Object temp;
 
     /* Make sure point-max won't overflow after this insertion.  */
-    XSET (temp, Lisp_Int, st.st_size + Z);
-    if (st.st_size + Z != XINT (temp))
+    XSET (temp, Lisp_Int, total);
+    if (total != XINT (temp))
       error ("maximum buffer size exceeded");
   }
 
-  if (NILP (visit))
+  if (NILP (visit) && total > 0)
     prepare_to_modify_buffer (point, point);
 
   move_gap (point);
-  if (GAP_SIZE < st.st_size)
-    make_gap (st.st_size - GAP_SIZE);
-    
+  if (GAP_SIZE < total)
+    make_gap (total - GAP_SIZE);
+
+  if (XINT (beg) != 0)
+    {
+      if (lseek (fd, XINT (beg), 0) < 0)
+       report_file_error ("Setting file position", Fcons (filename, Qnil));
+    }
+
   while (1)
     {
-      int try = min (st.st_size - inserted, 64 << 10);
+      int try = min (total - inserted, 64 << 10);
       int this;
 
       /* Allow quitting out of the actual I/O.  */
@@ -2438,14 +2558,33 @@ before the error is signaled.")
        report_file_error ("Opening input file", Fcons (filename, Qnil));
     }
 
-  signal_after_change (point, 0, inserted);
+  if (NILP (visit) && total > 0)
+    signal_after_change (point, 0, inserted);
   
+  if (inserted > 0)
+    {
+      p = Vafter_insert_file_functions;
+      while (!NILP (p))
+       {
+         insval = call1 (Fcar (p), make_number (inserted));
+         if (!NILP (insval))
+           {
+             CHECK_NUMBER (insval, 0);
+             inserted = XFASTINT (insval);
+           }
+         QUIT;
+         p = Fcdr (p);
+       }
+    }
+
   if (!NILP (val))
     RETURN_UNGCPRO (val);
   RETURN_UNGCPRO (Fcons (filename,
                         Fcons (make_number (inserted),
                                Qnil)));
 }
+\f
+static Lisp_Object build_annotations ();
 
 DEFUN ("write-region", Fwrite_region, Swrite_region, 3, 5,
   "r\nFWrite region to file: ",
@@ -2478,7 +2617,9 @@ to the file, instead of any buffer contents, and END is ignored.")
   unsigned char *fname = 0;    /* If non-0, original filename (must rename) */
 #endif /* VMS */
   Lisp_Object handler;
-  Lisp_Object visit_file = XTYPE (visit) == Lisp_String ? visit : filename;
+  Lisp_Object visit_file;
+  Lisp_Object annotations;
+  int visiting, quietly;
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
 
   /* Special kludge to simplify auto-saving */
@@ -2490,8 +2631,18 @@ to the file, instead of any buffer contents, and END is ignored.")
   else if (XTYPE (start) != Lisp_String)
     validate_region (&start, &end);
 
-  GCPRO4 (start, filename, visit, visit_file);
   filename = Fexpand_file_name (filename, Qnil);
+  if (XTYPE (visit) == Lisp_String)
+    visit_file = Fexpand_file_name (visit, Qnil);
+  else
+    visit_file = filename;
+
+  visiting = (EQ (visit, Qt) || XTYPE (visit) == Lisp_String);
+  quietly = !NILP (visit);
+
+  annotations = Qnil;
+
+  GCPRO4 (start, filename, annotations, visit_file);
 
   /* If the file name has special constructs in it,
      call the corresponding file handler.  */
@@ -2499,21 +2650,14 @@ to the file, instead of any buffer contents, and END is ignored.")
 
   if (!NILP (handler))
     {
-      Lisp_Object args[7];
       Lisp_Object val;
-      args[0] = handler;
-      args[1] = Qwrite_region;
-      args[2] = start;
-      args[3] = end;
-      args[4] = filename;
-      args[5] = append;
-      args[6] = visit;
-      val = Ffuncall (7, args);
+      val = call6 (handler, Qwrite_region, start, end,
+                  filename, append, visit);
 
       /* Do this before reporting IO error
         to avoid a "file has changed on disk" warning on
         next attempt to save.  */
-      if (EQ (visit, Qt) || XTYPE (visit) == Lisp_String)
+      if (visiting)
        {
          current_buffer->modtime = 0;
          current_buffer->save_modified = MODIFF;
@@ -2524,6 +2668,8 @@ to the file, instead of any buffer contents, and END is ignored.")
       return val;
     }
 
+  annotations = build_annotations (start, end);
+
 #ifdef CLASH_DETECTION
   if (!auto_saving)
     lock_file (visit_file);
@@ -2632,18 +2778,20 @@ to the file, instead of any buffer contents, and END is ignored.")
 
   if (XTYPE (start) == Lisp_String)
     {
-      failure = 0 > e_write (desc, XSTRING (start)->data,
-                            XSTRING (start)->size);
+      failure = 0 > a_write (desc, XSTRING (start)->data,
+                            XSTRING (start)->size, 0, &annotations);
       save_errno = errno;
     }
   else if (XINT (start) != XINT (end))
     {
+      int nwritten = 0;
       if (XINT (start) < GPT)
        {
          register int end1 = XINT (end);
          tem = XINT (start);
-         failure = 0 > e_write (desc, &FETCH_CHAR (tem),
-                                min (GPT, end1) - tem);
+         failure = 0 > a_write (desc, &FETCH_CHAR (tem),
+                                min (GPT, end1) - tem, tem, &annotations);
+         nwritten += min (GPT, end1) - tem;
          save_errno = errno;
        }
 
@@ -2651,22 +2799,29 @@ to the file, instead of any buffer contents, and END is ignored.")
        {
          tem = XINT (start);
          tem = max (tem, GPT);
-         failure = 0 > e_write (desc, &FETCH_CHAR (tem), XINT (end) - tem);
+         failure = 0 > a_write (desc, &FETCH_CHAR (tem), XINT (end) - tem,
+                                tem, &annotations);
+         nwritten += XINT (end) - tem;
+         save_errno = errno;
+       }
+
+      if (nwritten == 0)
+       {
+         /* If file was empty, still need to write the annotations */
+         failure = 0 > a_write (desc, "", 0, XINT (start), &annotations);
          save_errno = errno;
        }
     }
 
   immediate_quit = 0;
 
-#ifndef USG
-#ifndef VMS
-#ifndef BSD4_1
+#ifdef HAVE_FSYNC
   /* Note fsync appears to change the modtime on BSD4.2 (both vax and sun).
      Disk full in NFS may be reported here.  */
-  if (fsync (desc) < 0)
+  /* mib says that closing the file will try to write as fast as NFS can do
+     it, and that means the fsync here is not crucial for autosave files.  */
+  if (!auto_saving && fsync (desc) < 0)
     failure = 1, save_errno = errno;
-#endif
-#endif
 #endif
 
   /* Spurious "file has changed on disk" warnings have been 
@@ -2716,19 +2871,19 @@ to the file, instead of any buffer contents, and END is ignored.")
   /* Do this before reporting IO error
      to avoid a "file has changed on disk" warning on
      next attempt to save.  */
-  if (EQ (visit, Qt) || XTYPE (visit) == Lisp_String)
+  if (visiting)
     current_buffer->modtime = st.st_mtime;
 
   if (failure)
     error ("IO error writing %s: %s", fn, err_str (save_errno));
 
-  if (EQ (visit, Qt) || XTYPE (visit) == Lisp_String)
+  if (visiting)
     {
       current_buffer->save_modified = MODIFF;
       XFASTINT (current_buffer->save_length) = Z - BEG;
       current_buffer->filename = visit_file;
     }
-  else if (!NILP (visit))
+  else if (quietly)
     return Qnil;
 
   if (!auto_saving)
@@ -2737,6 +2892,87 @@ to the file, instead of any buffer contents, and END is ignored.")
   return Qnil;
 }
 
+Lisp_Object merge ();
+
+DEFUN ("car-less-than-car", Fcar_less_than_car, Scar_less_than_car, 2, 2, 0,
+  "Return t if (car A) is numerically less than (car B).")
+  (a, b)
+     Lisp_Object a, b;
+{
+  return Flss (Fcar (a), Fcar (b));
+}
+
+/* Build the complete list of annotations appropriate for writing out
+   the text between START and END, by calling all the functions in
+   write-region-annotate-functions and merging the lists they return.  */
+
+static Lisp_Object
+build_annotations (start, end)
+     Lisp_Object start, end;
+{
+  Lisp_Object annotations;
+  Lisp_Object p, res;
+  struct gcpro gcpro1, gcpro2;
+
+  annotations = Qnil;
+  p = Vwrite_region_annotate_functions;
+  GCPRO2 (annotations, p);
+  while (!NILP (p))
+    {
+      res = call2 (Fcar (p), start, end);
+      Flength (res);   /* Check basic validity of return value */
+      annotations = merge (annotations, res, Qcar_less_than_car);
+      p = Fcdr (p);
+    }
+  UNGCPRO;
+  return annotations;
+}
+
+/* Write to descriptor DESC the LEN characters starting at ADDR,
+   assuming they start at position POS in the buffer.
+   Intersperse with them the annotations from *ANNOT
+   (those which fall within the range of positions POS to POS + LEN),
+   each at its appropriate position.
+
+   Modify *ANNOT by discarding elements as we output them.
+   The return value is negative in case of system call failure.  */
+
+int
+a_write (desc, addr, len, pos, annot)
+     int desc;
+     register char *addr;
+     register int len;
+     int pos;
+     Lisp_Object *annot;
+{
+  Lisp_Object tem;
+  int nextpos;
+  int lastpos = pos + len;
+
+  while (1)
+    {
+      tem = Fcar_safe (Fcar (*annot));
+      if (INTEGERP (tem) && XINT (tem) >= pos && XFASTINT (tem) <= lastpos)
+       nextpos = XFASTINT (tem);
+      else
+       return e_write (desc, addr, lastpos - pos);
+      if (nextpos > pos)
+       {
+         if (0 > e_write (desc, addr, nextpos - pos))
+           return -1;
+         addr += nextpos - pos;
+         pos = nextpos;
+       }
+      tem = Fcdr (Fcar (*annot));
+      if (STRINGP (tem))
+       {
+         if (0 > e_write (desc, XSTRING (tem)->data, XSTRING (tem)->size))
+           return -1;
+       }
+      *annot = Fcdr (*annot);
+    }
+}
+
 int
 e_write (desc, addr, len)
      int desc;
@@ -2822,27 +3058,46 @@ Next attempt to save will certainly not complain of a discrepancy.")
   return Qnil;
 }
 
+DEFUN ("visited-file-modtime", Fvisited_file_modtime,
+  Svisited_file_modtime, 0, 0, 0,
+  "Return the current buffer's recorded visited file modification time.\n\
+The value is a list of the form (HIGH . LOW), like the time values\n\
+that `file-attributes' returns.")
+  ()
+{
+  return long_to_cons (current_buffer->modtime);
+}
+
 DEFUN ("set-visited-file-modtime", Fset_visited_file_modtime,
-  Sset_visited_file_modtime, 0, 0, 0,
+  Sset_visited_file_modtime, 0, 1, 0,
   "Update buffer's recorded modification time from the visited file's time.\n\
 Useful if the buffer was not read from the file normally\n\
-or if the file itself has been changed for some known benign reason.")
-  ()
+or if the file itself has been changed for some known benign reason.\n\
+An argument specifies the modification time value to use\n\
+\(instead of that of the visited file), in the form of a list\n\
+\(HIGH . LOW) or (HIGH LOW).")
+  (time_list)
+     Lisp_Object time_list;
 {
-  register Lisp_Object filename;
-  struct stat st;
-  Lisp_Object handler;
-
-  filename = Fexpand_file_name (current_buffer->filename, Qnil);
-
-  /* If the file name has special constructs in it,
-     call the corresponding file handler.  */
-  handler = Ffind_file_name_handler (filename);
-  if (!NILP (handler))
-    current_buffer->modtime = 0;
-  
-  else if (stat (XSTRING (filename)->data, &st) >= 0)
-    current_buffer->modtime = st.st_mtime;
+  if (!NILP (time_list))
+    current_buffer->modtime = cons_to_long (time_list);
+  else
+    {
+      register Lisp_Object filename;
+      struct stat st;
+      Lisp_Object handler;
+
+      filename = Fexpand_file_name (current_buffer->filename, Qnil);
+
+      /* If the file name has special constructs in it,
+        call the corresponding file handler.  */
+      handler = Ffind_file_name_handler (filename);
+      if (!NILP (handler))
+       /* The handler can find the file name the same way we did.  */
+       return call2 (handler, Qset_visited_file_modtime, Qnil);
+      else if (stat (XSTRING (filename)->data, &st) >= 0)
+       current_buffer->modtime = st.st_mtime;
+    }
 
   return Qnil;
 }
@@ -2899,6 +3154,12 @@ Non-nil second argument means save only current buffer.")
   char *omessage = echo_area_glyphs;
   extern int minibuf_level;
   int do_handled_files;
+  Lisp_Object oquit;
+
+  /* Ordinarily don't quit within this function,
+     but don't make it impossible to quit (in case we get hung in I/O).  */
+  oquit = Vquit_flag;
+  Vquit_flag = Qnil;
 
   /* No GCPRO needed, because (when it matters) all Lisp_Object variables
      point to non-strings reached from Vbuffer_alist.  */
@@ -2973,6 +3234,8 @@ Non-nil second argument means save only current buffer.")
   if (auto_saved && NILP (no_message))
     message1 (omessage ? omessage : "Auto-saving...done");
 
+  Vquit_flag = oquit;
+
   auto_saving = 0;
   return Qnil;
 }
@@ -3167,6 +3430,8 @@ DIR defaults to current buffer's directory default.")
   tem = Fstring_equal (val, insdef);
   if (!NILP (tem) && !NILP (defalt))
     return defalt;
+  if (XSTRING (val)->size == 0 && NILP (insdef))
+    return defalt;
   return Fsubstitute_in_file_name (val);
 }
 
@@ -3265,6 +3530,7 @@ syms_of_fileio ()
   Qinsert_file_contents = intern ("insert-file-contents");
   Qwrite_region = intern ("write-region");
   Qverify_visited_file_modtime = intern ("verify-visited-file-modtime");
+  Qset_visited_file_modtime = intern ("set-visited-file-modtime");
 
   staticpro (&Qexpand_file_name);
   staticpro (&Qdirectory_file_name);
@@ -3302,6 +3568,9 @@ syms_of_fileio ()
   Qfile_already_exists = intern("file-already-exists");
   staticpro (&Qfile_already_exists);
 
+  Qcar_less_than_car = intern ("car-less-than-car");
+  staticpro (&Qcar_less_than_car);
+
   Fput (Qfile_error, Qerror_conditions,
        Fcons (Qfile_error, Fcons (Qerror, Qnil)));
   Fput (Qfile_error, Qerror_message,
@@ -3337,6 +3606,24 @@ The function `find-file-name-handler' checks this list for a handler\n\
 for its argument.");
   Vfile_name_handler_alist = Qnil;
 
+  DEFVAR_LISP ("after-insert-file-functions", &Vafter_insert_file_functions,
+    "A list of functions to be called at the end of `insert-file-contents'.\n\
+Each is passed one argument, the number of bytes inserted.  It should return\n\
+the new byte count, and leave point the same.  If `insert-file-contents' is\n\
+intercepted by a handler from `file-name-handler-alist', that handler is\n\
+responsible for calling the after-insert-file-functions if appropriate.");
+  Vafter_insert_file_functions = Qnil;
+
+  DEFVAR_LISP ("write-region-annotate-functions", &Vwrite_region_annotate_functions,
+    "A list of functions to be called at the start of `write-region'.\n\
+Each is passed two arguments, START and END as for `write-region'.  It should\n\
+return a list of pairs (POSITION . STRING) of strings to be effectively\n\
+inserted at the specified positions of the file being written (1 means to\n\
+insert before the first byte written).  The POSITIONs must be sorted into\n\
+increasing order.  If there are several functions in the list, the several\n\
+lists are merged destructively.");
+  Vwrite_region_annotate_functions = Qnil;
+
   defsubr (&Sfind_file_name_handler);
   defsubr (&Sfile_name_directory);
   defsubr (&Sfile_name_nondirectory);
@@ -3376,8 +3663,10 @@ for its argument.");
   defsubr (&Sfile_newer_than_file_p);
   defsubr (&Sinsert_file_contents);
   defsubr (&Swrite_region);
+  defsubr (&Scar_less_than_car);
   defsubr (&Sverify_visited_file_modtime);
   defsubr (&Sclear_visited_file_modtime);
+  defsubr (&Svisited_file_modtime);
   defsubr (&Sset_visited_file_modtime);
   defsubr (&Sdo_auto_save);
   defsubr (&Sset_buffer_auto_saved);