1 
2 /**
3  * @file tpProcess.c
4  *
5  *  Parse and process the template data descriptions
6  *
7  * @addtogroup autogen
8  * @{
9  */
10 /*
11  * This file is part of AutoGen.
12  * AutoGen Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
13  *
14  * AutoGen is free software: you can redistribute it and/or modify it
15  * under the terms of the GNU General Public License as published by the
16  * Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * AutoGen is distributed in the hope that it will be useful, but
20  * WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22  * See the GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License along
25  * with this program.  If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 /**
29  *  Generate all the text within a block.
30  *  The caller must know the exact bounds of the block.
31  *
32  * @param tpl   template containing block of macros
33  * @param mac   first macro in series
34  * @param emac  one past last macro in series
35  */
36 static void
gen_block(templ_t * tpl,macro_t * mac,macro_t * emac)37 gen_block(templ_t * tpl, macro_t * mac, macro_t * emac)
38 {
39     /*
40      *  Set up the processing context for this block of macros.
41      *  It is used by the Guile callback routines and the exception
42      *  handling code.  It is all for user friendly diagnostics.
43      */
44     current_tpl = tpl;
45 
46     while ((mac != NULL) && (mac < emac)) {
47         mac_func_t fc = mac->md_code;
48         if (fc >= FUNC_CT)
49             fc = FTYP_BOGUS;
50 
51         scribble_free();
52         if (OPT_VALUE_TRACE >= TRACE_EVERYTHING)
53             trace_macro(tpl, mac);
54 
55         cur_macro = mac;
56         mac = (*(load_procs[ fc ]))(tpl, mac);
57     }
58 }
59 
60 /**
61  *  Print out information about the invocation of a macro.
62  *  Print up to the first 32 characters in the macro, for context.
63  *
64  * @param tpl   template containing macros
65  * @param mac   first macro in series
66  */
67 static void
trace_macro(templ_t * tpl,macro_t * mac)68 trace_macro(templ_t * tpl, macro_t * mac)
69 {
70     mac_func_t fc = mac->md_code;
71     if (fc >= FUNC_CT)
72         fc = FTYP_BOGUS;
73 
74     fprintf(trace_fp, TRACE_MACRO_FMT, ag_fun_names[fc], mac->md_code,
75             tpl->td_file, mac->md_line);
76 
77     if (mac->md_txt_off > 0) {
78         char * pz = tpl->td_text + mac->md_txt_off;
79         char * pe = BRK_NEWLINE_CHARS(pz);
80         if (pe > pz + 32)
81             pz = pz + 32;
82 
83         putc(' ', trace_fp); putc(' ', trace_fp);
84         fwrite(pz, (size_t)(pe - pz), 1, trace_fp);
85         putc(NL, trace_fp);
86     }
87 }
88 
89 /**
90  *  The template output goes to stdout.  Perhaps because output
91  *  is for a CGI script.  In any case, this case must be handled
92  *  specially.
93  *
94  * @param tpl   template to be processed
95  */
96 static void
do_stdout_tpl(templ_t * tpl)97 do_stdout_tpl(templ_t * tpl)
98 {
99     SCM res;
100 
101     last_scm_cmd = NULL; /* We cannot be in Scheme processing */
102 
103     switch (setjmp(abort_jmp_buf)) {
104     case SUCCESS:
105         break;
106 
107     case PROBLEM:
108         if (*oops_pfx != NUL) {
109             fprintf(stdout, DO_STDOUT_TPL_ABANDONED, oops_pfx);
110             oops_pfx = zNil;
111         }
112         fclose(stdout);
113         return;
114 
115     default:
116         fserr(AUTOGEN_EXIT_FS_ERROR, DO_STDOUT_TPL_BADR, oops_pfx);
117 
118     case FAILURE:
119         exit(EXIT_FAILURE);
120         /* NOTREACHED */
121     }
122 
123     curr_sfx           = DO_STDOUT_TPL_NOSFX;
124     curr_def_ctx       = root_def_ctx;
125     cur_fpstack        = &out_root;
126     out_root.stk_fp    = stdout;
127     out_root.stk_fname = DO_STDOUT_TPL_STDOUT;
128     out_root.stk_flags = FPF_NOUNLINK | FPF_STATIC_NM;
129     if (OPT_VALUE_TRACE >= TRACE_EVERYTHING)
130         fputs(DO_STDOUT_TPL_START_STD, trace_fp);
131 
132     /*
133      *  IF there is a CGI prefix for error messages,
134      *  THEN divert all output to a temporary file so that
135      *  the output will be clean for any error messages we have to emit.
136      */
137     if (*oops_pfx == NUL)
138         gen_block(tpl, tpl->td_macros, tpl->td_macros + tpl->td_mac_ct);
139 
140     else {
141         char const * pzRes;
142         (void)ag_scm_out_push_new(SCM_UNDEFINED);
143 
144         gen_block(tpl, tpl->td_macros, tpl->td_macros + tpl->td_mac_ct);
145 
146         /*
147          *  Read back in the spooled output.  Make sure it starts with
148          *  a content-type: prefix.  If not, we supply our own HTML prefix.
149          */
150         res   = ag_scm_out_pop(SCM_BOOL_T);
151         pzRes = scm_i_string_chars(res);
152 
153         /* 13 char prefix is:  "content-type:" */
154         if (strneqvcmp(pzRes, DO_STDOUT_TPL_CONTENT, 13) != 0)
155             fputs(DO_STDOUT_TPL_CONTENT, stdout);
156 
157         fwrite(pzRes, scm_c_string_length(res), 1, stdout);
158     }
159 
160     fclose(stdout);
161 }
162 
163 /**
164  * pop the current output spec structure.  Deallocate it and the
165  * file name, too, if necessary.
166  */
167 static out_spec_t *
next_out_spec(out_spec_t * os)168 next_out_spec(out_spec_t * os)
169 {
170     out_spec_t * res = os->os_next;
171 
172     if (os->os_dealloc_fmt)
173         AGFREE(os->os_file_fmt);
174 
175     AGFREE(os);
176     return res;
177 }
178 
179 static void
process_tpl(templ_t * tpl)180 process_tpl(templ_t * tpl)
181 {
182     /*
183      *  IF the template file does not specify any output suffixes,
184      *  THEN we will generate to standard out with the suffix set to zNoSfx.
185      *  With output going to stdout, we don't try to remove output on errors.
186      */
187     if (output_specs == NULL) {
188         do_stdout_tpl(tpl);
189         return;
190     }
191 
192     do  {
193         out_spec_t * os;
194 
195         /*
196          * We cannot be in Scheme processing.  We've either just started
197          * or we've made a long jump from our own code.  If we've made a
198          * long jump, we've printed a message that is sufficient and we
199          * don't need to print any scheme expressions.
200          */
201         last_scm_cmd = NULL;
202 
203         /*
204          *  HOW was that we got here?
205          */
206         switch (setjmp(abort_jmp_buf)) {
207         case SUCCESS:
208             os = output_specs;
209 
210             if (OPT_VALUE_TRACE >= TRACE_EVERYTHING) {
211                 fprintf(trace_fp, PROC_TPL_START, os->os_sfx);
212                 fflush(trace_fp);
213             }
214             /*
215              *  Set the output file name buffer.
216              *  It may get switched inside open_output.
217              */
218             open_output(os);
219             memcpy(&out_root, cur_fpstack, sizeof(out_root));
220             AGFREE(cur_fpstack);
221             cur_fpstack    = &out_root;
222             curr_sfx       = os->os_sfx;
223             curr_def_ctx   = root_def_ctx;
224             cur_fpstack->stk_flags &= ~FPF_FREE;
225             cur_fpstack->stk_prev   = NULL;
226             gen_block(tpl, tpl->td_macros, tpl->td_macros+tpl->td_mac_ct);
227 
228             do  {
229                 out_close(false);  /* keep output */
230             } while (cur_fpstack->stk_prev != NULL);
231 
232             output_specs = next_out_spec(os);
233             break;
234 
235         case PROBLEM:
236             os = output_specs;
237             /*
238              *  We got here by a long jump.  Close/purge the open files
239              *  and go on to the next output.
240              */
241             do  {
242                 out_close(true);  /* discard output */
243             } while (cur_fpstack->stk_prev != NULL);
244             last_scm_cmd = NULL; /* "problem" means "drop current output". */
245             output_specs = next_out_spec(os);
246             break;
247 
248         default:
249             fprintf(trace_fp, PROC_TPL_BOGUS_RET, oops_pfx);
250             oops_pfx = zNil;
251             /* FALLTHROUGH */
252 
253         case FAILURE:
254             os = output_specs;
255 
256             /*
257              *  We got here by a long jump.  Close/purge the open files.
258              */
259             do  {
260                 out_close(true);  /* discard output */
261             } while (cur_fpstack->stk_prev != NULL);
262 
263             /*
264              *  On failure (or unknown jump type), we quit the program, too.
265              */
266             processing_state = PROC_STATE_ABORTING;
267             while (os != NULL)
268                 os = next_out_spec(os);
269 
270             exit(EXIT_FAILURE);
271             /* NOTREACHED */
272         }
273     } while (output_specs != NULL);
274 }
275 
276 static void
set_utime(char const * fname)277 set_utime(char const * fname)
278 {
279 #ifdef HAVE_UTIMENSAT
280     enum { ACCESS_IDX = 0, MODIFY_IDX = 1 };
281     struct timespec const times[2] = {
282         [ACCESS_IDX] = {
283             .tv_sec     = 0,
284             .tv_nsec    = UTIME_OMIT
285         },
286         [MODIFY_IDX] = {
287             .tv_sec     = outfile_time.tv_sec,
288             .tv_nsec    = outfile_time.tv_nsec
289         }
290     };
291     utimensat(AT_FDCWD, fname, times, 0);
292 
293 #else
294     struct utimbuf const tbuf = {
295         .actime  = time(NULL),
296         .modtime = outfile_time
297     };
298 
299     /*
300      *  The putative start time is one second earlier than the
301      *  earliest output file time, regardless of when that is.
302      */
303     if (outfile_time <= start_time)
304         start_time = outfile_time - 1;
305 
306     utime(fname, &tbuf);
307 #endif
308 }
309 
310 /**
311  * close current output file
312  *
313  * @param purge "true" means the output is to be discarded
314  *
315  * Most output files will be set to read only, though that may
316  * get overridden. If the file name has been allocated, then it
317  * is also freed. The current output is set to the next in the
318  * stack.
319  */
320 static void
out_close(bool purge)321 out_close(bool purge)
322 {
323     if ((cur_fpstack->stk_flags & FPF_NOCHMOD) == 0)
324         make_readonly();
325 
326     if (OPT_VALUE_TRACE > TRACE_DEBUG_MESSAGE)
327         fprintf(trace_fp, OUT_CLOSE_TRACE_WRAP, __func__,
328                 cur_fpstack->stk_fname);
329 
330     fclose(cur_fpstack->stk_fp);
331 
332     /*
333      *  Only stdout and /dev/null are marked, "NOUNLINK"
334      */
335     if ((cur_fpstack->stk_flags & FPF_NOUNLINK) == 0) {
336         /*
337          *  IF we are told to purge the file OR the file is an AutoGen temp
338          *  file, then get rid of the output.
339          */
340         if (purge || ((cur_fpstack->stk_flags & FPF_UNLINK) != 0))
341             unlink(cur_fpstack->stk_fname);
342 
343         else
344             set_utime(cur_fpstack->stk_fname);
345     }
346 
347     /*
348      *  Do not deallocate statically allocated names
349      */
350     if ((cur_fpstack->stk_flags & FPF_STATIC_NM) == 0)
351         AGFREE(cur_fpstack->stk_fname);
352 
353     /*
354      *  Do not deallocate the root entry.  It is not allocated!!
355      */
356     if ((cur_fpstack->stk_flags & FPF_FREE) != 0) {
357         out_stack_t * p = cur_fpstack;
358         cur_fpstack = p->stk_prev;
359         AGFREE(p);
360     }
361 }
362 
363 /**
364  *  Figure out what to use as the base name of the output file.
365  *  If an argument is not provided, we use the base name of
366  *  the definitions file.
367  */
368 static void
open_output(out_spec_t * spec)369 open_output(out_spec_t * spec)
370 {
371     static char const write_mode[] = "w" FOPEN_BINARY_FLAG "+";
372 
373     char const * out_file = NULL;
374 
375     if (strcmp(spec->os_sfx, OPEN_OUTPUT_NULL) == 0) {
376         static int const flags = FPF_NOUNLINK | FPF_NOCHMOD | FPF_TEMPFILE;
377     null_open:
378         open_output_file(DEV_NULL, DEV_NULL_LEN, write_mode, flags);
379         return;
380     }
381 
382     /*
383      *  IF we are to skip the current suffix,
384      *  we will redirect the output to /dev/null and
385      *  perform all the work.  There may be side effects.
386      */
387     if (HAVE_OPT(SKIP_SUFFIX)) {
388         int     ct  = STACKCT_OPT(SKIP_SUFFIX);
389         const char ** ppz = STACKLST_OPT(SKIP_SUFFIX);
390 
391         while (--ct >= 0) {
392             if (strcmp(spec->os_sfx, *ppz++) == 0)
393                 goto null_open;
394         }
395     }
396 
397     /*
398      *  Remove any suffixes in the last file name
399      */
400     {
401         char const * def_file = OPT_ARG(BASE_NAME);
402         char   z[AG_PATH_MAX];
403         const char * pst = strrchr(def_file, '/');
404         char * end;
405 
406         pst = (pst == NULL) ? def_file : (pst + 1);
407 
408         /*
409          *  We allow users to specify a suffix with '-' and '_', but when
410          *  stripping a suffix from the "base name", we do not recognize 'em.
411          */
412         end = strchr(pst, '.');
413         if (end != NULL) {
414             size_t len = (unsigned)(end - pst);
415             if (len >= sizeof(z))
416                 AG_ABEND(BASE_NAME_TOO_LONG);
417 
418             memcpy(z, pst, len);
419             z[ end - pst ] = NUL;
420             pst = z;
421         }
422 
423         /*
424          *  Now formulate the output file name in the buffer
425          *  provided as the input argument.
426          */
427         out_file = aprf(spec->os_file_fmt, pst, spec->os_sfx);
428         if (out_file == NULL)
429             AG_ABEND(aprf(OPEN_OUTPUT_BAD_FMT, spec->os_file_fmt, pst,
430                           spec->os_sfx));
431     }
432 
433     open_output_file(out_file, strlen(out_file), write_mode, 0);
434     free(VOIDP(out_file));
435 }
436 /**
437  * @}
438  *
439  * Local Variables:
440  * mode: C
441  * c-file-style: "stroustrup"
442  * indent-tabs-mode: nil
443  * End:
444  * end of agen5/tpProcess.c */
445