aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/bison/m4/src/output.c
diff options
context:
space:
mode:
authoraxc <axc@yandex-team.ru>2022-02-10 16:47:35 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:47:35 +0300
commit1f5217043ad70f25dc35e75b3bd261a1e23d045e (patch)
tree11bf68c1fa5272d3d3446cbd5a0ff96ed9d75788 /contrib/tools/bison/m4/src/output.c
parent69505a07cbb096113e85aa02e7d136cac4aa826c (diff)
downloadydb-1f5217043ad70f25dc35e75b3bd261a1e23d045e.tar.gz
Restoring authorship annotation for <axc@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/bison/m4/src/output.c')
-rw-r--r--contrib/tools/bison/m4/src/output.c2034
1 files changed, 1017 insertions, 1017 deletions
diff --git a/contrib/tools/bison/m4/src/output.c b/contrib/tools/bison/m4/src/output.c
index c378afebdb..213d3bbba6 100644
--- a/contrib/tools/bison/m4/src/output.c
+++ b/contrib/tools/bison/m4/src/output.c
@@ -1,1017 +1,1017 @@
-/* GNU m4 -- A simple macro processor
-
- Copyright (C) 1989-1994, 2004-2013 Free Software Foundation, Inc.
-
- This file is part of GNU M4.
-
- GNU M4 is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- GNU M4 is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "m4.h"
-
-#include <limits.h>
-#include <sys/stat.h>
-
-#include "gl_avltree_oset.h"
-#include "gl_xoset.h"
-
-/* Size of initial in-memory buffer size for diversions. Small diversions
- would usually fit in. */
-#define INITIAL_BUFFER_SIZE 512
-
-/* Maximum value for the total of all in-memory buffer sizes for
- diversions. */
-#define MAXIMUM_TOTAL_SIZE (512 * 1024)
-
-/* Size of buffer size to use while copying files. */
-#define COPY_BUFFER_SIZE (32 * 512)
-
-/* Output functions. Most of the complexity is for handling cpp like
- sync lines.
-
- This code is fairly entangled with the code in input.c, and maybe it
- belongs there? */
-
-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, 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;
-
-struct m4_diversion
- {
- union
- {
- FILE *file; /* Diversion file on disk. */
- char *buffer; /* Malloc'd diversion buffer. */
- m4_diversion *next; /* Free-list pointer */
- } u;
- int divnum; /* Which diversion this represents. */
- int size; /* Usable size before reallocation. */
- int used; /* Used buffer length, or tmp file exists. */
- };
-
-/* Table of diversions 1 through INT_MAX. */
-static gl_oset_t diversion_table;
-
-/* Diversion 0 (not part of diversion_table). */
-static m4_diversion div0;
-
-/* 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;
-
-/* The number of the currently active diversion. This variable is
- maintained for the `divnum' builtin function. */
-int current_diversion;
-
-/* Current output diversion, NULL if output is being currently
- discarded. output_diversion->u is guaranteed non-NULL except when
- the diversion has never been used; use size to determine if it is a
- malloc'd buffer or a FILE. output_diversion->used is 0 if u.file
- is stdout, and non-zero if this is a malloc'd buffer or a temporary
- diversion file. */
-static m4_diversion *output_diversion;
-
-/* Cache of output_diversion->u.file, only valid when
- output_diversion->size is 0. */
-static FILE *output_file;
-
-/* Cache of output_diversion->u.buffer + output_diversion->used, only
- valid when output_diversion->size is non-zero. */
-static char *output_cursor;
-
-/* Cache of output_diversion->size - output_diversion->used, only
- valid when output_diversion->size is non-zero. */
-static int output_unused;
-
-/* Number of input line we are generating output for. */
-int output_current_line;
-
-/* Temporary directory holding all spilled diversion files. */
-static m4_temp_dir *output_temp_dir;
-
-/* Cache of most recently used spilled diversion files. */
-static FILE *tmp_file1;
-static FILE *tmp_file2;
-
-/* Diversions that own tmp_file, or 0. */
-static int tmp_file1_owner;
-static int tmp_file2_owner;
-
-/* True if tmp_file2 is more recently used. */
-static bool tmp_file2_recent;
-
-
-/* Internal routines. */
-
-/* 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 *diversion = (const m4_diversion *) elt;
- /* No need to worry about overflow, since we don't create diversions
- with negative divnum. */
- return diversion->divnum >= *(const int *) threshold;
-}
-
-/* Clean up any temporary directory. Designed for use as an atexit
- handler, where it is not safe to call exit() recursively; so this
- calls _exit if a problem is encountered. */
-static void
-cleanup_tmpfile (void)
-{
- /* Close any open diversions. */
- bool fail = false;
-
- if (diversion_table)
- {
- 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;
- }
- }
- gl_oset_iterator_free (&iter);
- }
-
- /* Clean up the temporary directory. */
- if (cleanup_temp_dir (output_temp_dir) != 0)
- fail = true;
- if (fail)
- _exit (exit_failure);
-}
-
-/* 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 = (char *) obstack_copy0 (&diversion_storage, tail,
- strlen (tail));
- free (tail);
- tail = strrchr (buffer, '-') + 1;
- }
- assert (0 < divnum);
- 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, or moved with
- m4_tmprename; 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 (int divnum)
-{
- const char *name;
- FILE *file;
-
- if (output_temp_dir == NULL)
- {
- output_temp_dir = create_temp_dir ("m4-", NULL, true);
- if (output_temp_dir == NULL)
- M4ERROR ((EXIT_FAILURE, errno,
- "cannot create temporary file for diversion"));
- atexit (cleanup_tmpfile);
- }
- name = m4_tmpname (divnum);
- register_temp_file (output_temp_dir, name);
- 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. If REREAD, the file is
- positioned at offset 0, otherwise the file is positioned at the
- end. Exits on failure, so the return value is always an open
- file. */
-static FILE *
-m4_tmpopen (int divnum, bool reread)
-{
- const char *name;
- FILE *file;
-
- if (tmp_file1_owner == divnum)
- {
- if (reread && fseeko (tmp_file1, 0, SEEK_SET) != 0)
- m4_error (EXIT_FAILURE, errno,
- _("cannot seek within diversion"));
- tmp_file2_recent = false;
- return tmp_file1;
- }
- else if (tmp_file2_owner == divnum)
- {
- if (reread && fseeko (tmp_file2, 0, SEEK_SET) != 0)
- m4_error (EXIT_FAILURE, errno,
- _("cannot seek within diversion"));
- tmp_file2_recent = true;
- return tmp_file2;
- }
- name = m4_tmpname (divnum);
- /* We need update mode, to avoid truncation. */
- file = fopen_temp (name, O_BINARY ? "rb+" : "r+");
- if (file == NULL)
- M4ERROR ((EXIT_FAILURE, errno,
- "cannot create temporary file for diversion"));
- else if (set_cloexec_flag (fileno (file), true) != 0)
- m4_error (0, errno, _("cannot protect diversion across forks"));
- /* Update mode starts at the beginning of the stream, but sometimes
- we want the end. */
- else if (!reread && fseeko (file, 0, SEEK_END) != 0)
- m4_error (EXIT_FAILURE, errno,
- _("cannot seek within diversion"));
- return file;
-}
-
-/* Close, but don't delete, a temporary FILE for diversion DIVNUM. To
- reduce the I/O overhead of repeatedly opening and closing the same
- file, this implementation caches the most recent spilled diversion.
- On the other hand, keeping every spilled diversion open would run
- into EMFILE limits. */
-static int
-m4_tmpclose (FILE *file, int divnum)
-{
- int result = 0;
- if (divnum != tmp_file1_owner && divnum != tmp_file2_owner)
- {
- if (tmp_file2_recent)
- {
- if (tmp_file1_owner)
- result = close_stream_temp (tmp_file1);
- tmp_file1 = file;
- tmp_file1_owner = divnum;
- }
- else
- {
- if (tmp_file2_owner)
- result = close_stream_temp (tmp_file2);
- tmp_file2 = file;
- tmp_file2_owner = divnum;
- }
- }
- return result;
-}
-
-/* Delete a closed temporary FILE for diversion DIVNUM. */
-static int
-m4_tmpremove (int divnum)
-{
- if (divnum == tmp_file1_owner)
- {
- int result = close_stream_temp (tmp_file1);
- if (result)
- return result;
- tmp_file1_owner = 0;
- }
- else if (divnum == tmp_file2_owner)
- {
- int result = close_stream_temp (tmp_file2);
- if (result)
- return result;
- tmp_file2_owner = 0;
- }
- return cleanup_temp_file (output_temp_dir, m4_tmpname (divnum));
-}
-
-/* Transfer the temporary file for diversion OLDNUM to the previously
- unused diversion NEWNUM. Return an open stream visiting the new
- temporary file, positioned at the end, or exit on failure. */
-static FILE*
-m4_tmprename (int oldnum, int newnum)
-{
- /* m4_tmpname reuses its return buffer. */
- char *oldname = xstrdup (m4_tmpname (oldnum));
- const char *newname = m4_tmpname (newnum);
- register_temp_file (output_temp_dir, newname);
- if (oldnum == tmp_file1_owner)
- {
- /* Be careful of mingw, which can't rename an open file. */
- if (RENAME_OPEN_FILE_WORKS)
- tmp_file1_owner = newnum;
- else
- {
- if (close_stream_temp (tmp_file1))
- m4_error (EXIT_FAILURE, errno,
- _("cannot close temporary file for diversion"));
- tmp_file1_owner = 0;
- }
- }
- else if (oldnum == tmp_file2_owner)
- {
- /* Be careful of mingw, which can't rename an open file. */
- if (RENAME_OPEN_FILE_WORKS)
- tmp_file2_owner = newnum;
- else
- {
- if (close_stream_temp (tmp_file2))
- m4_error (EXIT_FAILURE, errno,
- _("cannot close temporary file for diversion"));
- tmp_file2_owner = 0;
- }
- }
- /* Either it is safe to rename an open file, or no one should have
- oldname open at this point. */
- if (rename (oldname, newname))
- m4_error (EXIT_FAILURE, errno,
- _("cannot create temporary file for diversion"));
- unregister_temp_file (output_temp_dir, oldname);
- free (oldname);
- return m4_tmpopen (newnum, false);
-}
-
-
-/*------------------------.
-| Output initialization. |
-`------------------------*/
-
-void
-output_init (void)
-{
- diversion_table = gl_oset_create_empty (GL_AVLTREE_OSET, cmp_diversion_CB,
- NULL);
- div0.u.file = stdout;
- output_diversion = &div0;
- output_file = stdout;
- obstack_init (&diversion_storage);
-}
-
-void
-output_exit (void)
-{
- /* 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;
- if (tmp_file1_owner)
- m4_tmpremove (tmp_file1_owner);
- if (tmp_file2_owner)
- m4_tmpremove (tmp_file2_owner);
- diversion_table = NULL;
- gl_oset_free (table);
- obstack_free (&diversion_storage, NULL);
-}
-
-/*----------------------------------------------------------------.
-| Reorganize in-memory diversion buffers so the current diversion |
-| can accomodate LENGTH more characters without further |
-| reorganization. The current diversion buffer is made bigger if |
-| possible. But to make room for a bigger buffer, one of the |
-| in-memory diversion buffers might have to be flushed to a newly |
-| created temporary file. This flushed buffer might well be the |
-| current one. |
-`----------------------------------------------------------------*/
-
-static void
-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
- decided to flush them to disk. */
-
- output_diversion->used = output_diversion->size - output_unused;
-
- for (wanted_size = output_diversion->size;
- wanted_size < output_diversion->used + length;
- wanted_size = wanted_size == 0 ? INITIAL_BUFFER_SIZE : wanted_size * 2)
- ;
-
- /* Check if we are exceeding the maximum amount of buffer memory. */
-
- if (total_buffer_size - output_diversion->size + wanted_size
- > MAXIMUM_TOTAL_SIZE)
- {
- int selected_used;
- 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
- projected data, while making the selection. So, if it is
- selected indeed, we will flush it smaller, before it grows. */
-
- selected_diversion = output_diversion;
- selected_used = output_diversion->used + length;
-
- 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. 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_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 (selected_diversion->divnum);
-
- if (selected_diversion->used > 0)
- {
- 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"));
- }
-
- /* Reclaim the buffer space for other diversions. */
-
- free (selected_buffer);
- selected_diversion->used = 1;
- }
-
- /* Reload output_file, just in case the flushed diversion was current. */
-
- if (output_diversion == selected_diversion)
- {
- /* The flushed diversion was current indeed. */
-
- output_file = output_diversion->u.file;
- output_cursor = NULL;
- output_unused = 0;
- }
- else
- {
- /* Close any selected file since it is not the current diversion. */
- if (selected_diversion)
- {
- FILE *file = selected_diversion->u.file;
- selected_diversion->u.file = NULL;
- if (m4_tmpclose (file, selected_diversion->divnum) != 0)
- m4_error (0, errno,
- _("cannot close temporary file for diversion"));
- }
-
- /* The current buffer may be safely reallocated. */
- {
- char *buffer = output_diversion->u.buffer;
- output_diversion->u.buffer = xcharalloc ((size_t) wanted_size);
- memcpy (output_diversion->u.buffer, buffer, output_diversion->used);
- free (buffer);
- }
-
- total_buffer_size += wanted_size - output_diversion->size;
- output_diversion->size = wanted_size;
-
- output_cursor = output_diversion->u.buffer + output_diversion->used;
- output_unused = wanted_size - output_diversion->used;
- }
-}
-
-/*--------------------------------------------------------------.
-| Output one character CHAR, when it is known that it goes to a |
-| diversion file or an in-memory diversion buffer. |
-`--------------------------------------------------------------*/
-
-#define OUTPUT_CHARACTER(Char) \
- if (output_file) \
- putc ((Char), output_file); \
- else if (output_unused == 0) \
- output_character_helper ((Char)); \
- else \
- (output_unused--, *output_cursor++ = (Char))
-
-static void
-output_character_helper (int character)
-{
- make_room_for (1);
-
- if (output_file)
- putc (character, output_file);
- else
- {
- *output_cursor++ = character;
- output_unused--;
- }
-}
-
-/*-------------------------------------------------------------------.
-| Output one TEXT having LENGTH characters, when it is known that it |
-| goes to a diversion file or an in-memory diversion buffer. |
-`-------------------------------------------------------------------*/
-
-void
-output_text (const char *text, int length)
-{
- int count;
-
- if (!output_diversion || !length)
- return;
-
- if (!output_file && length > output_unused)
- make_room_for (length);
-
- if (output_file)
- {
- count = fwrite (text, length, 1, output_file);
- if (count != 1)
- M4ERROR ((EXIT_FAILURE, errno, "ERROR: copying inserted file"));
- }
- else
- {
- memcpy (output_cursor, text, (size_t) length);
- output_cursor += length;
- output_unused -= length;
- }
-}
-
-/*--------------------------------------------------------------------.
-| Add some text into an obstack OBS, taken from TEXT, having LENGTH |
-| characters. If OBS is NULL, output the text to an external file |
-| or an in-memory diversion buffer instead. If OBS is NULL, and |
-| there is no output file, the text is discarded. LINE is the line |
-| where the token starts (not necessarily current_line, in the case |
-| of multiline tokens). |
-| |
-| If we are generating sync lines, the output has to be examined, |
-| because we need to know how much output each input line generates. |
-| In general, sync lines are output whenever a single input lines |
-| generates several output lines, or when several input lines do not |
-| generate any output. |
-`--------------------------------------------------------------------*/
-
-void
-shipout_text (struct obstack *obs, const char *text, int length, int line)
-{
- static bool start_of_output_line = true;
- const char *cursor;
-
- /* If output goes to an obstack, merely add TEXT to it. */
-
- if (obs != NULL)
- {
- obstack_grow (obs, text, length);
- return;
- }
-
- /* Do nothing if TEXT should be discarded. */
-
- if (output_diversion == NULL)
- return;
-
- /* Output TEXT to a file, or in-memory diversion buffer. */
-
- if (!sync_output)
- switch (length)
- {
-
- /* In-line short texts. */
-
- case 8: OUTPUT_CHARACTER (*text); text++;
- case 7: OUTPUT_CHARACTER (*text); text++;
- case 6: OUTPUT_CHARACTER (*text); text++;
- case 5: OUTPUT_CHARACTER (*text); text++;
- case 4: OUTPUT_CHARACTER (*text); text++;
- case 3: OUTPUT_CHARACTER (*text); text++;
- case 2: OUTPUT_CHARACTER (*text); text++;
- case 1: OUTPUT_CHARACTER (*text);
- case 0:
- return;
-
- /* Optimize longer texts. */
-
- default:
- output_text (text, length);
- }
- else
- {
- /* Check for syncline only at the start of a token. Multiline
- tokens, and tokens that are out of sync but in the middle of
- the line, must wait until the next raw newline triggers a
- syncline. */
- if (start_of_output_line)
- {
- start_of_output_line = false;
- output_current_line++;
-#ifdef DEBUG_OUTPUT
- xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n",
- line, current_line, output_current_line);
-#endif
-
- /* Output a `#line NUM' synchronization directive if needed.
- If output_current_line was previously given a negative
- value (invalidated), output `#line NUM "FILE"' instead. */
-
- if (output_current_line != line)
- {
- OUTPUT_CHARACTER ('#');
- OUTPUT_CHARACTER ('l');
- OUTPUT_CHARACTER ('i');
- OUTPUT_CHARACTER ('n');
- OUTPUT_CHARACTER ('e');
- OUTPUT_CHARACTER (' ');
- for (cursor = ntoa (line, 10); *cursor; cursor++)
- OUTPUT_CHARACTER (*cursor);
- if (output_current_line < 1 && current_file[0] != '\0')
- {
- OUTPUT_CHARACTER (' ');
- OUTPUT_CHARACTER ('"');
- for (cursor = current_file; *cursor; cursor++)
- OUTPUT_CHARACTER (*cursor);
- OUTPUT_CHARACTER ('"');
- }
- OUTPUT_CHARACTER ('\n');
- output_current_line = line;
- }
- }
-
- /* Output the token, and track embedded newlines. */
- for (; length-- > 0; text++)
- {
- if (start_of_output_line)
- {
- start_of_output_line = false;
- output_current_line++;
-#ifdef DEBUG_OUTPUT
- xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n",
- line, current_line, output_current_line);
-#endif
- }
- OUTPUT_CHARACTER (*text);
- if (*text == '\n')
- start_of_output_line = true;
- }
- }
-}
-
-/* Functions for use by diversions. */
-
-/*------------------------------------------------------------------.
-| Make a file for diversion DIVNUM, and install it in the diversion |
-| table. Grow the size of the diversion table as needed. |
-`------------------------------------------------------------------*/
-
-/* The number of possible diversions is limited only by memory and
- available file descriptors (each overflowing diversion uses one). */
-
-void
-make_diversion (int divnum)
-{
- m4_diversion *diversion = NULL;
-
- if (current_diversion == divnum)
- return;
-
- if (output_diversion)
- {
- if (!output_diversion->size && !output_diversion->u.file)
- {
- assert (!output_diversion->used);
- if (!gl_oset_remove (diversion_table, output_diversion))
- assert (false);
- output_diversion->u.next = free_list;
- free_list = output_diversion;
- }
- 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, output_diversion->divnum) != 0)
- m4_error (0, errno,
- _("cannot close temporary file for diversion"));
- }
- output_diversion = NULL;
- output_file = NULL;
- output_cursor = NULL;
- output_unused = 0;
- }
-
- current_diversion = divnum;
-
- if (divnum < 0)
- return;
-
- 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)
- {
- /* First time visiting this diversion. */
- if (free_list)
- {
- diversion = free_list;
- free_list = diversion->u.next;
- }
- else
- {
- diversion = (m4_diversion *) obstack_alloc (&diversion_storage,
- sizeof *diversion);
- diversion->size = 0;
- diversion->used = 0;
- }
- diversion->u.file = NULL;
- diversion->divnum = divnum;
- gl_oset_add (diversion_table, diversion);
- }
-
- 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
- {
- if (!output_diversion->u.file && output_diversion->used)
- output_diversion->u.file = m4_tmpopen (output_diversion->divnum,
- false);
- output_file = output_diversion->u.file;
- }
- output_current_line = -1;
-}
-
-/*-------------------------------------------------------------------.
-| Insert a FILE into the current output file, in the same manner |
-| diversions are handled. This allows files to be included, without |
-| having them rescanned by m4. |
-`-------------------------------------------------------------------*/
-
-void
-insert_file (FILE *file)
-{
- static char buffer[COPY_BUFFER_SIZE];
- size_t length;
-
- /* Optimize out inserting into a sink. */
- if (!output_diversion)
- return;
-
- /* Insert output by big chunks. */
- while (1)
- {
- length = fread (buffer, 1, sizeof buffer, file);
- if (ferror (file))
- M4ERROR ((EXIT_FAILURE, errno, "error reading inserted file"));
- if (length == 0)
- break;
- output_text (buffer, length);
- }
-}
-
-/*-------------------------------------------------------------------.
-| 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. |
-`-------------------------------------------------------------------*/
-
-static void
-insert_diversion_helper (m4_diversion *diversion)
-{
- /* Effectively undivert only if an output stream is active. */
- if (output_diversion)
- {
- if (diversion->size)
- {
- if (!output_diversion->u.file)
- {
- /* Transferring diversion metadata is faster than
- copying contents. */
- assert (!output_diversion->used && output_diversion != &div0
- && !output_file);
- output_diversion->u.buffer = diversion->u.buffer;
- output_diversion->size = diversion->size;
- output_cursor = diversion->u.buffer + diversion->used;
- output_unused = diversion->size - diversion->used;
- diversion->u.buffer = NULL;
- }
- else
- {
- /* Avoid double-charging the total in-memory size when
- transferring from one in-memory diversion to
- another. */
- total_buffer_size -= diversion->size;
- output_text (diversion->u.buffer, diversion->used);
- }
- }
- else if (!output_diversion->u.file)
- {
- /* Transferring diversion metadata is faster than copying
- contents. */
- assert (!output_diversion->used && output_diversion != &div0
- && !output_file);
- output_diversion->u.file = m4_tmprename (diversion->divnum,
- output_diversion->divnum);
- output_diversion->used = 1;
- output_file = output_diversion->u.file;
- diversion->u.file = NULL;
- diversion->size = 1;
- }
- else
- {
- if (!diversion->u.file)
- diversion->u.file = m4_tmpopen (diversion->divnum, true);
- insert_file (diversion->u.file);
- }
-
- output_current_line = -1;
- }
-
- /* Return all space used by the diversion. */
- if (diversion->size)
- {
- if (!output_diversion)
- total_buffer_size -= diversion->size;
- free (diversion->u.buffer);
- diversion->size = 0;
- }
- else
- {
- if (diversion->u.file)
- {
- FILE *file = diversion->u.file;
- diversion->u.file = NULL;
- if (m4_tmpclose (file, diversion->divnum) != 0)
- m4_error (0, errno,
- _("cannot clean temporary file for diversion"));
- }
- if (m4_tmpremove (diversion->divnum) != 0)
- M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
- }
- diversion->used = 0;
- 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);
- }
-}
-
-/*----------------------------------------------------------------.
-| Get back all diversions. This is done just before exiting from |
-| main, and from m4_undivert (), if called without arguments. |
-`----------------------------------------------------------------*/
-
-void
-undivert_all (void)
-{
- 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);
-}
-
-/*-------------------------------------------------------------.
-| Produce all diversion information in frozen format on FILE. |
-`-------------------------------------------------------------*/
-
-void
-freeze_diversions (FILE *file)
-{
- int saved_number;
- int last_inserted;
- 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 */
-
- iter = gl_oset_iterator (diversion_table);
- while (gl_oset_iterator_next (&iter, &elt))
- {
- m4_diversion *diversion = (m4_diversion *) elt;
- if (diversion->size || diversion->used)
- {
- if (diversion->size)
- xfprintf (file, "D%d,%d\n", diversion->divnum, diversion->used);
- else
- {
- struct stat file_stat;
- diversion->u.file = m4_tmpopen (diversion->divnum, true);
- 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 + 0UL
- != (unsigned long int) file_stat.st_size))
- M4ERROR ((EXIT_FAILURE, 0, "diversion too large"));
- xfprintf (file, "D%d,%lu\n", diversion->divnum,
- (unsigned long int) file_stat.st_size);
- }
-
- insert_diversion_helper (diversion);
- putc ('\n', file);
-
- last_inserted = diversion->divnum;
- }
- }
- gl_oset_iterator_free (&iter);
-
- /* Save the active diversion number, if not already. */
-
- if (saved_number != last_inserted)
- xfprintf (file, "D%d,0\n\n", saved_number);
-}
+/* GNU m4 -- A simple macro processor
+
+ Copyright (C) 1989-1994, 2004-2013 Free Software Foundation, Inc.
+
+ This file is part of GNU M4.
+
+ GNU M4 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ GNU M4 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "m4.h"
+
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "gl_avltree_oset.h"
+#include "gl_xoset.h"
+
+/* Size of initial in-memory buffer size for diversions. Small diversions
+ would usually fit in. */
+#define INITIAL_BUFFER_SIZE 512
+
+/* Maximum value for the total of all in-memory buffer sizes for
+ diversions. */
+#define MAXIMUM_TOTAL_SIZE (512 * 1024)
+
+/* Size of buffer size to use while copying files. */
+#define COPY_BUFFER_SIZE (32 * 512)
+
+/* Output functions. Most of the complexity is for handling cpp like
+ sync lines.
+
+ This code is fairly entangled with the code in input.c, and maybe it
+ belongs there? */
+
+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, 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;
+
+struct m4_diversion
+ {
+ union
+ {
+ FILE *file; /* Diversion file on disk. */
+ char *buffer; /* Malloc'd diversion buffer. */
+ m4_diversion *next; /* Free-list pointer */
+ } u;
+ int divnum; /* Which diversion this represents. */
+ int size; /* Usable size before reallocation. */
+ int used; /* Used buffer length, or tmp file exists. */
+ };
+
+/* Table of diversions 1 through INT_MAX. */
+static gl_oset_t diversion_table;
+
+/* Diversion 0 (not part of diversion_table). */
+static m4_diversion div0;
+
+/* 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;
+
+/* The number of the currently active diversion. This variable is
+ maintained for the `divnum' builtin function. */
+int current_diversion;
+
+/* Current output diversion, NULL if output is being currently
+ discarded. output_diversion->u is guaranteed non-NULL except when
+ the diversion has never been used; use size to determine if it is a
+ malloc'd buffer or a FILE. output_diversion->used is 0 if u.file
+ is stdout, and non-zero if this is a malloc'd buffer or a temporary
+ diversion file. */
+static m4_diversion *output_diversion;
+
+/* Cache of output_diversion->u.file, only valid when
+ output_diversion->size is 0. */
+static FILE *output_file;
+
+/* Cache of output_diversion->u.buffer + output_diversion->used, only
+ valid when output_diversion->size is non-zero. */
+static char *output_cursor;
+
+/* Cache of output_diversion->size - output_diversion->used, only
+ valid when output_diversion->size is non-zero. */
+static int output_unused;
+
+/* Number of input line we are generating output for. */
+int output_current_line;
+
+/* Temporary directory holding all spilled diversion files. */
+static m4_temp_dir *output_temp_dir;
+
+/* Cache of most recently used spilled diversion files. */
+static FILE *tmp_file1;
+static FILE *tmp_file2;
+
+/* Diversions that own tmp_file, or 0. */
+static int tmp_file1_owner;
+static int tmp_file2_owner;
+
+/* True if tmp_file2 is more recently used. */
+static bool tmp_file2_recent;
+
+
+/* Internal routines. */
+
+/* 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 *diversion = (const m4_diversion *) elt;
+ /* No need to worry about overflow, since we don't create diversions
+ with negative divnum. */
+ return diversion->divnum >= *(const int *) threshold;
+}
+
+/* Clean up any temporary directory. Designed for use as an atexit
+ handler, where it is not safe to call exit() recursively; so this
+ calls _exit if a problem is encountered. */
+static void
+cleanup_tmpfile (void)
+{
+ /* Close any open diversions. */
+ bool fail = false;
+
+ if (diversion_table)
+ {
+ 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;
+ }
+ }
+ gl_oset_iterator_free (&iter);
+ }
+
+ /* Clean up the temporary directory. */
+ if (cleanup_temp_dir (output_temp_dir) != 0)
+ fail = true;
+ if (fail)
+ _exit (exit_failure);
+}
+
+/* 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 = (char *) obstack_copy0 (&diversion_storage, tail,
+ strlen (tail));
+ free (tail);
+ tail = strrchr (buffer, '-') + 1;
+ }
+ assert (0 < divnum);
+ 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, or moved with
+ m4_tmprename; 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 (int divnum)
+{
+ const char *name;
+ FILE *file;
+
+ if (output_temp_dir == NULL)
+ {
+ output_temp_dir = create_temp_dir ("m4-", NULL, true);
+ if (output_temp_dir == NULL)
+ M4ERROR ((EXIT_FAILURE, errno,
+ "cannot create temporary file for diversion"));
+ atexit (cleanup_tmpfile);
+ }
+ name = m4_tmpname (divnum);
+ register_temp_file (output_temp_dir, name);
+ 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. If REREAD, the file is
+ positioned at offset 0, otherwise the file is positioned at the
+ end. Exits on failure, so the return value is always an open
+ file. */
+static FILE *
+m4_tmpopen (int divnum, bool reread)
+{
+ const char *name;
+ FILE *file;
+
+ if (tmp_file1_owner == divnum)
+ {
+ if (reread && fseeko (tmp_file1, 0, SEEK_SET) != 0)
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot seek within diversion"));
+ tmp_file2_recent = false;
+ return tmp_file1;
+ }
+ else if (tmp_file2_owner == divnum)
+ {
+ if (reread && fseeko (tmp_file2, 0, SEEK_SET) != 0)
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot seek within diversion"));
+ tmp_file2_recent = true;
+ return tmp_file2;
+ }
+ name = m4_tmpname (divnum);
+ /* We need update mode, to avoid truncation. */
+ file = fopen_temp (name, O_BINARY ? "rb+" : "r+");
+ if (file == NULL)
+ M4ERROR ((EXIT_FAILURE, errno,
+ "cannot create temporary file for diversion"));
+ else if (set_cloexec_flag (fileno (file), true) != 0)
+ m4_error (0, errno, _("cannot protect diversion across forks"));
+ /* Update mode starts at the beginning of the stream, but sometimes
+ we want the end. */
+ else if (!reread && fseeko (file, 0, SEEK_END) != 0)
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot seek within diversion"));
+ return file;
+}
+
+/* Close, but don't delete, a temporary FILE for diversion DIVNUM. To
+ reduce the I/O overhead of repeatedly opening and closing the same
+ file, this implementation caches the most recent spilled diversion.
+ On the other hand, keeping every spilled diversion open would run
+ into EMFILE limits. */
+static int
+m4_tmpclose (FILE *file, int divnum)
+{
+ int result = 0;
+ if (divnum != tmp_file1_owner && divnum != tmp_file2_owner)
+ {
+ if (tmp_file2_recent)
+ {
+ if (tmp_file1_owner)
+ result = close_stream_temp (tmp_file1);
+ tmp_file1 = file;
+ tmp_file1_owner = divnum;
+ }
+ else
+ {
+ if (tmp_file2_owner)
+ result = close_stream_temp (tmp_file2);
+ tmp_file2 = file;
+ tmp_file2_owner = divnum;
+ }
+ }
+ return result;
+}
+
+/* Delete a closed temporary FILE for diversion DIVNUM. */
+static int
+m4_tmpremove (int divnum)
+{
+ if (divnum == tmp_file1_owner)
+ {
+ int result = close_stream_temp (tmp_file1);
+ if (result)
+ return result;
+ tmp_file1_owner = 0;
+ }
+ else if (divnum == tmp_file2_owner)
+ {
+ int result = close_stream_temp (tmp_file2);
+ if (result)
+ return result;
+ tmp_file2_owner = 0;
+ }
+ return cleanup_temp_file (output_temp_dir, m4_tmpname (divnum));
+}
+
+/* Transfer the temporary file for diversion OLDNUM to the previously
+ unused diversion NEWNUM. Return an open stream visiting the new
+ temporary file, positioned at the end, or exit on failure. */
+static FILE*
+m4_tmprename (int oldnum, int newnum)
+{
+ /* m4_tmpname reuses its return buffer. */
+ char *oldname = xstrdup (m4_tmpname (oldnum));
+ const char *newname = m4_tmpname (newnum);
+ register_temp_file (output_temp_dir, newname);
+ if (oldnum == tmp_file1_owner)
+ {
+ /* Be careful of mingw, which can't rename an open file. */
+ if (RENAME_OPEN_FILE_WORKS)
+ tmp_file1_owner = newnum;
+ else
+ {
+ if (close_stream_temp (tmp_file1))
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot close temporary file for diversion"));
+ tmp_file1_owner = 0;
+ }
+ }
+ else if (oldnum == tmp_file2_owner)
+ {
+ /* Be careful of mingw, which can't rename an open file. */
+ if (RENAME_OPEN_FILE_WORKS)
+ tmp_file2_owner = newnum;
+ else
+ {
+ if (close_stream_temp (tmp_file2))
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot close temporary file for diversion"));
+ tmp_file2_owner = 0;
+ }
+ }
+ /* Either it is safe to rename an open file, or no one should have
+ oldname open at this point. */
+ if (rename (oldname, newname))
+ m4_error (EXIT_FAILURE, errno,
+ _("cannot create temporary file for diversion"));
+ unregister_temp_file (output_temp_dir, oldname);
+ free (oldname);
+ return m4_tmpopen (newnum, false);
+}
+
+
+/*------------------------.
+| Output initialization. |
+`------------------------*/
+
+void
+output_init (void)
+{
+ diversion_table = gl_oset_create_empty (GL_AVLTREE_OSET, cmp_diversion_CB,
+ NULL);
+ div0.u.file = stdout;
+ output_diversion = &div0;
+ output_file = stdout;
+ obstack_init (&diversion_storage);
+}
+
+void
+output_exit (void)
+{
+ /* 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;
+ if (tmp_file1_owner)
+ m4_tmpremove (tmp_file1_owner);
+ if (tmp_file2_owner)
+ m4_tmpremove (tmp_file2_owner);
+ diversion_table = NULL;
+ gl_oset_free (table);
+ obstack_free (&diversion_storage, NULL);
+}
+
+/*----------------------------------------------------------------.
+| Reorganize in-memory diversion buffers so the current diversion |
+| can accomodate LENGTH more characters without further |
+| reorganization. The current diversion buffer is made bigger if |
+| possible. But to make room for a bigger buffer, one of the |
+| in-memory diversion buffers might have to be flushed to a newly |
+| created temporary file. This flushed buffer might well be the |
+| current one. |
+`----------------------------------------------------------------*/
+
+static void
+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
+ decided to flush them to disk. */
+
+ output_diversion->used = output_diversion->size - output_unused;
+
+ for (wanted_size = output_diversion->size;
+ wanted_size < output_diversion->used + length;
+ wanted_size = wanted_size == 0 ? INITIAL_BUFFER_SIZE : wanted_size * 2)
+ ;
+
+ /* Check if we are exceeding the maximum amount of buffer memory. */
+
+ if (total_buffer_size - output_diversion->size + wanted_size
+ > MAXIMUM_TOTAL_SIZE)
+ {
+ int selected_used;
+ 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
+ projected data, while making the selection. So, if it is
+ selected indeed, we will flush it smaller, before it grows. */
+
+ selected_diversion = output_diversion;
+ selected_used = output_diversion->used + length;
+
+ 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. 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_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 (selected_diversion->divnum);
+
+ if (selected_diversion->used > 0)
+ {
+ 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"));
+ }
+
+ /* Reclaim the buffer space for other diversions. */
+
+ free (selected_buffer);
+ selected_diversion->used = 1;
+ }
+
+ /* Reload output_file, just in case the flushed diversion was current. */
+
+ if (output_diversion == selected_diversion)
+ {
+ /* The flushed diversion was current indeed. */
+
+ output_file = output_diversion->u.file;
+ output_cursor = NULL;
+ output_unused = 0;
+ }
+ else
+ {
+ /* Close any selected file since it is not the current diversion. */
+ if (selected_diversion)
+ {
+ FILE *file = selected_diversion->u.file;
+ selected_diversion->u.file = NULL;
+ if (m4_tmpclose (file, selected_diversion->divnum) != 0)
+ m4_error (0, errno,
+ _("cannot close temporary file for diversion"));
+ }
+
+ /* The current buffer may be safely reallocated. */
+ {
+ char *buffer = output_diversion->u.buffer;
+ output_diversion->u.buffer = xcharalloc ((size_t) wanted_size);
+ memcpy (output_diversion->u.buffer, buffer, output_diversion->used);
+ free (buffer);
+ }
+
+ total_buffer_size += wanted_size - output_diversion->size;
+ output_diversion->size = wanted_size;
+
+ output_cursor = output_diversion->u.buffer + output_diversion->used;
+ output_unused = wanted_size - output_diversion->used;
+ }
+}
+
+/*--------------------------------------------------------------.
+| Output one character CHAR, when it is known that it goes to a |
+| diversion file or an in-memory diversion buffer. |
+`--------------------------------------------------------------*/
+
+#define OUTPUT_CHARACTER(Char) \
+ if (output_file) \
+ putc ((Char), output_file); \
+ else if (output_unused == 0) \
+ output_character_helper ((Char)); \
+ else \
+ (output_unused--, *output_cursor++ = (Char))
+
+static void
+output_character_helper (int character)
+{
+ make_room_for (1);
+
+ if (output_file)
+ putc (character, output_file);
+ else
+ {
+ *output_cursor++ = character;
+ output_unused--;
+ }
+}
+
+/*-------------------------------------------------------------------.
+| Output one TEXT having LENGTH characters, when it is known that it |
+| goes to a diversion file or an in-memory diversion buffer. |
+`-------------------------------------------------------------------*/
+
+void
+output_text (const char *text, int length)
+{
+ int count;
+
+ if (!output_diversion || !length)
+ return;
+
+ if (!output_file && length > output_unused)
+ make_room_for (length);
+
+ if (output_file)
+ {
+ count = fwrite (text, length, 1, output_file);
+ if (count != 1)
+ M4ERROR ((EXIT_FAILURE, errno, "ERROR: copying inserted file"));
+ }
+ else
+ {
+ memcpy (output_cursor, text, (size_t) length);
+ output_cursor += length;
+ output_unused -= length;
+ }
+}
+
+/*--------------------------------------------------------------------.
+| Add some text into an obstack OBS, taken from TEXT, having LENGTH |
+| characters. If OBS is NULL, output the text to an external file |
+| or an in-memory diversion buffer instead. If OBS is NULL, and |
+| there is no output file, the text is discarded. LINE is the line |
+| where the token starts (not necessarily current_line, in the case |
+| of multiline tokens). |
+| |
+| If we are generating sync lines, the output has to be examined, |
+| because we need to know how much output each input line generates. |
+| In general, sync lines are output whenever a single input lines |
+| generates several output lines, or when several input lines do not |
+| generate any output. |
+`--------------------------------------------------------------------*/
+
+void
+shipout_text (struct obstack *obs, const char *text, int length, int line)
+{
+ static bool start_of_output_line = true;
+ const char *cursor;
+
+ /* If output goes to an obstack, merely add TEXT to it. */
+
+ if (obs != NULL)
+ {
+ obstack_grow (obs, text, length);
+ return;
+ }
+
+ /* Do nothing if TEXT should be discarded. */
+
+ if (output_diversion == NULL)
+ return;
+
+ /* Output TEXT to a file, or in-memory diversion buffer. */
+
+ if (!sync_output)
+ switch (length)
+ {
+
+ /* In-line short texts. */
+
+ case 8: OUTPUT_CHARACTER (*text); text++;
+ case 7: OUTPUT_CHARACTER (*text); text++;
+ case 6: OUTPUT_CHARACTER (*text); text++;
+ case 5: OUTPUT_CHARACTER (*text); text++;
+ case 4: OUTPUT_CHARACTER (*text); text++;
+ case 3: OUTPUT_CHARACTER (*text); text++;
+ case 2: OUTPUT_CHARACTER (*text); text++;
+ case 1: OUTPUT_CHARACTER (*text);
+ case 0:
+ return;
+
+ /* Optimize longer texts. */
+
+ default:
+ output_text (text, length);
+ }
+ else
+ {
+ /* Check for syncline only at the start of a token. Multiline
+ tokens, and tokens that are out of sync but in the middle of
+ the line, must wait until the next raw newline triggers a
+ syncline. */
+ if (start_of_output_line)
+ {
+ start_of_output_line = false;
+ output_current_line++;
+#ifdef DEBUG_OUTPUT
+ xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n",
+ line, current_line, output_current_line);
+#endif
+
+ /* Output a `#line NUM' synchronization directive if needed.
+ If output_current_line was previously given a negative
+ value (invalidated), output `#line NUM "FILE"' instead. */
+
+ if (output_current_line != line)
+ {
+ OUTPUT_CHARACTER ('#');
+ OUTPUT_CHARACTER ('l');
+ OUTPUT_CHARACTER ('i');
+ OUTPUT_CHARACTER ('n');
+ OUTPUT_CHARACTER ('e');
+ OUTPUT_CHARACTER (' ');
+ for (cursor = ntoa (line, 10); *cursor; cursor++)
+ OUTPUT_CHARACTER (*cursor);
+ if (output_current_line < 1 && current_file[0] != '\0')
+ {
+ OUTPUT_CHARACTER (' ');
+ OUTPUT_CHARACTER ('"');
+ for (cursor = current_file; *cursor; cursor++)
+ OUTPUT_CHARACTER (*cursor);
+ OUTPUT_CHARACTER ('"');
+ }
+ OUTPUT_CHARACTER ('\n');
+ output_current_line = line;
+ }
+ }
+
+ /* Output the token, and track embedded newlines. */
+ for (; length-- > 0; text++)
+ {
+ if (start_of_output_line)
+ {
+ start_of_output_line = false;
+ output_current_line++;
+#ifdef DEBUG_OUTPUT
+ xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n",
+ line, current_line, output_current_line);
+#endif
+ }
+ OUTPUT_CHARACTER (*text);
+ if (*text == '\n')
+ start_of_output_line = true;
+ }
+ }
+}
+
+/* Functions for use by diversions. */
+
+/*------------------------------------------------------------------.
+| Make a file for diversion DIVNUM, and install it in the diversion |
+| table. Grow the size of the diversion table as needed. |
+`------------------------------------------------------------------*/
+
+/* The number of possible diversions is limited only by memory and
+ available file descriptors (each overflowing diversion uses one). */
+
+void
+make_diversion (int divnum)
+{
+ m4_diversion *diversion = NULL;
+
+ if (current_diversion == divnum)
+ return;
+
+ if (output_diversion)
+ {
+ if (!output_diversion->size && !output_diversion->u.file)
+ {
+ assert (!output_diversion->used);
+ if (!gl_oset_remove (diversion_table, output_diversion))
+ assert (false);
+ output_diversion->u.next = free_list;
+ free_list = output_diversion;
+ }
+ 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, output_diversion->divnum) != 0)
+ m4_error (0, errno,
+ _("cannot close temporary file for diversion"));
+ }
+ output_diversion = NULL;
+ output_file = NULL;
+ output_cursor = NULL;
+ output_unused = 0;
+ }
+
+ current_diversion = divnum;
+
+ if (divnum < 0)
+ return;
+
+ 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)
+ {
+ /* First time visiting this diversion. */
+ if (free_list)
+ {
+ diversion = free_list;
+ free_list = diversion->u.next;
+ }
+ else
+ {
+ diversion = (m4_diversion *) obstack_alloc (&diversion_storage,
+ sizeof *diversion);
+ diversion->size = 0;
+ diversion->used = 0;
+ }
+ diversion->u.file = NULL;
+ diversion->divnum = divnum;
+ gl_oset_add (diversion_table, diversion);
+ }
+
+ 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
+ {
+ if (!output_diversion->u.file && output_diversion->used)
+ output_diversion->u.file = m4_tmpopen (output_diversion->divnum,
+ false);
+ output_file = output_diversion->u.file;
+ }
+ output_current_line = -1;
+}
+
+/*-------------------------------------------------------------------.
+| Insert a FILE into the current output file, in the same manner |
+| diversions are handled. This allows files to be included, without |
+| having them rescanned by m4. |
+`-------------------------------------------------------------------*/
+
+void
+insert_file (FILE *file)
+{
+ static char buffer[COPY_BUFFER_SIZE];
+ size_t length;
+
+ /* Optimize out inserting into a sink. */
+ if (!output_diversion)
+ return;
+
+ /* Insert output by big chunks. */
+ while (1)
+ {
+ length = fread (buffer, 1, sizeof buffer, file);
+ if (ferror (file))
+ M4ERROR ((EXIT_FAILURE, errno, "error reading inserted file"));
+ if (length == 0)
+ break;
+ output_text (buffer, length);
+ }
+}
+
+/*-------------------------------------------------------------------.
+| 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. |
+`-------------------------------------------------------------------*/
+
+static void
+insert_diversion_helper (m4_diversion *diversion)
+{
+ /* Effectively undivert only if an output stream is active. */
+ if (output_diversion)
+ {
+ if (diversion->size)
+ {
+ if (!output_diversion->u.file)
+ {
+ /* Transferring diversion metadata is faster than
+ copying contents. */
+ assert (!output_diversion->used && output_diversion != &div0
+ && !output_file);
+ output_diversion->u.buffer = diversion->u.buffer;
+ output_diversion->size = diversion->size;
+ output_cursor = diversion->u.buffer + diversion->used;
+ output_unused = diversion->size - diversion->used;
+ diversion->u.buffer = NULL;
+ }
+ else
+ {
+ /* Avoid double-charging the total in-memory size when
+ transferring from one in-memory diversion to
+ another. */
+ total_buffer_size -= diversion->size;
+ output_text (diversion->u.buffer, diversion->used);
+ }
+ }
+ else if (!output_diversion->u.file)
+ {
+ /* Transferring diversion metadata is faster than copying
+ contents. */
+ assert (!output_diversion->used && output_diversion != &div0
+ && !output_file);
+ output_diversion->u.file = m4_tmprename (diversion->divnum,
+ output_diversion->divnum);
+ output_diversion->used = 1;
+ output_file = output_diversion->u.file;
+ diversion->u.file = NULL;
+ diversion->size = 1;
+ }
+ else
+ {
+ if (!diversion->u.file)
+ diversion->u.file = m4_tmpopen (diversion->divnum, true);
+ insert_file (diversion->u.file);
+ }
+
+ output_current_line = -1;
+ }
+
+ /* Return all space used by the diversion. */
+ if (diversion->size)
+ {
+ if (!output_diversion)
+ total_buffer_size -= diversion->size;
+ free (diversion->u.buffer);
+ diversion->size = 0;
+ }
+ else
+ {
+ if (diversion->u.file)
+ {
+ FILE *file = diversion->u.file;
+ diversion->u.file = NULL;
+ if (m4_tmpclose (file, diversion->divnum) != 0)
+ m4_error (0, errno,
+ _("cannot clean temporary file for diversion"));
+ }
+ if (m4_tmpremove (diversion->divnum) != 0)
+ M4ERROR ((0, errno, "cannot clean temporary file for diversion"));
+ }
+ diversion->used = 0;
+ 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);
+ }
+}
+
+/*----------------------------------------------------------------.
+| Get back all diversions. This is done just before exiting from |
+| main, and from m4_undivert (), if called without arguments. |
+`----------------------------------------------------------------*/
+
+void
+undivert_all (void)
+{
+ 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);
+}
+
+/*-------------------------------------------------------------.
+| Produce all diversion information in frozen format on FILE. |
+`-------------------------------------------------------------*/
+
+void
+freeze_diversions (FILE *file)
+{
+ int saved_number;
+ int last_inserted;
+ 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 */
+
+ iter = gl_oset_iterator (diversion_table);
+ while (gl_oset_iterator_next (&iter, &elt))
+ {
+ m4_diversion *diversion = (m4_diversion *) elt;
+ if (diversion->size || diversion->used)
+ {
+ if (diversion->size)
+ xfprintf (file, "D%d,%d\n", diversion->divnum, diversion->used);
+ else
+ {
+ struct stat file_stat;
+ diversion->u.file = m4_tmpopen (diversion->divnum, true);
+ 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 + 0UL
+ != (unsigned long int) file_stat.st_size))
+ M4ERROR ((EXIT_FAILURE, 0, "diversion too large"));
+ xfprintf (file, "D%d,%lu\n", diversion->divnum,
+ (unsigned long int) file_stat.st_size);
+ }
+
+ insert_diversion_helper (diversion);
+ putc ('\n', file);
+
+ last_inserted = diversion->divnum;
+ }
+ }
+ gl_oset_iterator_free (&iter);
+
+ /* Save the active diversion number, if not already. */
+
+ if (saved_number != last_inserted)
+ xfprintf (file, "D%d,0\n\n", saved_number);
+}