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