Re: branch-1_4 allocation overflow

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
Gary V. Vaughan <gary <at> gnu.org> writes:

> >
> > Here's the two-part patch I am using for head.  First I optimized  
> > the data
> > representation, and got rid of a number of potential size_t-to-int  
> > bugs on 64-
> > bit platforms.  The second part pulls in a gnulib list structure,  
> > to convert
> > the sparse array into an efficient AVL tree.
> 
> Nice! I remember thinking about doing something similar in the pre- 
> gnulib
> days, but was exhausted after writing the hash ADT and forgot about it.

I liked this so much (in the interest of removing arbitrary limits) that I 
ported it to the branch.  In the process, I discovered that gl_oset is lighter-
weight than gl_list, fixed a bug in gnulib's oset, and found that I still had a 
bug in head when hitting the EMFILE limit on my platform (calling fclose on a 
bogus char* is never a good idea :).  Tripped on cygwin, which apparently 
cannot handle more than 1024 fd's (even though <limits.h> claims otherwise), by 
doing:

$ build/src/m4
include(examples/forloop.m4)dnl
forloop(i,1,2200,`divert(i)divnum
')
build/src/m4:stdin:2: cannot create temporary file for diversion: Too many open 
files
Aborted (core dumped)

So I'll have to re-port this to head.  But I'm not quite sure how to add it to 
the head testsuite, since not all platforms have a puny OPEN_MAX limit.  And on 
head, maybe it would be worth the extra complexity of closing and reopening 
temp files, to get around even that arbitrary limit.

2006-11-13  Eric Blake  <[email protected]>

	Backport sparse diversion handling from head.
	* m4/gnulib-cache.m4: Augment with 'gnulib-tool --import
	avltree-oset'.
	* src/output.c (struct m4_diversion): Rename from struct
	diversion, and update members.  All users changed.
	(diversion_table): Change to an ordered set, instead of an array.
	(div0): New storage for diversion 0.
	(diversions): No longer needed.
	(free_list): New list to allow recycling diversion storage.
	(diversion_storage): New storage to reduce malloc overhead.
	(cmp_diversion_CB, threshold_diversion_CB): New callbacks.
	(output_init, output_exit, cleanup_tmpfile, make_room_for)
	(make_diversion): Handle new diversion storage scheme.
	(insert_diversion_helper): New function.
	(insert_diversion, undivert_all, freeze_diversions): Use it.
	* doc/m4.texinfo (Divert, Diversions): Move hidden test of memory
	exhaustion to visible test of large diversion numbers.
	* NEWS: Document this fix.

Index: NEWS
===================================================================
RCS file: /sources/m4/m4/NEWS,v
retrieving revision 1.1.1.1.2.80
diff -u -r1.1.1.1.2.80 NEWS
--- NEWS	11 Nov 2006 12:54:46 -0000	1.1.1.1.2.80
+++ NEWS	13 Nov 2006 18:48:48 -0000
@@ -5,7 +5,10 @@
 Version 1.4.8 - ?? ??? 2006, by ??  (CVS version 1.4.7a)
 
 * The `divert' macro and `-H'/`--hashsize' command line option no longer
-  cause a core dump when handed extra large values.
+  cause a core dump when handed extra large values.  Also, `divert' now
+  uses memory proportional to the number of diversions in use, rather than
+  to the maximum diversion number encountered, so that large diversion
+  numbers are less likely to exhaust system memory.
 * The `--help' and `--version' command line options now consistently
   override all earlier options.  For example, `m4 --debugfile=trace
   --help' now no longer accidentally creates an empty file `trace'.
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.1.1.1.2.100
diff -u -r1.1.1.1.2.100 m4.texinfo
--- doc/m4.texinfo	11 Nov 2006 12:54:46 -0000	1.1.1.1.2.100
+++ doc/m4.texinfo	13 Nov 2006 18:48:48 -0000
@@ -3652,20 +3652,6 @@
 f
 m4exit
 @end example
-
-@comment We also need to test allocation overflow.  On 32-bit
-@comment platforms, this should fail outright.  But on 64-bit platforms
-@comment with enough memory, the allocation might succeed (hopefully
-@comment testers don't mind the memory thrashing), so fake the same
-@comment output for test success.
-
-@example
-divert(eval(`1<<28'))
-divert(`2')
-errprint(__program__`: memory exhausted
-')m4exit(`1')
-@error{}m4: memory exhausted
-@end example
 @end ignore
 
 @c FIXME: need some explanation here why this is a useful feature, not
@@ -3725,10 +3711,10 @@
 @result{}Wrapped TEXT preceeds diverted text.
 @end example
 
-If output is diverted to a non-existent diversion, it is simply
-discarded.  This can be used to suppress unwanted output.  A common
-example of unwanted output is the trailing newlines after macro
-definitions.  Here is how to avoid them.
+If output is diverted to a negative diversion, it is simply discarded.
+This can be used to suppress unwanted output.  A common example of
+unwanted output is the trailing newlines after macro definitions.  Here
+is a common programming idiom in @code{m4} for avoiding them.
 
 @example
 divert(`-1')
@@ -3738,7 +3724,19 @@
 @result{}
 @end example
 
-This is a common programming idiom in @code{m4}.
+@cindex @acronym{GNU} extensions
+Traditional implementations only supported ten diversions.  But as a
+@acronym{GNU} extension, diversion numbers can be as large as positive
+integers will allow, rather than treating a multi-digit diversion number
+as a request to discard text.
+
+@example
+divert(eval(`1<<28'))world
+divert(`2')hello
+^D
+@result{}hello
+@result{}world
+@end example
 
 Note that @code{divert} is an English word, but also an active macro
 without arguments.  When processing plain text, the word might appear in
Index: m4/gnulib-cache.m4
===================================================================
RCS file: /sources/m4/m4/m4/Attic/gnulib-cache.m4,v
retrieving revision 1.1.2.19
diff -u -r1.1.2.19 gnulib-cache.m4
--- m4/gnulib-cache.m4	1 Nov 2006 22:29:08 -0000	1.1.2.19
+++ m4/gnulib-cache.m4	13 Nov 2006 18:48:48 -0000
@@ -15,11 +15,11 @@
 
 
 # Specification in the form of a command-line invocation:
-#   gnulib-tool --import --dir=. --lib=libm4 --source-base=lib --m4-base=m4 --
doc-base=doc --aux-dir=. --no-libtool --macro-prefix=M4 binary-io clean-temp 
cloexec close-stream closeout config-h error fdl fopen-safer free gendocs 
getopt gnupload mkstemp obstack regex stdbool stdlib-safer strstr strtol 
unlocked-io verror xalloc xvasprintf
+#   gnulib-tool --import --dir=. --lib=libm4 --source-base=lib --m4-base=m4 --
doc-base=doc --aux-dir=. --no-libtool --macro-prefix=M4 avltree-oset binary-io 
clean-temp cloexec close-stream closeout config-h error fdl fopen-safer free 
gendocs getopt gnupload mkstemp obstack regex stdbool stdlib-safer strstr 
strtol unlocked-io verror xalloc xvasprintf
 
 # Specification in the form of a few gnulib-tool.m4 macro invocations:
 gl_LOCAL_DIR([])
-gl_MODULES([binary-io clean-temp cloexec close-stream closeout config-h error 
fdl fopen-safer free gendocs getopt gnupload mkstemp obstack regex stdbool 
stdlib-safer strstr strtol unlocked-io verror xalloc xvasprintf])
+gl_MODULES([avltree-oset binary-io clean-temp cloexec close-stream closeout 
config-h error fdl fopen-safer free gendocs getopt gnupload mkstemp obstack 
regex stdbool stdlib-safer strstr strtol unlocked-io verror xalloc xvasprintf])
 gl_AVOID([])
 gl_SOURCE_BASE([lib])
 gl_M4_BASE([m4])
Index: src/output.c
===================================================================
RCS file: /sources/m4/m4/src/Attic/output.c,v
retrieving revision 1.1.1.1.2.15
diff -u -r1.1.1.1.2.15 output.c
--- src/output.c	8 Nov 2006 05:08:26 -0000	1.1.1.1.2.15
+++ src/output.c	13 Nov 2006 18:48:48 -0000
@@ -23,6 +23,8 @@
 
 #include <sys/stat.h>
 
+#include "gl_avltree_oset.h"
+
 /* Size of initial in-memory buffer size for diversions.  Small diversions
    would usually fit in.  */
 #define INITIAL_BUFFER_SIZE 512
@@ -40,24 +42,40 @@
    This code is fairly entangled with the code in input.c, and maybe it
    belongs there?  */
 
-/* In a struct diversion, only one of file or buffer be may non-NULL,
-   depending on the fact output is diverted to a file or in memory
-   buffer.  Further, if buffer is NULL, then pointer is NULL, size and
-   unused are zero.  */
+typedef struct temp_dir m4_temp_dir;
+
+/* When part of diversion_table, each struct m4_diversion either
+   represents an open file (zero size, non-NULL u.file), an in-memory
+   buffer (non-zero size, non-NULL u.buffer), or an unused placeholder
+   diversion (zero size, u is NULL).  When not part of
+   diversion_table, u.next is a pointer to the free_list chain.  */
 
-struct diversion
+typedef struct m4_diversion m4_diversion;
+
+struct m4_diversion
   {
-    FILE *file;			/* diversion file on disk */
-    char *buffer;		/* in-memory diversion buffer */
+    union
+      {
+	FILE *file;		/* diversion file on disk */
+	char *buffer;		/* in-memory diversion buffer */
+	m4_diversion *next;	/* free-list pointer */
+      } u;
+    int divnum;			/* which diversion this represents */
     int size;			/* usable size before reallocation */
     int used;			/* used length in characters */
   };
 
-/* Table of diversions.  */
-static struct diversion *diversion_table;
+/* Table of diversions 1 through INT_MAX.  */
+static gl_oset_t diversion_table;
+
+/* Diversion 0 (not part of diversion_table).  */
+static m4_diversion div0;
 
-/* Number of entries in diversion table.  */
-static int diversions;
+/* Linked list of reclaimed diversion storage.  */
+static m4_diversion *free_list;
+
+/* Obstack from which diversion storage is allocated.  */
+static struct obstack diversion_storage;
 
 /* Total size of all in-memory buffer sizes.  */
 static int total_buffer_size;
@@ -67,7 +85,7 @@
 int current_diversion;
 
 /* Current output diversion, NULL if output is being currently discarded.  */
-static struct diversion *output_diversion;
+static m4_diversion *output_diversion;
 
 /* Values of some output_diversion fields, cached out for speed.  */
 static FILE *output_file;	/* current value of (file) */
@@ -77,40 +95,56 @@
 /* Number of input line we are generating output for.  */
 int output_current_line;
 
-typedef struct temp_dir m4_temp_dir;
-
 /* Temporary directory holding all spilled diversion files.  */
 static m4_temp_dir *output_temp_dir;
 
 
 
 /*------------------------.
-| Output initialisation.  |
+| Output initialization.  |
 `------------------------*/
 
+/* Callback for comparing list elements ELT1 and ELT2 for order in
+   diversion_table.  */
+static int
+cmp_diversion_CB (const void *elt1, const void *elt2)
+{
+  const m4_diversion *d1 = (const m4_diversion *) elt1;
+  const m4_diversion *d2 = (const m4_diversion *) elt2;
+  /* No need to worry about overflow, since we don't create diversions
+     with negative divnum.  */
+  return d1->divnum - d2->divnum;
+}
+
+/* Callback for comparing list element ELT against THRESHOLD.  */
+static bool
+threshold_diversion_CB (const void *elt, const void *threshold)
+{
+  const m4_diversion *div = (const m4_diversion *) elt;
+  /* No need to worry about overflow, since we don't create diversions
+     with negative divnum.  */
+  return div->divnum >= *(const int *) threshold;
+}
+
 void
 output_init (void)
 {
-  diversion_table = (struct diversion *) xmalloc (sizeof (struct diversion));
-  diversions = 1;
-  diversion_table[0].file = stdout;
-  diversion_table[0].buffer = NULL;
-  diversion_table[0].size = 0;
-  diversion_table[0].used = 0;
-
-  total_buffer_size = 0;
-  current_diversion = 0;
-  output_diversion = diversion_table;
+  diversion_table = gl_oset_create_empty (GL_AVLTREE_OSET, cmp_diversion_CB);
+  div0.u.file = stdout;
+  output_diversion = &div0;
   output_file = stdout;
-  output_cursor = NULL;
-  output_unused = 0;
+  obstack_init (&diversion_storage);
 }
 
 void
 output_exit (void)
 {
-  free (diversion_table);
+  /* Order is important, since we may have registered cleanup_tmpfile
+     as an atexit handler, and it must not traverse stale memory.  */
+  gl_oset_t table = diversion_table;
   diversion_table = NULL;
+  gl_oset_free (table);
+  obstack_free (&diversion_storage, NULL);
 }
 
 /* Clean up any temporary directory.  Designed for use as an atexit
@@ -120,20 +154,24 @@
 cleanup_tmpfile (void)
 {
   /* Close any open diversions.  */
-  int divnum;
-  struct diversion *diversion;
   bool fail = false;
 
   if (diversion_table)
-    for (divnum = 1; divnum < diversions; divnum++)
-      {
-	diversion = diversion_table + divnum;
-	if (diversion->file && close_stream_temp (diversion->file) != 0)
-	  {
-	    M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
-	    fail = true;
-	  }
-      }
+    {
+      const void *elt;
+      gl_oset_iterator_t iter = gl_oset_iterator (diversion_table);
+      while (gl_oset_iterator_next (&iter, &elt))
+	{
+	  m4_diversion *diversion = (m4_diversion *) elt;
+	  if (!diversion->size && diversion->u.file
+	      && close_stream_temp (diversion->u.file) != 0)
+	    {
+	      M4ERROR ((0, errno,
+			"cannot clean temporary file for diversion"));
+	      fail = true;
+	    }
+	}
+    }
 
   /* Clean up the temporary directory.  */
   if (cleanup_temp_dir (output_temp_dir) != 0)
@@ -187,6 +225,7 @@
 make_room_for (int length)
 {
   int wanted_size;
+  m4_diversion *selected_diversion = NULL;
 
   /* Compute needed size for in-memory buffer.  Diversions in-memory
      buffers start at 0 bytes, then 512, then keep doubling until it is
@@ -204,10 +243,12 @@
   if (total_buffer_size - output_diversion->size + wanted_size
       > MAXIMUM_TOTAL_SIZE)
     {
-      struct diversion *selected_diversion;
       int selected_used;
-      struct diversion *diversion;
+      char *selected_buffer;
+      m4_diversion *diversion;
       int count;
+      gl_oset_iterator_t iter;
+      const void *elt;
 
       /* Find out the buffer having most data, in view of flushing it to
 	 disk.  Fake the current buffer as having already received the
@@ -217,29 +258,38 @@
       selected_diversion = output_diversion;
       selected_used = output_diversion->used + length;
 
-      for (diversion = diversion_table + 1;
-	   diversion < diversion_table + diversions;
-	   diversion++)
-	if (diversion->used > selected_used)
-	  {
-	    selected_diversion = diversion;
-	    selected_used = diversion->used;
-	  }
+      iter = gl_oset_iterator (diversion_table);
+      while (gl_oset_iterator_next (&iter, &elt))
+	{
+	  diversion = (m4_diversion *) elt;
+	  if (diversion->used > selected_used)
+	    {
+	      selected_diversion = diversion;
+	      selected_used = diversion->used;
+	    }
+	}
+      gl_oset_iterator_free (&iter);
 
       /* Create a temporary file, write the in-memory buffer of the
-	 diversion to this file, then release the buffer.  */
+	 diversion to this file, then release the buffer.  Zero the
+	 diversion before doing anything that can exit () (including
+	 m4_tmpfile), so that the atexit handler doesn't try to close
+	 a garbage pointer as a file.  */
 
-      selected_diversion->file = m4_tmpfile ();
-      if (set_cloexec_flag (fileno (selected_diversion->file), true) != 0)
+      selected_buffer = selected_diversion->u.buffer;
+      total_buffer_size -= selected_diversion->size;
+      selected_diversion->size = 0;
+      selected_diversion->u.file = NULL;
+      selected_diversion->u.file = m4_tmpfile ();
+
+      if (set_cloexec_flag (fileno (selected_diversion->u.file), true) != 0)
 	M4ERROR ((warning_status, errno,
 		  "Warning: cannot protect diversion across forks"));
 
       if (selected_diversion->used > 0)
 	{
-	  count = fwrite (selected_diversion->buffer,
-			  (size_t) selected_diversion->used,
-			  1,
-			  selected_diversion->file);
+	  count = fwrite (selected_buffer, (size_t) selected_diversion->used,
+			  1, selected_diversion->u.file);
 	  if (count != 1)
 	    M4ERROR ((EXIT_FAILURE, errno,
 		      "ERROR: cannot flush diversion to temporary file"));
@@ -247,37 +297,31 @@
 
       /* Reclaim the buffer space for other diversions.  */
 
-      free (selected_diversion->buffer);
-      total_buffer_size -= selected_diversion->size;
-
-      selected_diversion->buffer = NULL;
-      selected_diversion->size = 0;
+      free (selected_buffer);
       selected_diversion->used = 0;
     }
 
   /* Reload output_file, just in case the flushed diversion was current.  */
 
-  output_file = output_diversion->file;
-  if (output_file)
+  if (output_diversion == selected_diversion)
     {
-
       /* The flushed diversion was current indeed.  */
 
+      output_file = output_diversion->u.file;
       output_cursor = NULL;
       output_unused = 0;
     }
   else
     {
-
       /* The buffer may be safely reallocated.  */
 
-      output_diversion->buffer
-	= xrealloc (output_diversion->buffer, (size_t) wanted_size);
+      output_diversion->u.buffer
+	= xrealloc (output_diversion->u.buffer, (size_t) wanted_size);
 
       total_buffer_size += wanted_size - output_diversion->size;
       output_diversion->size = wanted_size;
 
-      output_cursor = output_diversion->buffer + output_diversion->used;
+      output_cursor = output_diversion->u.buffer + output_diversion->used;
       output_unused = wanted_size - output_diversion->used;
     }
 }
@@ -445,12 +489,22 @@
 void
 make_diversion (int divnum)
 {
-  struct diversion *diversion;
+  m4_diversion *diversion = NULL;
+
+  if (current_diversion == divnum)
+    return;
 
   if (output_diversion)
     {
-      output_diversion->file = output_file;
-      output_diversion->used = output_diversion->size - output_unused;
+      if (!output_diversion->size && !output_diversion->u.file)
+	{
+	  if (!gl_oset_remove (diversion_table, output_diversion))
+	    error (EXIT_FAILURE, 0, "INTERNAL ERROR: make_diversion failed");
+	  output_diversion->u.next = free_list;
+	  free_list = output_diversion;
+	}
+      else
+	output_diversion->used = output_diversion->size - output_unused;
       output_diversion = NULL;
       output_file = NULL;
       output_cursor = NULL;
@@ -462,26 +516,47 @@
   if (divnum < 0)
     return;
 
-  if (divnum >= diversions)
+  if (divnum == 0)
+    diversion = &div0;
+  else
+    {
+      const void *elt;
+      if (gl_oset_search_atleast (diversion_table, threshold_diversion_CB,
+				  &divnum, &elt))
+	{
+	  m4_diversion *temp = (m4_diversion *) elt;
+	  if (temp->divnum == divnum)
+	    diversion = temp;
+	}
+    }
+  if (diversion == NULL)
     {
-      diversion_table = (struct diversion *)
-	xnrealloc (diversion_table, divnum + 1, sizeof (struct diversion));
-      for (diversion = diversion_table + diversions;
-	   diversion <= diversion_table + divnum;
-	   diversion++)
+      /* First time visiting this diversion.  */
+      if (free_list)
 	{
-	  diversion->file = NULL;
-	  diversion->buffer = NULL;
+	  diversion = free_list;
+	  free_list = diversion->u.next;
+	}
+      else
+	{
+	  diversion = (m4_diversion *) obstack_alloc (&diversion_storage,
+						      sizeof *diversion);
 	  diversion->size = 0;
 	  diversion->used = 0;
 	}
-      diversions = divnum + 1;
+      diversion->u.file = NULL;
+      diversion->divnum = divnum;
+      gl_oset_add (diversion_table, diversion);
     }
 
-  output_diversion = diversion_table + divnum;
-  output_file = output_diversion->file;
-  output_cursor = output_diversion->buffer + output_diversion->used;
-  output_unused = output_diversion->size - output_diversion->used;
+  output_diversion = diversion;
+  if (output_diversion->size)
+    {
+      output_cursor = output_diversion->u.buffer + output_diversion->used;
+      output_unused = output_diversion->size - output_diversion->used;
+    }
+  else
+    output_file = output_diversion->u.file;
   output_current_line = -1;
 }
 
@@ -515,59 +590,66 @@
     }
 }
 
-/*-------------------------------------------------------------------------.
-| Insert diversion number DIVNUM into the current output file.  The	   |
-| diversion is NOT placed on the expansion obstack, because it must not be |
-| rescanned.  When the file is closed, it is deleted by the system.	   |
-`-------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------.
+| Insert DIVERSION (but not div0) into the current output file.  The |
+| diversion is NOT placed on the expansion obstack, because it must  |
+| not be rescanned.  When the file is closed, it is deleted by the   |
+| system.							     |
+`-------------------------------------------------------------------*/
 
-void
-insert_diversion (int divnum)
+static void
+insert_diversion_helper (m4_diversion *diversion)
 {
-  struct diversion *diversion;
-
-  /* Do not care about unexisting diversions.  Also, diversion 0 is stdout,
-     which is effectively always empty.  */
-
-  if (divnum <= 0 || divnum >= diversions)
-    return;
-
-  /* Also avoid undiverting into self.  */
-
-  diversion = diversion_table + divnum;
-  if (diversion == output_diversion)
-    return;
-
   /* Effectively undivert only if an output stream is active.  */
-
   if (output_diversion)
     {
-      if (diversion->file)
+      if (diversion->size)
+	output_text (diversion->u.buffer, diversion->used);
+      else if (diversion->u.file)
 	{
-	  rewind (diversion->file);
-	  insert_file (diversion->file);
+	  rewind (diversion->u.file);
+	  insert_file (diversion->u.file);
 	}
-      else if (diversion->buffer)
-	output_text (diversion->buffer, diversion->used);
 
       output_current_line = -1;
     }
 
   /* Return all space used by the diversion.  */
-
-  if (diversion->file)
-    {
-      if (close_stream_temp (diversion->file) != 0)
-	M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
-      diversion->file = NULL;
-    }
-  else if (diversion->buffer)
+  if (diversion->size)
     {
-      free (diversion->buffer);
-      diversion->buffer = NULL;
+      free (diversion->u.buffer);
       diversion->size = 0;
       diversion->used = 0;
     }
+  else if (diversion->u.file && close_stream_temp (diversion->u.file) != 0)
+    M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
+  gl_oset_remove (diversion_table, diversion);
+  diversion->u.next = free_list;
+  free_list = diversion;
+}
+
+/*-------------------------------------------------------------------------.
+| Insert diversion number DIVNUM into the current output file.  The	   |
+| diversion is NOT placed on the expansion obstack, because it must not be |
+| rescanned.  When the file is closed, it is deleted by the system.	   |
+`-------------------------------------------------------------------------*/
+
+void
+insert_diversion (int divnum)
+{
+  const void *elt;
+
+  /* Do not care about nonexistent diversions, and undiverting stdout
+     or self is a no-op.  */
+  if (divnum <= 0 || current_diversion == divnum)
+    return;
+  if (gl_oset_search_atleast (diversion_table, threshold_diversion_CB,
+			      &divnum, &elt))
+    {
+      m4_diversion *diversion = (m4_diversion *) elt;
+      if (diversion->divnum == divnum)
+	insert_diversion_helper (diversion);
+    }
 }
 
 /*-------------------------------------------------------------------------.
@@ -578,10 +660,15 @@
 void
 undivert_all (void)
 {
-  int divnum;
-
-  for (divnum = 1; divnum < diversions; divnum++)
-    insert_diversion (divnum);
+  const void *elt;
+  gl_oset_iterator_t iter = gl_oset_iterator (diversion_table);
+  while (gl_oset_iterator_next (&iter, &elt))
+    {
+      m4_diversion *diversion = (m4_diversion *) elt;
+      if (diversion->divnum != current_diversion)
+	insert_diversion_helper (diversion);
+    }
+  gl_oset_iterator_free (&iter);
 }
 
 /*-------------------------------------------------------------.
@@ -593,38 +680,39 @@
 {
   int saved_number;
   int last_inserted;
-  int divnum;
-  struct diversion *diversion;
-  struct stat file_stat;
+  gl_oset_iterator_t iter;
+  const void *elt;
 
   saved_number = current_diversion;
   last_inserted = 0;
   make_diversion (0);
   output_file = file;		/* kludge in the frozen file */
 
-  for (divnum = 1; divnum < diversions; divnum++)
+  iter = gl_oset_iterator (diversion_table);
+  while (gl_oset_iterator_next (&iter, &elt))
     {
-      diversion = diversion_table + divnum;
-      if (diversion->file || diversion->buffer)
+      m4_diversion *diversion = (m4_diversion *) elt;;
+      if (diversion->size || diversion->u.file)
 	{
-	  if (diversion->file)
+	  if (diversion->size)
+	    fprintf (file, "D%d,%d\n", diversion->divnum, diversion->used);
+	  else
 	    {
-	      fflush (diversion->file);
-	      if (fstat (fileno (diversion->file), &file_stat) < 0)
+	      struct stat file_stat;
+	      fflush (diversion->u.file);
+	      if (fstat (fileno (diversion->u.file), &file_stat) < 0)
 		M4ERROR ((EXIT_FAILURE, errno, "cannot stat diversion"));
 	      if (file_stat.st_size < 0
-		  || file_stat.st_size != (unsigned long) file_stat.st_size)
+		  || file_stat.st_size != (unsigned long int) file_stat.st_size)
 		M4ERROR ((EXIT_FAILURE, 0, "diversion too large"));
-	      fprintf (file, "D%d,%lu", divnum,
+	      fprintf (file, "D%d,%lu", diversion->divnum,
 		       (unsigned long int) file_stat.st_size);
 	    }
-	  else
-	    fprintf (file, "D%d,%d\n", divnum, diversion->used);
 
-	  insert_diversion (divnum);
+	  insert_diversion_helper (diversion);
 	  putc ('\n', file);
 
-	  last_inserted = divnum;
+	  last_inserted = diversion->divnum;
 	}
     }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.