]> code.delx.au - gnu-emacs/blobdiff - src/fileio.c
[!SYSTEM_MALLOC] (MEMMOVE_MISSING): Defined.
[gnu-emacs] / src / fileio.c
index e3a2cc9f2bb65bf686a911b911b03e95ab1f0691..09313252a3a105d2e9bd56a822c959c647fe252e 100644 (file)
@@ -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
@@ -100,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;
@@ -112,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;
@@ -1506,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)
@@ -1530,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;
 }
@@ -1585,6 +1594,7 @@ A prefix arg makes KEEP-TIME non-nil.")
   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);
@@ -1613,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);
@@ -1631,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))
        {
@@ -1767,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
@@ -2339,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();
 
@@ -2371,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;
     }
@@ -2410,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.  */
@@ -2495,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: ",
@@ -2536,6 +2618,8 @@ to the file, instead of any buffer contents, and END is ignored.")
 #endif /* VMS */
   Lisp_Object handler;
   Lisp_Object visit_file;
+  Lisp_Object annotations;
+  int visiting, quietly;
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
 
   /* Special kludge to simplify auto-saving */
@@ -2553,7 +2637,12 @@ to the file, instead of any buffer contents, and END is ignored.")
   else
     visit_file = filename;
 
-  GCPRO4 (start, filename, visit, visit_file);
+  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.  */
@@ -2568,7 +2657,7 @@ 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 = 0;
          current_buffer->save_modified = MODIFF;
@@ -2579,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);
@@ -2687,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;
        }
 
@@ -2706,7 +2799,16 @@ 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;
        }
     }
@@ -2769,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)
@@ -2790,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;
@@ -2911,7 +3094,7 @@ An argument specifies the modification time value to use\n\
       handler = Ffind_file_name_handler (filename);
       if (!NILP (handler))
        /* The handler can find the file name the same way we did.  */
-       return call3 (handler, Qset_visited_file_modtime, Qnil);
+       return call2 (handler, Qset_visited_file_modtime, Qnil);
       else if (stat (XSTRING (filename)->data, &st) >= 0)
        current_buffer->modtime = st.st_mtime;
     }
@@ -2971,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.  */
@@ -3045,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;
 }
@@ -3377,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,
@@ -3412,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);
@@ -3451,6 +3663,7 @@ 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);