1 /* quick but useful static ssi processor.
2 */
3 #include "ex_utils.h"
4 #include "opt.h"
5 
6 #include <sys/time.h>
7 #include <time.h>
8 
9 #include <pwd.h>
10 
11 #include <dirent.h>
12 
13 #include <getopt.h>
14 
15 #define USE_POPEN 1 /* hacky ... */
16 
17 #if USE_POPEN
18 #include <stdio.h>
19 #else
20 /* spawn.h doesn't exist on MacOSX ... *sigh*/
21 #include <spawn.h>
22 #endif
23 
24 #define EX_SSI_FAILED(x) do {                                           \
25         vstr_add_fmt(s1, s1->len,                                       \
26                      "<!-- \"%s\" SSI command FAILED -->\n", (x));      \
27         goto ssi_parse_failed;                                          \
28     }                                                                   \
29     while (FALSE)
30 
31 #define EX_SSI_OK(x, sf, p, l, end) do {                                \
32       vstr_add_fmt(s1, s1->len, "<!-- \"%s ${vstr:%p%zu%zu%u}"          \
33                    "\" SSI command OK -->%s",                           \
34                    (x), (sf), (p), (l), VSTR_TYPE_ADD_ALL_REF,          \
35                    (end) ? "\n" : "");                                  \
36     }                                                                   \
37     while (FALSE)
38 
39 
40 
41 
42 static char *timespec = NULL;
43 static int use_size_abbrev = TRUE;
44 
ex_ssi_cat_read_fd_write_stdout(Vstr_base * s1,int fd)45 static void ex_ssi_cat_read_fd_write_stdout(Vstr_base *s1, int fd)
46 {
47   while (TRUE)
48   {
49     int io_w_state = IO_OK;
50     int io_r_state = io_get(s1, fd);
51 
52     if (io_r_state == IO_EOF)
53       break;
54 
55     io_w_state = io_put(s1, 1);
56 
57     io_limit(io_r_state, fd, io_w_state, 1, s1);
58   }
59 }
60 
ex_ssi_srch_end(Vstr_base * s2,size_t pos,size_t len)61 static size_t ex_ssi_srch_end(Vstr_base *s2, size_t pos, size_t len)
62 {
63   int instr = FALSE;
64 
65   while (TRUE)
66   {
67     size_t srch = vstr_cspn_cstr_chrs_fwd(s2, pos, len, "\\\"-");
68 
69     pos += srch;
70     len -= srch;
71 
72     if (!len)
73       break;
74 
75     if (0) { }
76     else if (!instr && vstr_export_chr(s2, pos) == '-')
77     { /* see if it's the end... */
78       if (len < strlen("-->"))
79         return (0);
80 
81       if (vstr_cmp_bod_cstr_eq(s2, pos, len, "-->"))
82         return (pos + strlen("->"));
83     }
84     else if (!instr && vstr_export_chr(s2, pos) == '\\')
85     { /* do nothing */ }
86     else if ( instr && vstr_export_chr(s2, pos) == '\\')
87     {
88       if (len > 1)
89       {
90         ++pos;
91         --len;
92       }
93     }
94     else if (vstr_export_chr(s2, pos) == '"')
95     { instr = !instr; }
96 
97     ++pos;
98     --len;
99   }
100 
101   return (0);
102 }
103 
ex_ssi_skip_val(Vstr_base * s2,size_t * srch,size_t val)104 static inline void ex_ssi_skip_val(Vstr_base *s2, size_t *srch, size_t val)
105 {
106   vstr_del(s2, 1, val);
107   *srch        -= val;
108 }
109 
ex_ssi_skip_wsp(Vstr_base * s2,size_t * srch)110 static inline void ex_ssi_skip_wsp(Vstr_base *s2, size_t *srch)
111 {
112   size_t val = vstr_spn_cstr_chrs_fwd(s2, 1, *srch, " ");
113 
114   ex_ssi_skip_val(s2, srch, val);
115 }
116 
ex_ssi_skip_str(Vstr_base * s2,size_t * srch,const char * passed_val)117 static inline void ex_ssi_skip_str(Vstr_base *s2, size_t *srch,
118                                    const char *passed_val)
119 {
120   size_t val = strlen(passed_val);
121 
122   ex_ssi_skip_val(s2, srch, val);
123 }
124 
ex_ssi_attr_val(Vstr_base * s2,size_t * srch)125 static size_t ex_ssi_attr_val(Vstr_base *s2, size_t *srch)
126 {
127   size_t pos = 1;
128   size_t len = *srch;
129   size_t ret = 0;
130 
131   while (TRUE)
132   {
133     size_t scan = vstr_cspn_cstr_chrs_fwd(s2, pos, len, "\\\"");
134 
135     if (scan == len)
136       return (0);
137 
138     ret += scan;
139 
140     if (vstr_export_chr(s2, pos + scan) != '\\')
141       break;
142 
143     if (scan == (len - 1))
144       return (0);
145 
146     ++ret;
147 
148     vstr_del(s2, pos + scan, 1);
149     pos += scan + 1;
150     len -= scan + 1;
151     --*srch;
152 
153     ASSERT(len);
154   }
155 
156   return (ret);
157 }
158 
ex_ssi_file_attr(Vstr_base * s2,size_t * srch)159 static size_t ex_ssi_file_attr(Vstr_base *s2, size_t *srch)
160 {
161   size_t ret = 0;
162 
163   ex_ssi_skip_wsp(s2, srch);
164 
165   if (vstr_cmp_case_bod_cstr_eq(s2, 1, *srch, "file=\""))
166     ex_ssi_skip_str(s2, srch, "file=\"");
167   else
168     return (0);
169 
170   if (!(ret = ex_ssi_attr_val(s2, srch)))
171     return (0);
172 
173   return (ret);
174 }
175 
176 #if !USE_POPEN
ex_ssi_spawn_r(const char * prog,pid_t * pid,char * argv[],char * env[])177 static int ex_ssi_spawn_r(const char *prog, pid_t *pid,
178                           char *argv[], char *env[])
179 {
180   posix_spawn_file_actions_t acts[1];
181   int fds[2];
182   char *dummy_env[] = {NULL};
183 
184   if (!env)
185     env = dummy_env;
186 
187   if ((errno = posix_spawn_file_actions_init(acts)))
188     err(EXIT_FAILURE, "spawn_make");
189 
190   if (pipe(fds) == -1)
191     err(EXIT_FAILURE, "pipe");
192 
193   if ((errno = posix_spawn_file_actions_adddup2(attr, fds[1], FILENO_STDOUT)))
194     err(EXIT_FAILURE, "spawn_dup2");
195   if ((errno = posix_spawn_file_actions_addclose(attr, fds[0])))
196     err(EXIT_FAILURE, "spawn_close");
197   if ((errno = posix_spawn_file_actions_addclose(attr, fds[1])))
198     err(EXIT_FAILURE, "spawn_close");
199 
200   if ((errno = posix_spawnp(pid, prog, acts, NULL, argv, NULL)))
201     err(EXIT_FAILURE, "spawn");
202 
203   if ((errno = posix_spawn_file_actions_destroy(acts)))
204     err(EXIT_FAILURE, "spawn_free");
205 
206   close(fds[1]);
207 
208   return (fds[0]);
209 }
210 #endif
211 
ex_ssi_exec(Vstr_base * s1,Vstr_base * s2,size_t pos,size_t len)212 static void ex_ssi_exec(Vstr_base *s1,
213                         Vstr_base *s2, size_t pos, size_t len)
214 {
215 #if USE_POPEN
216   FILE *fp = NULL; /* FIXME: hack job */
217   /*  struct stat64 sbuf[1];
218    *
219    * if (stat64(vstr_export_cstr_ptr(s2, pos, len), sbuf))
220    *   err(EXIT_FAILURE, "stat(%s)", vstr_export_cstr_ptr(s2, pos, len));
221    */
222   if (!(fp = popen(vstr_export_cstr_ptr(s2, pos, len), "r")))
223     err(EXIT_FAILURE, "popen(%s)", vstr_export_cstr_ptr(s2, pos, len));
224 
225   ex_ssi_cat_read_fd_write_stdout(s1, fileno(fp));
226 
227   pclose(fp);
228 #else
229   Vstr_sects *sects = vstr_sects_make(4);
230   size_t srch = 0;
231   size_t tpos = pos;
232   size_t tlen = len;
233   pid_t pid;
234 
235   while ((srch < tlen) &&
236          (tmp = vstr_cspn_cstr_chrs_fwd(s2, , tlen, " ")))
237   {
238   }
239 
240   /* FIXME: doesn't handle <foo "bar baz" arg2>... */
241   vstr_split_cstr_buf(s2, pos, len, " ", sects, 0, VSTR_FLAG_SPLIT_NO_RET);
242 
243   ex_ssi_cat_read_fd_write_stdout(s1,
244                                   ex_ssi_spawn_r(argv[0], &pid, argv, NULL));
245   waitpid(pid, NULL, 0);
246 #endif
247 }
248 
ex_ssi_strftime(time_t val,int use_gmt)249 static const char *ex_ssi_strftime(time_t val, int use_gmt)
250 {
251   static char ret[4096];
252   const char *spec = timespec;
253   struct tm *tm_val = NULL;
254 
255   if (!spec)  spec = "%c";
256 
257   if (use_gmt)
258     tm_val = gmtime(&val);
259   else
260     tm_val = localtime(&val);
261 
262   if (!tm_val)
263     err(EXIT_FAILURE, "gmtime");
264 
265   strftime(ret, sizeof(ret), spec, tm_val);
266 
267   return (ret);
268 }
269 
ex_ssi_getpwuid_name(uid_t uid)270 static const char *ex_ssi_getpwuid_name(uid_t uid)
271 {
272   struct passwd *pw = getpwuid(uid);
273 
274   if (!pw)
275     return (":unknown:");
276 
277   return (pw->pw_name);
278 }
279 
ex_ssi_process(Vstr_base * s1,Vstr_base * s2,time_t last_modified,int last)280 static int ex_ssi_process(Vstr_base *s1, Vstr_base *s2, time_t last_modified,
281                           int last)
282 {
283   size_t srch = 0;
284   int ret = FALSE;
285 
286   /* we don't want to create more data, if we are over our limit */
287   if (s1->len > EX_MAX_W_DATA_INCORE)
288     return (FALSE);
289 
290   while (s2->len >= strlen("<!--#"))
291   {
292     if (!(srch = vstr_srch_cstr_buf_fwd(s2, 1, s2->len, "<!--#")))
293     {
294       if (last)
295         break;
296 
297       ret = TRUE;
298       vstr_mov(s1, s1->len, s2, 1, s2->len - strlen("<!--#"));
299       break;
300     }
301 
302     if (srch > 1)
303     {
304       ret = TRUE;
305       vstr_add_vstr(s1, s1->len, s2, 1, srch - 1, VSTR_TYPE_ADD_BUF_REF);
306       vstr_del(s2, 1, srch - 1);
307     }
308 
309     if (!(srch = ex_ssi_srch_end(s2, 1, s2->len)))
310       break;
311 
312     ret = TRUE;
313 
314     if (0) { }
315     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "<!--#include"))
316     {
317       int fd = -1;
318       size_t tmp = 0;
319 
320       ex_ssi_skip_str(s2, &srch, "<!--#include");
321 
322       if (!(tmp = ex_ssi_file_attr(s2, &srch)))
323         EX_SSI_FAILED("include");
324 
325       EX_SSI_OK("include", s2, 1, tmp, TRUE);
326 
327       if (s1->conf->malloc_bad)
328         errno = ENOMEM, err(EXIT_FAILURE, "add data");
329 
330       fd = io_open(vstr_export_cstr_ptr(s2, 1, tmp));
331 
332       ex_ssi_cat_read_fd_write_stdout(s1, fd);
333 
334       if (close(fd) == -1)
335         warn("close(%s)", vstr_export_cstr_ptr(s2, 1, tmp));
336     }
337     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#exec"))
338     {
339       size_t tmp = 0;
340 
341       ex_ssi_skip_str(s2, &srch, "<!--#exec");
342       ex_ssi_skip_wsp(s2, &srch);
343 
344       if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "cmd=\""))
345         ex_ssi_skip_str(s2, &srch, "cmd=\"");
346       else
347         EX_SSI_FAILED("exec");
348 
349       if (!(tmp = ex_ssi_attr_val(s2, &srch)))
350         EX_SSI_FAILED("exec");
351 
352       if (s1->conf->malloc_bad)
353         errno = ENOMEM, err(EXIT_FAILURE, "add data");
354 
355       EX_SSI_OK("exec", s2, 1, tmp, TRUE);
356 
357       ex_ssi_exec(s1, s2, 1, tmp);
358     }
359     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#config"))
360     {
361       size_t tmp = 0;
362       enum { tERR = -1,
363              tsize, ttime } type = tERR;
364       const char *tname[2] = {"config sizefmt", "config timefmt"};
365 
366       ex_ssi_skip_str(s2, &srch, "<!--#config");
367       ex_ssi_skip_wsp(s2, &srch);
368 
369       if (0) { }
370       else if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "timefmt=\""))
371         ex_ssi_skip_str(s2, &srch, "timefmt=\""), type = ttime;
372       else if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "sizefmt=\""))
373         ex_ssi_skip_str(s2, &srch, "sizefmt=\""), type = tsize;
374       else
375         EX_SSI_FAILED("config");
376 
377       ASSERT(type >= 0);
378 
379       if (!(tmp = ex_ssi_attr_val(s2, &srch)))
380         EX_SSI_FAILED(tname[type]);
381 
382       EX_SSI_OK(tname[type], s2, 1, tmp, FALSE);
383 
384       switch (type)
385       {
386         case ttime:
387           free(timespec);
388           timespec = vstr_export_cstr_malloc(s2, 1, tmp);
389           break;
390         case tsize:
391           if (0){ }
392           else if (vstr_cmp_cstr_eq(s2, 1, tmp, "bytes"))
393             use_size_abbrev = FALSE;
394           else if (vstr_cmp_cstr_eq(s2, 1, tmp, "abbrev"))
395             use_size_abbrev = TRUE;
396           break;
397         default:
398           ASSERT(FALSE);
399       }
400 
401       /* <!--#config errmsg="foo" --> ? */
402     }
403     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#echo"))
404     {
405       size_t tmp = 0;
406 
407       ex_ssi_skip_str(s2, &srch, "<!--#echo");
408       ex_ssi_skip_wsp(s2, &srch);
409 
410       if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "encoding=\""))
411         ex_ssi_skip_str(s2, &srch, "encoding=\"");
412       else
413         EX_SSI_FAILED("echo");
414 
415       if (!(tmp = ex_ssi_attr_val(s2, &srch)))
416         EX_SSI_FAILED("echo");
417 
418       if (!vstr_cmp_cstr_eq(s2, 1, tmp, "none"))
419         EX_SSI_FAILED("echo");
420 
421       srch -= tmp + 1;
422       vstr_del(s2, 1, tmp + 1);
423       ex_ssi_skip_wsp(s2, &srch);
424 
425       if (vstr_cmp_case_bod_cstr_eq(s2, 1, srch, "var=\""))
426         ex_ssi_skip_str(s2, &srch, "var=\"");
427       else
428         EX_SSI_FAILED("echo");
429 
430       if (!(tmp = ex_ssi_attr_val(s2, &srch)))
431         EX_SSI_FAILED("echo");
432 
433       if (0) { }
434       else if (vstr_cmp_cstr_eq(s2, 1, tmp, "LAST_MODIFIED"))
435         vstr_add_cstr_buf(s1, s1->len, ex_ssi_strftime(last_modified, FALSE));
436       else if (vstr_cmp_cstr_eq(s2, 1, tmp, "DATE_GMT"))
437         vstr_add_cstr_buf(s1, s1->len, ex_ssi_strftime(time(NULL), TRUE));
438       else if (vstr_cmp_cstr_eq(s2, 1, tmp, "DATE_LOCAL"))
439         vstr_add_cstr_buf(s1, s1->len, ex_ssi_strftime(time(NULL), FALSE));
440       else if (vstr_cmp_cstr_eq(s2, 1, tmp, "USER_NAME"))
441         vstr_add_cstr_buf(s1, s1->len, ex_ssi_getpwuid_name(getuid()));
442       else
443         EX_SSI_FAILED("echo");
444 
445       EX_SSI_OK("echo", s2, 1, tmp, FALSE);
446 
447 
448       /* <!--#echo encoding="none" var="LAST_MODIFIED" --> */
449       /* <!--#echo encoding="url" var="LAST_MODIFIED" --> */
450       /* <!--#echo encoding="entity" var="LAST_MODIFIED" --> */
451 
452       /* <!--#echo var="DOCUMENT_NAME" --> */
453       /* <!--#echo var="DOCUMENT_URI" --> */
454 
455     }
456     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#fsize"))
457     {
458       size_t tmp = 0;
459       struct stat64 sbuf[1];
460 
461       ex_ssi_skip_str(s2, &srch, "<!--#fsize");
462 
463       if (!(tmp = ex_ssi_file_attr(s2, &srch)))
464         EX_SSI_FAILED("fsize");
465 
466       EX_SSI_OK("fsize", s2, 1, tmp, FALSE);
467 
468       if (s1->conf->malloc_bad)
469         errno = ENOMEM, err(EXIT_FAILURE, "add data");
470 
471       if (stat64(vstr_export_cstr_ptr(s2, 1, tmp), sbuf))
472         err(EXIT_FAILURE, "stat(%s)", vstr_export_cstr_ptr(s2, 1, tmp));
473 
474       if (use_size_abbrev)
475         vstr_add_fmt(s1, s1->len, "${BKMG.ju:%ju}",
476                      (VSTR_AUTOCONF_uintmax_t)sbuf->st_size);
477       else
478         vstr_add_fmt(s1, s1->len, "%ju",
479                      (VSTR_AUTOCONF_uintmax_t)sbuf->st_size);
480     }
481     else if (vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#flastmod"))
482     {
483       size_t tmp = 0;
484       struct stat64 sbuf[1];
485 
486       ex_ssi_skip_str(s2, &srch, "<!--#flastmod");
487 
488       if (!(tmp = ex_ssi_file_attr(s2, &srch)))
489         EX_SSI_FAILED("flastmod");
490 
491       EX_SSI_OK("flastmod", s2, 1, tmp, FALSE);
492 
493       if (s1->conf->malloc_bad)
494         errno = ENOMEM, err(EXIT_FAILURE, "add data");
495 
496       if (stat64(vstr_export_cstr_ptr(s2, 1, tmp), sbuf))
497         err(EXIT_FAILURE, "stat(%s)", vstr_export_cstr_ptr(s2, 1, tmp));
498 
499       vstr_add_cstr_buf(s1, s1->len, ex_ssi_strftime(sbuf->st_mtime, TRUE));
500     }
501     else if (0 && vstr_cmp_case_bod_cstr_eq(s2, 1, s2->len, "<!--#if"))
502     {
503       ex_ssi_skip_str(s2, &srch, "<!--#if");
504       ex_ssi_skip_wsp(s2, &srch);
505 
506       /* <!--#if expr="foo" --> ? */
507       /* <!--#elif expt="foo" --> ? */
508       /* <!--#else --> ? */
509       /* <!--#endif --> ? */
510 
511     }
512     else
513       vstr_add_cstr_ptr(s1, s1->len, "<!-- UNKNOWN SSI command -->\n");
514 
515     ASSERT(vstr_export_chr(s2, srch) == '>');
516 
517     vstr_del(s2, 1, srch);
518   }
519 
520  ssi_parse_failed:
521 
522   if (last && s2->len)
523   {
524     ret = TRUE;
525     vstr_mov(s1, s1->len, s2, 1, s2->len);
526   }
527 
528   return (ret);
529 }
530 
531 
ex_ssi_process_limit(Vstr_base * s1,Vstr_base * s2,time_t last_modified,unsigned int lim)532 static void ex_ssi_process_limit(Vstr_base *s1, Vstr_base *s2,
533                                  time_t last_modified, unsigned int lim)
534 {
535   while (s2->len > lim)
536   {
537     int proc_data = ex_ssi_process(s1, s2, last_modified, !lim);
538     if (!proc_data && (io_put(s1, STDOUT_FILENO) == IO_BLOCK))
539       io_block(-1, STDOUT_FILENO);
540   }
541 }
542 
ex_ssi_read_fd_write_stdout(Vstr_base * s1,Vstr_base * s2,int fd)543 static void ex_ssi_read_fd_write_stdout(Vstr_base *s1, Vstr_base *s2, int fd)
544 {
545   struct stat64 sbuf[1];
546   time_t last_modified = time(NULL);
547 
548   if (fstat64(fd, sbuf))
549     warn("fstat");
550   else
551     last_modified = sbuf->st_mtime;
552 
553   while (TRUE)
554   {
555     int io_w_state = IO_OK;
556     int io_r_state = io_get(s2, fd);
557 
558     if (io_r_state == IO_EOF)
559       break;
560 
561     ex_ssi_process(s1, s2, last_modified, FALSE);
562 
563     io_w_state = io_put(s1, 1);
564 
565     io_limit(io_r_state, fd, io_w_state, 1, s1);
566   }
567 
568   ex_ssi_process_limit(s1, s2, last_modified, 0);
569 }
570 
ex_ssi_fin(Vstr_base * s1,time_t timestamp,const char * fname)571 static void ex_ssi_fin(Vstr_base *s1, time_t timestamp, const char *fname)
572 {
573   free(timespec);
574   timespec = NULL;
575 
576   vstr_add_fmt(s1, s1->len,
577                "<!-- SSI processing of %s -->\n"
578                "<!--   done on %s -->\n"
579                "<!--   done by jssi -->\n",
580                fname, ex_ssi_strftime(timestamp, FALSE));
581 }
582 
usage(const char * program_name,int ret,const char * prefix)583 static void usage(const char *program_name, int ret, const char *prefix)
584 {
585   Vstr_base *out = vstr_make_base(NULL);
586 
587   if (!out)
588     errno = ENOMEM, err(EXIT_FAILURE, "usage");
589 
590   vstr_add_fmt(out, 0, "%s\n"
591           " Format: %s [-hV]\n"
592           " --help -h         - Print this message.\n"
593           " --prefix-path     - Prefix path with argument.\n"
594           " --suffix-path     - Suffix path with argument.\n"
595           " --version -V      - Print the version string.\n",
596           prefix, program_name);
597 
598   if (io_put_all(out, ret ? STDERR_FILENO : STDOUT_FILENO) == IO_FAIL)
599     err(EXIT_FAILURE, "write");
600 
601   exit (ret);
602 }
603 
merge_path(const char * beg,const char * end,const char * name)604 static void merge_path(const char *beg, const char *end, const char *name)
605 {
606   Vstr_base *tmp = vstr_dup_cstr_ptr(NULL, beg);
607 
608   if (!tmp)
609     errno = ENOMEM, err(EXIT_FAILURE, "%s", name);
610 
611   vstr_add_cstr_ptr(tmp, tmp->len, ":");
612   vstr_add_cstr_ptr(tmp, tmp->len, end);
613 
614   if (tmp->conf->malloc_bad || !vstr_export_cstr_ptr(tmp, 1, tmp->len))
615     errno = ENOMEM, err(EXIT_FAILURE, "%s", name);
616 
617   setenv("PATH", vstr_export_cstr_ptr(tmp, 1, tmp->len), TRUE);
618   vstr_free_base(tmp);
619 }
620 
cl_cmd_line(int * passed_argc,char *** passed_argv)621 static void cl_cmd_line(int *passed_argc, char ***passed_argv)
622 {
623   int    argc = *passed_argc;
624   char **argv = *passed_argv;
625 
626   char optchar = 0;
627   const char *program_name = NULL;
628   struct option long_options[] =
629   {
630    {"help", no_argument, NULL, 'h'},
631    {"prefix-path", required_argument, NULL, 1},
632    {"suffix-path", required_argument, NULL, 2},
633    {"version", no_argument, NULL, 'V'},
634    {NULL, 0, NULL, 0}
635   };
636   Vstr_base *out = vstr_make_base(NULL);
637 
638   if (!out)
639     errno = ENOMEM, err(EXIT_FAILURE, "command line");
640 
641   program_name = opt_program_name(argv[0], "jssi");
642 
643   while ((optchar = getopt_long(argc, argv, "hV", long_options, NULL)) != -1)
644   {
645     switch (optchar)
646     {
647       case '?': usage(program_name, EXIT_FAILURE, "");
648       case 'h': usage(program_name, EXIT_SUCCESS, "");
649 
650       case 'V':
651           vstr_add_fmt(out, 0,"\
652 %s version 1.0.0, compiled on %s.\n\
653 Written by James Antill\n\
654 \n\
655 Uses Vstr string library.\n\
656 ",
657                        program_name, __DATE__);
658 
659         if (io_put_all(out, STDOUT_FILENO) == IO_FAIL)
660           err(EXIT_FAILURE, "write");
661 
662         exit (EXIT_SUCCESS);
663 
664       case 1:
665         merge_path(optarg, getenv("PATH"), "prefix-path");
666         break;
667 
668       case 2:
669         merge_path(getenv("PATH"), optarg, "suffix-path");
670         break;
671 
672       default:
673         abort();
674     }
675   }
676   vstr_free_base(out); out = NULL;
677 
678   argc -= optind;
679   argv += optind;
680 
681   *passed_argc = argc;
682   *passed_argv = argv;
683 }
684 
main(int argc,char * argv[])685 int main(int argc, char *argv[])
686 {
687   Vstr_base *s2 = NULL;
688   Vstr_base *s1 = ex_init(&s2);
689   int count = 0; /* getopt reduces it by one */
690   time_t now = time(NULL);
691   int beg_dir = -1;
692   DIR *beg_dir_obj = NULL;
693 
694   cl_cmd_line(&argc, &argv);
695 
696   vstr_sc_fmt_add_all(s1->conf);
697   vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
698 
699   if (count >= argc)
700   {
701     io_fd_set_o_nonblock(STDIN_FILENO);
702     ex_ssi_read_fd_write_stdout(s1, s2, STDIN_FILENO);
703     ex_ssi_fin(s1, now, "stdin");
704   }
705 
706   if (!(beg_dir_obj = opendir(".")))
707     err(EXIT_FAILURE, "opendir(.)");
708   beg_dir = dirfd(beg_dir_obj);
709 
710   while (count < argc)
711   {
712     int fd = io_open(argv[count]);
713     size_t len = strlen(argv[count]);
714     size_t dbeg = 0;
715     size_t tdname_len = 0;
716 
717     if (!vstr_add_buf(s1, s1->len, argv[count], len))
718       errno = ENOMEM, err(EXIT_FAILURE, "add data");
719 
720     if (fchdir(beg_dir) == -1)
721       err(EXIT_FAILURE, "fchdir()");
722 
723     dbeg = s1->len - len + 1;
724     vstr_sc_dirname(s1, dbeg, len, &tdname_len);
725     if (tdname_len)
726     {
727       const char *tmp = vstr_export_cstr_ptr(s1, dbeg, tdname_len);
728 
729       if (chdir(tmp) == -1)
730         err(EXIT_FAILURE, "chdir(%s)", tmp);
731     }
732     vstr_del(s1, s1->len - len + 1, len);
733 
734     ex_ssi_read_fd_write_stdout(s1, s2, fd);
735     ex_ssi_fin(s1, now, argv[count]);
736 
737     if (close(fd) == -1)
738       warn("close(%s)", argv[count]);
739 
740     ++count;
741   }
742   closedir(beg_dir_obj);
743 
744   io_put_all(s1, STDOUT_FILENO);
745 
746   exit (ex_exit(s1, s2));
747 }
748