use dynwind_begin and dynwind_end
[bpt/emacs.git] / src / decompress.c
index a6323a8..9dfec88 100644 (file)
@@ -1,5 +1,5 @@
 /* Interface to zlib.
-   Copyright (C) 2013 Free Software Foundation, Inc.
+   Copyright (C) 2013-2014 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -26,49 +26,137 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "character.h"
 #include "buffer.h"
 
-\f
-#define BUFFER_SIZE 16384
+#include <verify.h>
+
+static Lisp_Object Qzlib_dll;
+
+#ifdef WINDOWSNT
+#include <windows.h>
+#include "w32.h"
+
+/* Macro for defining functions that will be loaded from the zlib DLL.  */
+#define DEF_ZLIB_FN(rettype,func,args) static rettype (FAR CDECL *fn_##func)args
+
+/* Macro for loading zlib functions from the library.  */
+#define LOAD_ZLIB_FN(lib,func) {                                       \
+    fn_##func = (void *) GetProcAddress (lib, #func);                  \
+    if (!fn_##func) return false;                                      \
+  }
+
+DEF_ZLIB_FN (int, inflateInit2_,
+            (z_streamp strm, int  windowBits, const char *version, int stream_size));
+
+DEF_ZLIB_FN (int, inflate,
+            (z_streamp strm, int flush));
+
+DEF_ZLIB_FN (int, inflateEnd,
+            (z_streamp strm));
+
+static bool zlib_initialized;
+
+static bool
+init_zlib_functions (void)
+{
+  HMODULE library = w32_delayed_load (Qzlib_dll);
+
+  if (!library)
+    {
+      message1 ("zlib library not found");
+      return false;
+    }
+
+  LOAD_ZLIB_FN (library, inflateInit2_);
+  LOAD_ZLIB_FN (library, inflate);
+  LOAD_ZLIB_FN (library, inflateEnd);
+  return true;
+}
+
+#define fn_inflateInit2(strm, windowBits) \
+        fn_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
 
-struct decompress_unwind_data {
-  ptrdiff_t old_point, start;
+#else /* !WINDOWSNT */
+
+#define fn_inflateInit2                inflateInit2
+#define fn_inflate             inflate
+#define fn_inflateEnd          inflateEnd
+
+#endif /* WINDOWSNT */
+
+\f
+struct decompress_unwind_data
+{
+  ptrdiff_t old_point, start, nbytes;
   z_stream *stream;
 };
 
 static void
-unwind_decompress (void *ddata) {
+unwind_decompress (void *ddata)
+{
   struct decompress_unwind_data *data = ddata;
-  inflateEnd (data->stream);
-  /* Delete any uncompressed data already inserted and restore
-     point. */
-  if (data->start) {
-    del_range (data->start, PT);
-    SET_PT (data->old_point);
-  }
+  fn_inflateEnd (data->stream);
+
+  /* Delete any uncompressed data already inserted on error.  */
+  if (data->start)
+    del_range (data->start, data->start + data->nbytes);
+
+  /* Put point where it was, or if the buffer has shrunk because the
+     compressed data is bigger than the uncompressed, at
+     point-max.  */
+  SET_PT (min (data->old_point, ZV));
 }
 
-DEFUN ("decompress-gzipped-region", Fdecompress_gzipped_region,
-       Sdecompress_gzipped_region,
+DEFUN ("zlib-available-p", Fzlib_available_p, Szlib_available_p, 0, 0, 0,
+       doc: /* Return t if zlib decompression is available in this instance of Emacs.  */)
+     (void)
+{
+#ifdef WINDOWSNT
+  Lisp_Object found = Fassq (Qzlib_dll, Vlibrary_cache);
+  if (CONSP (found))
+    return XCDR (found);
+  else
+    {
+      Lisp_Object status;
+      zlib_initialized = init_zlib_functions ();
+      status = zlib_initialized ? Qt : Qnil;
+      Vlibrary_cache = Fcons (Fcons (Qzlib_dll, status), Vlibrary_cache);
+      return status;
+    }
+#else
+  return Qt;
+#endif
+}
+
+DEFUN ("zlib-decompress-region", Fzlib_decompress_region,
+       Szlib_decompress_region,
        2, 2, 0,
-       doc: /* Decompress a gzip-compressed region.
-The text in the region will be replaced by the decompressed data.
-On failure, nil is returned and the data is left in place.
-This function can only be called in unibyte buffers.*/)
+       doc: /* Decompress a gzip- or zlib-compressed region.
+Replace the text in the region by the decompressed data.
+On failure, return nil and leave the data in place.
+This function can be called only in unibyte buffers.  */)
   (Lisp_Object start, Lisp_Object end)
 {
-  ptrdiff_t istart, iend, point = PT;
+  ptrdiff_t istart, iend, pos_byte;
   z_stream stream;
-  int decompressed;
-  char out[16384];
+  int inflate_status;
   struct decompress_unwind_data unwind_data;
-  ptrdiff_t count = SPECPDL_INDEX ();
+  dynwind_begin ();
 
   validate_region (&start, &end);
 
   if (! NILP (BVAR (current_buffer, enable_multibyte_characters)))
-    error ("This function can only be called in unibyte buffers");
+    error ("This function can be called only in unibyte buffers");
+
+#ifdef WINDOWSNT
+  if (!zlib_initialized)
+    zlib_initialized = init_zlib_functions ();
+  if (!zlib_initialized) {
+    dynwind_end ();
+    return Qnil;
+  }
+#endif
 
   /* This is a unibyte buffer, so character positions and bytes are
-     the same. */
+     the same.  */
   istart = XINT (start);
   iend = XINT (end);
   move_gap_both (iend, iend);
@@ -79,48 +167,61 @@ This function can only be called in unibyte buffers.*/)
   stream.avail_in = 0;
   stream.next_in = Z_NULL;
 
-  /* This magic number apparently means "this is gzip". */
-  if (inflateInit2 (&stream, 16 + MAX_WBITS) != Z_OK)
+  /* The magic number 32 apparently means "autodetect both the gzip and
+     zlib formats" according to zlib.h.  */
+  if (fn_inflateInit2 (&stream, MAX_WBITS + 32) != Z_OK) {
+    dynwind_end ();
     return Qnil;
-
-  /* We're inserting the decompressed data at the end of the
-     compressed data. */
-  SET_PT (iend);
-
-  stream.avail_in = iend - istart;
-  stream.next_in = (char *) BYTE_POS_ADDR (istart);
+  }
 
   unwind_data.start = iend;
   unwind_data.stream = &stream;
-  unwind_data.old_point = point;
+  unwind_data.old_point = PT;
+  unwind_data.nbytes = 0;
   record_unwind_protect_ptr (unwind_decompress, &unwind_data);
 
-  immediate_quit = 1;
+  /* Insert the decompressed data at the end of the compressed data.  */
+  SET_PT (iend);
 
-  /* Run inflate() on input until the output buffer isn't full. */
-  do {
-    int result;
-    stream.avail_out = BUFFER_SIZE;
-    stream.next_out = out;
-    result = inflate (&stream, Z_NO_FLUSH);
-    if (result < 0) {
-      unbind_to (count, Qnil);
-      return Qnil;
+  pos_byte = istart;
+
+  /* Keep calling 'inflate' until it reports an error or end-of-input.  */
+  do
+    {
+      /* Maximum number of bytes that one 'inflate' call should read and write.
+        Do not make avail_out too large, as that might unduly delay C-g.
+        zlib requires that avail_in and avail_out not exceed UINT_MAX.  */
+      ptrdiff_t avail_in = min (iend - pos_byte, UINT_MAX);
+      int avail_out = 16 * 1024;
+      int decompressed;
+
+      if (GAP_SIZE < avail_out)
+       make_gap (avail_out - GAP_SIZE);
+      stream.next_in = BYTE_POS_ADDR (pos_byte);
+      stream.avail_in = avail_in;
+      stream.next_out = GPT_ADDR;
+      stream.avail_out = avail_out;
+      inflate_status = fn_inflate (&stream, Z_NO_FLUSH);
+      pos_byte += avail_in - stream.avail_in;
+      decompressed = avail_out - stream.avail_out;
+      insert_from_gap (decompressed, decompressed, 0);
+      unwind_data.nbytes += decompressed;
+      QUIT;
     }
+  while (inflate_status == Z_OK);
 
-    decompressed = BUFFER_SIZE - stream.avail_out;
-    insert_1_both (out, decompressed, decompressed, 0, 0, 0);
-    QUIT;
-  } while (stream.avail_out == 0);
-
-  immediate_quit = 0;
+  if (inflate_status != Z_STREAM_END){
+    
+      dynwind_end ();
+      return Qnil;
+    }
 
   unwind_data.start = 0;
-  unbind_to (count, Qnil);
 
-  /* Delete the compressed data. */
+  /* Delete the compressed data.  */
   del_range (istart, iend);
 
+  dynwind_end ();
   return Qt;
 }
 
@@ -131,7 +232,9 @@ This function can only be called in unibyte buffers.*/)
 void
 syms_of_decompress (void)
 {
-  defsubr (&Sdecompress_gzipped_region);
+#include "decompress.x"
+
+  DEFSYM (Qzlib_dll, "zlib");
 }
 
 #endif /* HAVE_ZLIB */