1
2 /**
3 * @file tpLoad.c
4 *
5 * This module will load a template and return a template structure.
6 *
7 * @addtogroup autogen
8 * @{
9 */
10 /*
11 * This file is part of AutoGen.
12 * Copyright (C) 1992-2018 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 * Return the template structure matching the name passed in.
30 */
31 static templ_t *
find_tpl(char const * tpl_name)32 find_tpl(char const * tpl_name)
33 {
34 templ_t * pT = named_tpls;
35 while (pT != NULL) {
36 if (streqvcmp(tpl_name, pT->td_name) == 0)
37 break;
38 pT = C(templ_t *, (pT->td_scan));
39 }
40 return pT;
41 }
42
43 /**
44 * the name is a regular file with read access.
45 * @param[in] fname file name to check
46 * @returns \a true when the named file exists and is a regular file
47 * @returns \a false otherwise.
48 */
49 static bool
read_okay(char const * fname)50 read_okay(char const * fname)
51 {
52 struct stat stbf;
53 if (stat(fname, &stbf) != 0)
54 return false;
55 if (! S_ISREG(stbf.st_mode))
56 return false;
57 return (access(fname, R_OK) == 0) ? true : false;
58 }
59
60 /**
61 * Expand a directory name that starts with '$'.
62 *
63 * @param[in,out] dir_pp pointer to pointer to directory name
64 * @returns the resulting pointer
65 */
66 static char const *
expand_dir(char const ** dir_pp,char * name_buf)67 expand_dir(char const ** dir_pp, char * name_buf)
68 {
69 char * res = VOIDP(*dir_pp);
70
71 if (res[1] == NUL)
72 AG_ABEND(aprf(LOAD_FILE_SHORT_NAME, res));
73
74 if (! optionMakePath(name_buf, (int)AG_PATH_MAX, res,
75 autogenOptions.pzProgPath)) {
76 /*
77 * The name expanded to "empty", so substitute curdir.
78 */
79 strcpy(res, FIND_FILE_CURDIR);
80
81 } else {
82 free(res);
83 AGDUPSTR(res, name_buf, "find dir name");
84 *dir_pp = res; /* save computed name for later */
85 }
86
87 return res;
88 }
89
90 static inline bool
file_search_dirs(char const * in_name,char * res_name,char const * const * sfx_list,char const * referring_tpl,size_t nm_len,bool no_suffix)91 file_search_dirs(
92 char const * in_name,
93 char * res_name,
94 char const * const * sfx_list,
95 char const * referring_tpl,
96 size_t nm_len,
97 bool no_suffix)
98 {
99 /*
100 * Search each directory in our directory search list for the file.
101 * We always force two copies of this option, so we know it exists.
102 * Later entries are more recently added and are searched first.
103 * We start the "dirlist" pointing to the real last entry.
104 */
105 int ct = STACKCT_OPT(TEMPL_DIRS);
106 char const ** dirlist = STACKLST_OPT(TEMPL_DIRS) + ct - 1;
107 char const * c_dir = FIND_FILE_CURDIR;
108
109 /*
110 * IF the file name starts with a directory separator,
111 * then we only search once, looking for the exact file name.
112 */
113 if (*in_name == '/')
114 ct = -1;
115
116 for (;;) {
117 char * pzEnd;
118
119 /*
120 * c_dir is always FIND_FILE_CURDIR the first time through
121 * and is never that value after that.
122 */
123 if (c_dir == FIND_FILE_CURDIR) {
124
125 memcpy(res_name, in_name, nm_len);
126 pzEnd = res_name + nm_len;
127 *pzEnd = NUL;
128
129 } else {
130 unsigned int fmt_len;
131
132 /*
133 * IF one of our template paths starts with '$', then expand it
134 * and replace it now and forever (the rest of this run, anyway).
135 */
136 if (*c_dir == '$')
137 c_dir = expand_dir(dirlist+1, res_name);
138
139 fmt_len = (unsigned)snprintf(
140 res_name, AG_PATH_MAX - MAX_SUFFIX_LEN,
141 FIND_FILE_DIR_FMT, c_dir, in_name);
142 if (fmt_len >= AG_PATH_MAX - MAX_SUFFIX_LEN)
143 break; // fail-return
144 pzEnd = res_name + fmt_len;
145 }
146
147 if (read_okay(res_name))
148 return true;
149
150 /*
151 * IF the file does not already have a suffix,
152 * THEN try the ones that are okay for this file.
153 */
154 if (no_suffix && (sfx_list != NULL)) {
155 char const * const * sfxl = sfx_list;
156 *(pzEnd++) = '.';
157
158 do {
159 strcpy(pzEnd, *(sfxl++)); /* must fit */
160 if (read_okay(res_name))
161 return true;
162
163 } while (*sfxl != NULL);
164 }
165
166 /*
167 * IF we've exhausted the search list,
168 * THEN see if we're done, else go through search dir list.
169 *
170 * We try one more thing if there is a referrer.
171 * If the searched-for file is a full path, "ct" will
172 * start at -1 and we will leave the loop here and now.
173 */
174 if (--ct < 0) {
175 if ((referring_tpl == NULL) || (ct != -1))
176 break;
177 c_dir = referring_tpl;
178
179 } else {
180 c_dir = *(dirlist--);
181 }
182 }
183
184 return false;
185 }
186
187 /**
188 * Search for a file.
189 *
190 * Starting with the current directory, search the directory list trying to
191 * find the base template file name. If there is a referring template (a
192 * template with an "INCLUDE" macro), then try that, too, before giving up.
193 *
194 * @param[in] in_name the file name we are looking for.
195 * @param[out] res_name where we stash the file name we found.
196 * @param[in] sfx_list a list of suffixes to try, if \a in_name has none.
197 * @param[in] referring_tpl file name of the template with a INCLUDE macro.
198 *
199 * @returns \a SUCCESS when \a res_name is valid
200 * @returns \a FAILURE when the file is not found.
201 */
202 static tSuccess
find_file(char const * in_name,char * res_name,char const * const * sfx_list,char const * referring_tpl)203 find_file(char const * in_name,
204 char * res_name,
205 char const * const * sfx_list,
206 char const * referring_tpl)
207 {
208 bool no_suffix;
209 void * free_me = NULL;
210 tSuccess res = SUCCESS;
211
212 size_t nm_len = strlen(in_name);
213 if (nm_len >= AG_PATH_MAX - MAX_SUFFIX_LEN)
214 return FAILURE;
215
216 /*
217 * Expand leading environment variables.
218 * We will not mess with embedded ones.
219 */
220 if (*in_name == '$') {
221 if (! optionMakePath(res_name, (int)AG_PATH_MAX, in_name,
222 autogenOptions.pzProgPath))
223 return FAILURE;
224
225 AGDUPSTR(in_name, res_name, "find file name");
226 free_me = VOIDP(in_name);
227
228 /*
229 * in_name now points to the name the file system can use.
230 * It must _not_ point to res_name because we will likely
231 * rewrite that value using this pointer!
232 */
233 nm_len = strlen(in_name);
234 }
235
236 /*
237 * Not a complete file name. If there is not already
238 * a suffix for the file name, then append ".tpl".
239 * Check for immediate access once again.
240 */
241 {
242 char * bf = strrchr(in_name, '/');
243 bf = (bf != NULL) ? strchr(bf, '.') : strchr(in_name, '.');
244 no_suffix = (bf == NULL);
245 }
246
247 /*
248 * The referrer is useful only if it includes a directory name.
249 * If not NULL, referring_tpl becomes an allocated directory name.
250 */
251 if (referring_tpl != NULL) {
252 char * pz = strrchr(referring_tpl, '/');
253 if (pz == NULL)
254 referring_tpl = NULL;
255 else {
256 AGDUPSTR(referring_tpl, referring_tpl, "refer tpl");
257 pz = strrchr(referring_tpl, '/');
258 *pz = NUL;
259 }
260 }
261
262 if (! file_search_dirs(in_name, res_name, sfx_list, referring_tpl,
263 nm_len, no_suffix))
264 res = FAILURE;
265
266 AGFREE(free_me);
267 AGFREE(referring_tpl);
268 return res;
269 }
270
271 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
272 /**
273 * Count the macros in a template.
274 * We need to allocate the right number of pointers.
275 */
276 static size_t
cnt_macros(char const * pz)277 cnt_macros(char const * pz)
278 {
279 size_t ct = 2;
280 for (;;) {
281 pz = strstr(pz, st_mac_mark);
282 if (pz == NULL)
283 break;
284 ct += 2;
285 if (strncmp(pz - end_mac_len, end_mac_mark, end_mac_len) == 0)
286 ct--;
287 pz += st_mac_len;
288 }
289 return ct;
290 }
291
292 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
293 /**
294 * Load the macro array and file name.
295 * @param[in,out] tpl the template to load
296 * @param[in] fname the source file name of the template
297 * @param[in] pzN someting
298 * @param[in] data the template text
299 */
300 static void
load_macs(templ_t * tpl,char const * fname,char const * pzData)301 load_macs(templ_t * tpl, char const * fname, char const * pzData)
302 {
303 macro_t * pMac = tpl->td_macros;
304
305 {
306 char * txt = (char *)(pMac + tpl->td_mac_ct);
307
308 AGDUPSTR(tpl->td_file, fname, "templ file");
309
310 memcpy(txt, PSEUDO_MAC_TPL_FILE, PSEUDO_MAC_TPL_FILE_LEN+1);
311 tpl->td_name = txt;
312 tpl->td_text = (txt += PSEUDO_MAC_TPL_FILE_LEN);
313 tpl->td_scan = txt + 1;
314 }
315
316 current_tpl = tpl;
317
318 {
319 macro_t * e_mac = parse_tpl(pMac, &pzData);
320 int ct;
321
322 /*
323 * Make sure all of the input string was scanned.
324 */
325 if (pzData != NULL)
326 AG_ABEND(LOAD_MACS_BAD_PARSE);
327
328 ct = (int)(e_mac - pMac);
329
330 /*
331 * IF there are empty macro slots,
332 * THEN pack the text
333 */
334 if (ct < tpl->td_mac_ct) {
335 int delta =
336 (int)(sizeof(macro_t) * (size_t)(tpl->td_mac_ct - ct));
337 void * data =
338 (tpl->td_name == NULL) ? tpl->td_text : tpl->td_name;
339 size_t size = (size_t)(tpl->td_scan - (char *)data);
340 memmove(VOIDP(e_mac), data, size);
341
342 tpl->td_text -= delta;
343 tpl->td_scan -= delta;
344 tpl->td_name -= delta;
345 tpl->td_mac_ct = ct;
346 }
347 }
348
349 tpl->td_size = (size_t)(tpl->td_scan - (char *)tpl);
350 tpl->td_scan = NULL;
351
352 /*
353 * We cannot reallocate a smaller array because
354 * the entries are all linked together and
355 * realloc-ing it may cause it to move.
356 */
357 #if defined(DEBUG_ENABLED)
358 if (HAVE_OPT(SHOW_DEFS)) {
359 static char const zSum[] =
360 "loaded %d macros from %s\n"
361 "\tBinary template size: 0x%zX\n\n";
362 fprintf(trace_fp, zSum, tpl->td_mac_ct, fname, tpl->td_size);
363 }
364 #endif
365 }
366
367 /**
368 * Load a template from mapped memory. Load up the pseudo macro,
369 * count the macros, allocate the data, and parse all the macros.
370 *
371 * @param[in] minfo information about the mapped memory.
372 * @param[in] fname the full path input file name.
373 *
374 * @returns the digested data
375 */
376 static templ_t *
digest_tpl(tmap_info_t * minfo,char * fname)377 digest_tpl(tmap_info_t * minfo, char * fname)
378 {
379 templ_t * res;
380
381 /*
382 * Count the number of macros in the template. Compute
383 * the output data size as a function of the number of macros
384 * and the size of the template data. These may get reduced
385 * by comments.
386 */
387 char const * dta =
388 load_pseudo_mac((char const *)minfo->txt_data, fname);
389
390 size_t mac_ct = cnt_macros(dta);
391 size_t alloc_sz = (sizeof(*res) + (mac_ct * sizeof(macro_t))
392 + minfo->txt_size
393 - (size_t)(dta - (char const *)minfo->txt_data)
394 + strlen(fname) + 0x10)
395 & (size_t)(~0x0F);
396
397 res = (templ_t *)AGALOC(alloc_sz, "main template");
398 memset(VOIDP(res), 0, alloc_sz);
399
400 /*
401 * Initialize the values:
402 */
403 res->td_magic = magic_marker;
404 res->td_size = alloc_sz;
405 res->td_mac_ct = (int)mac_ct;
406
407 strcpy(res->td_start_mac, st_mac_mark); /* must fit */
408 strcpy(res->td_end_mac, end_mac_mark); /* must fit */
409 load_macs(res, fname, dta);
410
411 res->td_name -= (long)res;
412 res->td_text -= (long)res;
413 res = (templ_t *)AGREALOC(VOIDP(res), res->td_size,
414 "resize template");
415 res->td_name += (long)res;
416 res->td_text += (long)res;
417
418 return res;
419 }
420
421 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
422 /**
423 * Starting with the current directory, search the directory
424 * list trying to find the base template file name.
425 */
426 static templ_t *
tpl_load(char const * fname,char const * referrer)427 tpl_load(char const * fname, char const * referrer)
428 {
429 static tmap_info_t map_info;
430 static char tpl_file[ AG_PATH_MAX ];
431
432 /*
433 * Find the template file somewhere
434 */
435 {
436 static char const * const sfx_list[] = {
437 LOAD_TPL_SFX_TPL, LOAD_TPL_SFX_AGL, NULL };
438 if (! SUCCESSFUL(find_file(fname, tpl_file, sfx_list, referrer))) {
439 errno = ENOENT;
440 AG_CANT(LOAD_TPL_CANNOT_MAP, fname);
441 }
442 }
443
444 /*
445 * Make sure the specified file is a regular file.
446 * Make sure the output time stamp is at least as recent.
447 */
448 {
449 struct stat stbf;
450 if (stat(tpl_file, &stbf) != 0)
451 AG_CANT(LOAD_TPL_CANNOT_STAT, fname);
452
453 if (! S_ISREG(stbf.st_mode)) {
454 errno = EINVAL;
455 AG_CANT(LOAD_TPL_IRREGULAR, fname);
456 }
457
458 if (time_is_before(outfile_time, stbf.st_mtime))
459 outfile_time = stbf.st_mtime;
460 if (time_is_before(maxfile_time, stbf.st_mtime))
461 maxfile_time = stbf.st_mtime;
462 }
463
464 text_mmap(tpl_file, PROT_READ|PROT_WRITE, MAP_PRIVATE, &map_info);
465 if (TEXT_MMAP_FAILED_ADDR(map_info.txt_data))
466 AG_ABEND(aprf(LOAD_TPL_CANNOT_OPEN, tpl_file));
467
468 if (dep_fp != NULL)
469 add_source_file(tpl_file);
470
471 /*
472 * Process the leading pseudo-macro. The template proper
473 * starts immediately after it.
474 */
475 {
476 macro_t * sv_mac = cur_macro;
477 templ_t * res;
478 cur_macro = NULL;
479
480 res = digest_tpl(&map_info, tpl_file);
481 cur_macro = sv_mac;
482 text_munmap(&map_info);
483
484 return res;
485 }
486 }
487
488 /**
489 * Deallocate anything related to a template.
490 * This includes the pointer passed in and any macros that have an
491 * unload procedure associated with it.
492 *
493 * @param[in] tpl the template to unload
494 */
495 static void
tpl_unload(templ_t * tpl)496 tpl_unload(templ_t * tpl)
497 {
498 macro_t * mac = tpl->td_macros;
499 int ct = tpl->td_mac_ct;
500
501 while (--ct >= 0) {
502 unload_proc_p_t proc;
503 unsigned int ix = mac->md_code;
504
505 /*
506 * "select" functions get remapped, depending on the alias used for
507 * the selection. See the "mac_func_t" enumeration in functions.h.
508 */
509 if (ix >= FUNC_CT)
510 ix = FTYP_SELECT;
511
512 proc = unload_procs[ ix ];
513 if (proc != NULL)
514 (*proc)(mac);
515
516 mac++;
517 }
518
519 AGFREE(tpl->td_file);
520 AGFREE(tpl);
521 }
522
523 /**
524 * This gets called when all is well at the end.
525 * The supplied template and all named templates are unloaded.
526 *
527 * @param[in] tpl the last template standing
528 */
529 static void
cleanup(templ_t * tpl)530 cleanup(templ_t * tpl)
531 {
532 if (HAVE_OPT(USED_DEFINES))
533 print_used_defines();
534
535 if (dep_fp != NULL)
536 wrap_up_depends();
537
538 optionFree(&autogenOptions);
539
540 for (;;) {
541 tpl_unload(tpl);
542 tpl = named_tpls;
543 if (tpl == NULL)
544 break;
545 named_tpls = C(templ_t *, (tpl->td_scan));
546 }
547
548 free_for_context(INT_MAX);
549 unload_defs();
550 }
551
552 /**
553 * @}
554 *
555 * Local Variables:
556 * mode: C
557 * c-file-style: "stroustrup"
558 * indent-tabs-mode: nil
559 * End:
560 * end of agen5/tpLoad.c */
561