/* Attempt to open FILE; if it opens, verify that it is not a directory, and ensure it does not leak across execs. */ FILE * m4_fopen (m4 *context, const char *file, const char *mode) { FILE *fp = NULL; if (file) { struct stat st; int fd; fp = fopen (file, mode); fd = fileno (fp); if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode)) { fclose (fp); errno = EISDIR; return NULL; } if (set_cloexec_flag (fileno (fp), true) != 0) m4_error (context, 0, errno, NULL, _("cannot protect input file across forks")); } return fp; }
/* 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); }
/* 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 && fseek (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 && fseek (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 && fseek (file, 0, SEEK_END) != 0) m4_error (EXIT_FAILURE, errno, _("cannot seek within diversion")); return file; }
/* Change the debug output stream to FP. If the underlying file is the same as stdout, use stdout instead so that debug messages appear in the correct relative position. Report errors on behalf of CALLER. */ static void set_debug_file (m4 *context, const m4_call_info *caller, FILE *fp) { FILE *debug_file; struct stat stdout_stat, debug_stat; assert (context); debug_file = m4_get_debug_file (context); if (debug_file != NULL && debug_file != stderr && debug_file != stdout && close_stream (debug_file) != 0) m4_error (context, 0, errno, caller, _("error writing to debug stream")); debug_file = fp; m4_set_debug_file (context, fp); if (debug_file != NULL && debug_file != stdout) { if (fstat (fileno (stdout), &stdout_stat) < 0) return; if (fstat (fileno (debug_file), &debug_stat) < 0) return; /* mingw has a bug where fstat on a regular file reports st_ino of 0. On normal system, st_ino should never be 0. */ if (stdout_stat.st_ino == debug_stat.st_ino && stdout_stat.st_dev == debug_stat.st_dev && stdout_stat.st_ino != 0) { if (debug_file != stderr && close_stream (debug_file) != 0) m4_error (context, 0, errno, caller, _("error writing to debug stream")); m4_set_debug_file (context, stdout); } } }
/* Generic load function. Push the input file or load the module named FILENAME, if it can be found in the search path. Complain about inaccesible files iff SILENT is false. */ bool m4_load_filename (m4 *context, const m4_call_info *caller, const char *filename, m4_obstack *obs, bool silent) { char *filepath = NULL; char *suffix = NULL; bool new_input = false; if (m4_get_posixly_correct_opt (context)) { if (access (filename, R_OK) == 0) filepath = xstrdup (filename); } else filepath = m4_path_search (context, filename, FILE_SUFFIXES); if (filepath) suffix = strrchr (filepath, '.'); if (!m4_get_posixly_correct_opt (context) && suffix && STREQ (suffix, LT_MODULE_EXT)) { m4_module_load (context, filename, obs); } else { FILE *fp = NULL; if (filepath) fp = m4_fopen (context, filepath, "r"); if (fp == NULL) { if (!silent) m4_error (context, 0, errno, caller, _("cannot open file '%s'"), filename); free (filepath); return false; } m4_push_file (context, fp, filepath, true); new_input = true; } free (filepath); return new_input; }
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; }
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; }
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; } }