Re: branch-1_4 allocation overflow

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
Eric Blake <ebb9 <at> byu.net> writes:

> 
> $ 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)

For that matter, the arbitrary limit of EMFILE can be worked around on the 
branch:

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

	* src/output.c (cleanup_tmpfile): Avoid double error message when
	umask is prohibitive.
	(m4_tmpname, m4_tmpopen, m4_tmpclose, m4_tmpremove): New
	functions.
	(m4_tmpfile): Add parameter, move cloexec action here.
	(make_room_for): Adjust caller.  Don't keep too many files open.
	(insert_diversion_helper): Unlink emptied temp files.
	(make_diversion): Don't keep too many files open.
	* doc/m4.texinfo (Diversions): Tweak wording, now that open file
	descriptors are no longer a limiting factor.
	* NEWS: Document this change.

Index: NEWS
===================================================================
RCS file: /sources/m4/m4/NEWS,v
retrieving revision 1.1.1.1.2.81
diff -u -r1.1.1.1.2.81 NEWS
--- NEWS	13 Nov 2006 19:10:56 -0000	1.1.1.1.2.81
+++ NEWS	13 Nov 2006 23:19:20 -0000
@@ -8,7 +8,8 @@
   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.
+  numbers are less likely to exhaust system memory; and is no longer
+  limited by the maximum number of file descriptors.
 * 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.101
diff -u -r1.1.1.1.2.101 m4.texinfo
--- doc/m4.texinfo	13 Nov 2006 19:10:56 -0000	1.1.1.1.2.101
+++ doc/m4.texinfo	13 Nov 2006 23:19:20 -0000
@@ -3584,8 +3584,8 @@
 diversion still in memory, freeing this memory for other diversions.
 When creating the temporary file, @code{m4} honors the value of the
 environment variable @env{TMPDIR}, and falls back to @file{/tmp}.
-So, it is theoretically possible that the number of diversions be
-limited by the number of available file descriptors.
+So, it is theoretically possible that the number and aggregate size of
+diversions is limited only by available disk space.
 
 @ignore
 @comment We need to test spilled diversions, but don't need to expose
Index: src/output.c
===================================================================
RCS file: /sources/m4/m4/src/Attic/output.c,v
retrieving revision 1.1.1.1.2.16
diff -u -r1.1.1.1.2.16 output.c
--- src/output.c	13 Nov 2006 19:10:57 -0000	1.1.1.1.2.16
+++ src/output.c	13 Nov 2006 23:19:20 -0000
@@ -21,6 +21,7 @@
 
 #include "m4.h"
 
+#include <limits.h>
 #include <sys/stat.h>
 
 #include "gl_avltree_oset.h"
@@ -47,8 +48,9 @@
 /* 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.  */
+   diversion (zero size, u is NULL, non-zero used indicates that a
+   file has been created).  When not part of diversion_table, u.next
+   is a pointer to the free_list chain.  */
 
 typedef struct m4_diversion m4_diversion;
 
@@ -180,16 +182,32 @@
     _exit (exit_failure);
 }
 
-/* Create a temporary file open for reading and writing in a secure
-   temp directory.  The file will be automatically closed and deleted
-   on a fatal signal.  When done with the file, close it with
-   close_stream_temp.  Exits on failure, so the return value is always
-   an open file.  */
+/* Convert DIVNUM into a temporary file name for use in m4_tmp*.  */
+static const char *
+m4_tmpname (int divnum)
+{
+  static char *buffer;
+  static char *tail;
+  if (buffer == NULL)
+    {
+      tail = xasprintf ("%s/m4-%d", output_temp_dir->dir_name, INT_MAX);
+      buffer = obstack_copy0 (&diversion_storage, tail, strlen (tail));
+      tail = strrchr (buffer, '-') + 1;
+    }
+  sprintf (tail, "%d", divnum);
+  return buffer;
+}
+
+/* Create a temporary file for diversion DIVNUM open for reading and
+   writing in a secure temp directory.  The file will be automatically
+   closed and deleted on a fatal signal.  The file can be closed and
+   reopened with m4_tmpclose and m4_tmpopen; when finally done with
+   the file, close it with m4_tmpremove.  Exits on failure, so the
+   return value is always an open file.  */
 static FILE *
-m4_tmpfile (void)
+m4_tmpfile (int divnum)
 {
-  static unsigned int count;
-  char *name;
+  const char *name;
   FILE *file;
 
   if (output_temp_dir == NULL)
@@ -201,17 +219,56 @@
 		  "cannot create temporary file for diversion"));
       atexit (cleanup_tmpfile);
     }
-  name = xasprintf ("%s/m4-%d", output_temp_dir->dir_name, count++);
+  name = m4_tmpname (divnum);
   register_temp_file (output_temp_dir, name);
   errno = 0;
   file = fopen_temp (name, O_BINARY ? "wb+" : "w+");
   if (file == NULL)
+    {
+      unregister_temp_file (output_temp_dir, name);
+      M4ERROR ((EXIT_FAILURE, errno,
+		"cannot create temporary file for diversion"));
+    }
+  else if (set_cloexec_flag (fileno (file), true) != 0)
+    M4ERROR ((warning_status, errno,
+	      "Warning: cannot protect diversion across forks"));
+  return file;
+}
+
+/* Reopen a temporary file for diversion DIVNUM for reading and
+   writing in a secure temp directory.  Exits on failure, so the
+   return value is always an open file.  */
+static FILE *
+m4_tmpopen (int divnum)
+{
+  const char *name = m4_tmpname (divnum);
+  FILE *file;
+
+  errno = 0;
+  file = fopen_temp (name, O_BINARY ? "ab+" : "a+");
+  if (file == NULL)
     M4ERROR ((EXIT_FAILURE, errno,
 	      "cannot create temporary file for diversion"));
-  free (name);
+  else if (set_cloexec_flag (fileno (file), true) != 0)
+    M4ERROR ((warning_status, errno,
+	      "Warning: cannot protect diversion across forks"));
   return file;
 }
 
+/* Close, but don't delete, a temporary FILE.  */
+static int
+m4_tmpclose (FILE *file)
+{
+  return close_stream_temp (file);
+}
+
+/* Delete a closed temporary FILE for diversion DIVNUM.  */
+static int
+m4_tmpremove (int divnum)
+{
+  return cleanup_temp_file (output_temp_dir, m4_tmpname (divnum));
+}
+
 /*-----------------------------------------------------------------------.
 | Reorganize in-memory diversion buffers so the current diversion can	 |
 | accomodate LENGTH more characters without further reorganization.  The |
@@ -280,11 +337,7 @@
       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"));
+      selected_diversion->u.file = m4_tmpfile (selected_diversion->divnum);
 
       if (selected_diversion->used > 0)
 	{
@@ -298,7 +351,7 @@
       /* Reclaim the buffer space for other diversions.  */
 
       free (selected_buffer);
-      selected_diversion->used = 0;
+      selected_diversion->used = 1;
     }
 
   /* Reload output_file, just in case the flushed diversion was current.  */
@@ -313,8 +366,16 @@
     }
   else
     {
-      /* The buffer may be safely reallocated.  */
+      /* Close any selected file since it is not the current diversion.  */
+      if (selected_diversion)
+	{
+	  FILE *file = selected_diversion->u.file;
+	  selected_diversion->u.file = 0;
+	  if (m4_tmpclose (file) != 0)
+	    M4ERROR ((0, errno, "cannot close temporary file for diversion"));
+	}
 
+      /* The current buffer may be safely reallocated.  */
       output_diversion->u.buffer
 	= xrealloc (output_diversion->u.buffer, (size_t) wanted_size);
 
@@ -501,10 +562,18 @@
 	  if (!gl_oset_remove (diversion_table, output_diversion))
 	    error (EXIT_FAILURE, 0, "INTERNAL ERROR: make_diversion failed");
 	  output_diversion->u.next = free_list;
+	  output_diversion->used = 0;
 	  free_list = output_diversion;
 	}
-      else
+      else if (output_diversion->size)
 	output_diversion->used = output_diversion->size - output_unused;
+      else if (output_diversion->used)
+	{
+	  FILE *file = output_diversion->u.file;
+	  output_diversion->u.file = NULL;
+	  if (m4_tmpclose (file) != 0)
+	    M4ERROR ((0, errno, "cannot close temporary file for diversion"));
+	}
       output_diversion = NULL;
       output_file = NULL;
       output_cursor = NULL;
@@ -556,7 +625,11 @@
       output_unused = output_diversion->size - output_diversion->used;
     }
   else
-    output_file = output_diversion->u.file;
+    {
+      if (!output_diversion->u.file && output_diversion->used)
+	output_diversion->u.file = m4_tmpopen (output_diversion->divnum);
+      output_file = output_diversion->u.file;
+    }
   output_current_line = -1;
 }
 
@@ -605,10 +678,15 @@
     {
       if (diversion->size)
 	output_text (diversion->u.buffer, diversion->used);
-      else if (diversion->u.file)
+      else
 	{
-	  rewind (diversion->u.file);
-	  insert_file (diversion->u.file);
+	  if (!diversion->u.file && diversion->used)
+	    diversion->u.file = m4_tmpopen (diversion->divnum);
+	  if (diversion->u.file)
+	    {
+	      rewind (diversion->u.file);
+	      insert_file (diversion->u.file);
+	    }
 	}
 
       output_current_line = -1;
@@ -621,8 +699,19 @@
       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"));
+  else
+    {
+      if (diversion->u.file)
+	{
+	  FILE *file = diversion->u.file;
+	  diversion->u.file = NULL;
+	  diversion->used = 0;
+	  if (m4_tmpclose (file) != 0)
+	    M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
+	}
+      if (m4_tmpremove (diversion->divnum) != 0)
+	M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
+    }
   gl_oset_remove (diversion_table, diversion);
   diversion->u.next = free_list;
   free_list = diversion;
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.