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