2006-06-08 Marcus Brinkmann <marcus@g10code.de>
[gnupg.git] / tests / asschk.c
1 /* asschk.c - Assuan Server Checker
2  *      Copyright (C) 2002 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 /* This is a simple stand-alone Assuan server test program.  We don't
22    want to use the assuan library because we don't want to hide errors
23    in that library. 
24
25    The script language is line based.  Empty lines or lines containing
26    only white spaces are ignored, line with a hash sign as first non
27    white space character are treated as comments.
28    
29    A simple macro mechanism is implemnted.  Macros are expanded before
30    a line is processed but after comment processing.  Macros are only
31    expanded once and non existing macros expand to the empty string.
32    A macro is dereferenced by prefixing its name with a dollar sign;
33    the end of the name is currently indicated by a white space, a
34    dollar sign or a slash.  To use a dollor sign verbatim, double it.
35
36    A macro is assigned by prefixing a statement with the macro name
37    and an equal sign.  The value is assigned verbatim if it does not
38    resemble a command, otherwise the return value of the command will
39    get assigned.  The command "let" may be used to assign values
40    unambigiously and it should be used if the value starts with a
41    letter.
42
43    Conditions are not yes implemented except for a simple evaluation
44    which yields false for an empty string or the string "0".  The
45    result may be negated by prefixing with a '!'.
46
47    The general syntax of a command is:
48
49    [<name> =] <statement> [<args>]
50
51    If NAME is not specifed but the statement returns a value it is
52    assigned to the name "?" so that it can be referenced using "$?".
53    The following commands are implemented:
54
55    let <value>
56       Return VALUE.
57
58    echo <value>
59       Print VALUE.
60
61    openfile <filename>
62       Open file FILENAME for read access and return the file descriptor.
63
64    createfile <filename>
65       Create file FILENAME, open for write access and return the file
66       descriptor.
67
68    pipeserver <program>
69       Connect to the Assuan server PROGRAM.
70
71    send <line>
72       Send LINE to the server.
73
74    expect-ok
75       Expect an OK response from the server.  Status and data out put
76       is ignored.
77
78    expect-err
79       Expect an ERR response from the server.  Status and data out put
80       is ignored.
81
82    count-status <code>
83       Initialize the assigned variable to 0 and assign it as an counter for 
84       status code CODE.  This command must be called with an assignment.
85
86    quit
87       Terminate the process.
88
89    quit-if <condition>
90       Terminate the process if CONDITION evaluates to true.
91
92    fail-if <condition>
93       Terminate the process with an exit code of 1 if CONDITION
94       evaluates to true.
95
96    cmpfiles <first> <second>
97       Returns true when the content of the files FIRST and SECOND match.
98
99    getenv <name>
100       Return the value of the environment variable NAME.
101
102 */
103
104 #include <stdio.h>
105 #include <stdlib.h>
106 #include <string.h>
107 #include <errno.h>
108 #include <stdarg.h>
109 #include <assert.h>
110 #include <unistd.h>
111 #include <fcntl.h>
112
113 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
114 # define ATTR_PRINTF(f,a)  __attribute__ ((format (printf,f,a)))
115 #else
116 # define ATTR_PRINTF(f,a)
117 #endif
118
119 #if __STDC_VERSION__ < 199901L
120 # if __GNUC__ >= 2
121 #  define __func__ __FUNCTION__
122 # else
123 /* Let's try our luck here.  Some systems may provide __func__ without
124    providing __STDC_VERSION__ 199901L.  */
125 #  if 0
126 #   define __func__ "<unknown>"
127 #  endif
128 # endif
129 #endif
130
131 #define spacep(p) (*(p) == ' ' || *(p) == '\t')
132
133 #define MAX_LINELEN 2048
134
135 typedef enum {
136   LINE_OK = 0,
137   LINE_ERR,
138   LINE_STAT,
139   LINE_DATA,
140   LINE_END,
141 } LINETYPE;
142
143 typedef enum {
144   VARTYPE_SIMPLE = 0,
145   VARTYPE_FD,
146   VARTYPE_COUNTER
147 } VARTYPE;
148
149
150 struct variable_s {
151   struct variable_s *next;
152   VARTYPE type;
153   unsigned int count;
154   char *value;
155   char name[1];
156 };
157 typedef struct variable_s *VARIABLE;
158
159
160 static void die (const char *format, ...)  ATTR_PRINTF(1,2);
161
162
163 /* Name of this program to be printed in error messages. */
164 static const char *invocation_name;
165
166 /* Talk a bit about what is going on. */
167 static int opt_verbose;
168
169 /* Option to ignore the echo command. */
170 static int opt_no_echo;
171
172 /* File descriptors used to communicate with the current server. */
173 static int server_send_fd = -1;
174 static int server_recv_fd = -1;
175
176 /* The Assuan protocol limits the line length to 1024, so we can
177    safely use a (larger) buffer.  The buffer is filled using the
178    read_assuan(). */
179 static char recv_line[MAX_LINELEN];
180 /* Tell the status of the current line. */
181 static LINETYPE recv_type;
182
183 /* This is our variable storage. */
184 static VARIABLE variable_list;
185
186 \f
187 static void
188 die (const char *format, ...)
189 {
190   va_list arg_ptr;
191
192   fflush (stdout);
193   fprintf (stderr, "%s: ", invocation_name);
194
195   va_start (arg_ptr, format);
196   vfprintf (stderr, format, arg_ptr);
197   va_end (arg_ptr);
198   putc ('\n', stderr);
199
200   exit (1);
201 }
202
203 #define die(format, args...) (die) ("%s: " format, __func__ , ##args)
204
205 static void
206 err (const char *format, ...)
207 {
208   va_list arg_ptr;
209
210   fflush (stdout);
211   fprintf (stderr, "%s: ", invocation_name);
212
213   va_start (arg_ptr, format);
214   vfprintf (stderr, format, arg_ptr);
215   va_end (arg_ptr);
216   putc ('\n', stderr);
217 }
218
219 static void *
220 xmalloc (size_t n)
221 {
222   void *p = malloc (n);
223   if (!p)
224     die ("out of core");
225   return p;
226 }
227
228 static void *
229 xcalloc (size_t n, size_t m)
230 {
231   void *p = calloc (n, m);
232   if (!p)
233     die ("out of core");
234   return p;
235 }
236
237 static char *
238 xstrdup (const char *s)
239 {
240   char *p = xmalloc (strlen (s)+1);
241   strcpy (p, s);
242   return p;
243 }
244
245
246 /* Write LENGTH bytes from BUFFER to FD. */
247 static int
248 writen (int fd, const char *buffer, size_t length)
249 {
250   while (length)
251     {
252       int nwritten = write (fd, buffer, length);
253       
254       if (nwritten < 0)
255         {
256           if (errno == EINTR)
257             continue;
258           return -1; /* write error */
259         }
260       length -= nwritten;
261       buffer += nwritten;
262     }
263   return 0;  /* okay */
264 }
265
266
267
268 \f
269 /* Assuan specific stuff. */
270
271 /* Read a line from FD, store it in the global recv_line, analyze the
272    type and store that in recv_type.  The function terminates on a
273    communication error.  Returns a pointer into the inputline to the
274    first byte of the arguments.  The parsing is very strict to match
275    excalty what we want to send. */
276 static char *
277 read_assuan (int fd)
278 {
279   static char pending[MAX_LINELEN];
280   static size_t pending_len;
281   size_t nleft = sizeof recv_line;
282   char *buf = recv_line;
283   char *p;
284
285   while (nleft > 0)
286     {
287       int n;
288
289       if (pending_len)
290         {
291           if (pending_len >= nleft)
292             die ("received line too large");
293           memcpy (buf, pending, pending_len);
294           n = pending_len;
295           pending_len = 0;
296         }
297       else
298         n = read (fd, buf, nleft);
299
300       if (opt_verbose)
301         {
302           int i;
303           printf ("%s: read \"", __FUNCTION__);
304           for (i = 0; i < n; i ++)
305             putc (buf[i], stdout);
306           printf ("\"\n");
307         }
308
309       if (n < 0)
310         {
311           if (errno == EINTR)
312             continue;
313           die ("reading fd %d failed: %s", fd, strerror (errno));
314         }
315       else if (!n)
316         die ("received incomplete line on fd %d", fd);
317       p = buf;
318       nleft -= n;
319       buf += n;
320       
321       for (; n && *p != '\n'; n--, p++)
322         ;
323       if (n)
324         {
325           if (n>1)
326             {
327               n--;
328               memcpy (pending, p + 1, n);
329               pending_len = n;
330             }
331           *p = '\0';
332           break;
333         }
334     }
335   if (!nleft)
336     die ("received line too large");
337
338   p = recv_line;
339   if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2]))
340     {
341       recv_type = LINE_OK;
342       p += 3;
343     }
344   else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R'
345            && (p[3] == ' ' || !p[3]))
346     {
347       recv_type = LINE_ERR;
348       p += 4;
349     }
350   else if (p[0] == 'S' && (p[1] == ' ' || !p[1]))
351     {
352       recv_type = LINE_STAT;
353       p += 2;
354     }
355   else if (p[0] == 'D' && p[1] == ' ')
356     {
357       recv_type = LINE_DATA;
358       p += 2;
359     }
360   else if (p[0] == 'E' && p[1] == 'N' &&  p[2] == 'D' && !p[3])
361     {
362       recv_type = LINE_END;
363       p += 3;
364     }
365   else 
366     die ("invalid line type (%.5s)", p);
367
368   return p;
369 }
370
371 /* Write LINE to the server using FD.  It is expected that the line
372    contains the terminating linefeed as last character. */
373 static void
374 write_assuan (int fd, const char *line)
375 {
376   char buffer[1026];
377   size_t n = strlen (line);
378
379   if (n > 1024)
380     die ("line too long for Assuan protocol");
381   strcpy (buffer, line);
382   if (!n || buffer[n-1] != '\n')
383     buffer[n++] = '\n';
384
385   if (writen (fd, buffer, n))
386       die ("sending line (\"%s\") to %d failed: %s", buffer, fd,
387            strerror (errno));
388 }
389
390
391 /* Start the server with path PGMNAME and connect its stdout and
392    strerr to a newly created pipes; the file descriptors are then
393    store in the gloabl variables SERVER_SEND_FD and
394    SERVER_RECV_FD. The initial handcheck is performed.*/
395 static void
396 start_server (const char *pgmname)
397 {
398   int rp[2];
399   int wp[2];
400   pid_t pid;
401
402   if (pipe (rp) < 0)
403     die ("pipe creation failed: %s", strerror (errno));
404   if (pipe (wp) < 0)
405     die ("pipe creation failed: %s", strerror (errno));
406
407   fflush (stdout);
408   fflush (stderr);
409   pid = fork ();
410   if (pid < 0)
411     die ("fork failed");
412
413   if (!pid)
414     {
415       const char *arg0;
416
417       arg0 = strrchr (pgmname, '/');
418       if (arg0)
419         arg0++;
420       else
421         arg0 = pgmname;
422
423       if (wp[0] != STDIN_FILENO)
424         {
425           if (dup2 (wp[0], STDIN_FILENO) == -1)
426               die ("dup2 failed in child: %s", strerror (errno));
427           close (wp[0]);
428         }
429       if (rp[1] != STDOUT_FILENO)
430         {
431           if (dup2 (rp[1], STDOUT_FILENO) == -1)
432               die ("dup2 failed in child: %s", strerror (errno));
433           close (rp[1]);
434         }
435       if (!opt_verbose)
436         {
437           int fd = open ("/dev/null", O_WRONLY);
438           if (fd == -1)
439             die ("can't open `/dev/null': %s", strerror (errno));
440           if (dup2 (fd, STDERR_FILENO) == -1)
441             die ("dup2 failed in child: %s", strerror (errno));
442           close (fd);
443         }
444
445       close (wp[1]);
446       close (rp[0]);
447       execl (pgmname, arg0, "--server", NULL); 
448       die ("exec failed for `%s': %s", pgmname, strerror (errno));
449     }
450   close (wp[0]);
451   close (rp[1]);
452   server_send_fd = wp[1];
453   server_recv_fd = rp[0];
454
455   read_assuan (server_recv_fd);
456   if (recv_type != LINE_OK)
457     die ("no greating message");
458 }
459
460
461
462
463 \f
464 /* Script intepreter. */
465
466 static void
467 unset_var (const char *name)
468 {
469   VARIABLE var;
470
471   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
472     ;
473   if (!var)
474     return;
475 /*    fprintf (stderr, "unsetting `%s'\n", name); */
476
477   if (var->type == VARTYPE_FD && var->value)
478     {
479       int fd;
480
481       fd = atoi (var->value);
482       if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
483           close (fd);
484     }
485
486   free (var->value);
487   var->value = NULL;
488   var->type = 0;
489   var->count = 0;
490 }
491
492
493 static void
494 set_type_var (const char *name, const char *value, VARTYPE type)
495 {
496   VARIABLE var;
497
498   if (!name)
499     name = "?"; 
500   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
501     ;
502   if (!var)
503     {
504       var = xcalloc (1, sizeof *var + strlen (name));
505       strcpy (var->name, name);
506       var->next = variable_list;
507       variable_list = var;
508     }
509   else
510     free (var->value);
511
512   if (var->type == VARTYPE_FD && var->value)
513     {
514       int fd;
515
516       fd = atoi (var->value);
517       if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
518           close (fd);
519     }
520   
521   var->type = type;
522   var->count = 0;
523   if (var->type == VARTYPE_COUNTER)
524     {
525       /* We need some extra sapce as scratch area for get_var. */
526       var->value = xmalloc (strlen (value) + 1 + 20);
527       strcpy (var->value, value);
528     }
529   else
530     var->value = xstrdup (value);
531 }
532
533 static void
534 set_var (const char *name, const char *value)
535 {
536   set_type_var (name, value, 0);
537 }
538
539
540 static const char *
541 get_var (const char *name)
542 {
543   VARIABLE var;
544
545   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
546     ;
547   if (!var)
548     return NULL;
549   if (var->type == VARTYPE_COUNTER && var->value)
550     { /* Use the scratch space allocated by set_var. */
551       char *p = var->value + strlen(var->value)+1;
552       sprintf (p, "%u", var->count);
553       return p;
554     }
555   else
556     return var->value;
557 }
558
559
560 /* Incremente all counter type variables with NAME in their VALUE. */
561 static void
562 inc_counter (const char *name)
563 {
564   VARIABLE var;
565
566   if (!*name)
567     return;
568   for (var=variable_list; var; var = var->next)
569     {
570       if (var->type == VARTYPE_COUNTER
571           && var->value && !strcmp (var->value, name))
572         var->count++;
573     }
574 }
575
576
577 /* Expand variables in LINE and return a new allocated buffer if
578    required.  The function might modify LINE if the expanded version
579    fits into it. */
580 static char *
581 expand_line (char *buffer)
582 {
583   char *line = buffer;
584   char *p, *pend;
585   const char *value;
586   size_t valuelen, n;
587   char *result = NULL;
588
589   while (*line)
590     {
591       p = strchr (line, '$');
592       if (!p)
593         return result; /* nothing more to expand */
594       
595       if (p[1] == '$') /* quoted */
596         {
597           memmove (p, p+1, strlen (p+1)+1);
598           line = p + 1;
599           continue;
600         }
601       for (pend=p+1; *pend && !spacep (pend)
602            && *pend != '$' && *pend != '/'; pend++)
603         ;
604       if (*pend)
605         {
606           int save = *pend;
607           *pend = 0;
608           value = get_var (p+1);
609           *pend = save;
610         }
611       else
612         value = get_var (p+1);
613       if (!value)
614         value = "";
615       valuelen = strlen (value);
616       if (valuelen <= pend - p)
617         {
618           memcpy (p, value, valuelen);
619           p += valuelen;
620           n = pend - p;
621           if (n)
622             memmove (p, p+n, strlen (p+n)+1);
623           line = p;
624         }
625       else
626         {
627           char *src = result? result : buffer;
628           char *dst;
629
630           dst = xmalloc (strlen (src) + valuelen + 1);
631           n = p - src;
632           memcpy (dst, src, n);
633           memcpy (dst + n, value, valuelen);
634           n += valuelen;
635           strcpy (dst + n, pend);
636           line = dst + n;
637           free (result);
638           result = dst;
639         }
640     }
641   return result;
642 }
643
644
645 /* Evaluate COND and return the result. */
646 static int 
647 eval_boolean (const char *cond)
648 {
649   int true = 1;
650
651   for ( ; *cond == '!'; cond++)
652     true = !true;
653   if (!*cond || (*cond == '0' && !cond[1]))
654     return !true;
655   return true;
656 }
657
658
659
660
661 \f
662 static void
663 cmd_let (const char *assign_to, char *arg)
664 {
665   set_var (assign_to, arg);
666 }
667
668
669 static void
670 cmd_echo (const char *assign_to, char *arg)
671 {
672   if (!opt_no_echo)
673     printf ("%s\n", arg);
674 }
675
676 static void
677 cmd_send (const char *assign_to, char *arg)
678 {
679   if (opt_verbose)
680     fprintf (stderr, "sending `%s'\n", arg);
681   write_assuan (server_send_fd, arg); 
682 }
683
684 static void
685 handle_status_line (char *arg)
686 {
687   char *p;
688
689   for (p=arg; *p && !spacep (p); p++)
690     ;
691   if (*p)
692     {
693       int save = *p;
694       *p = 0;
695       inc_counter (arg);
696       *p = save;
697     }
698   else
699     inc_counter (arg);
700 }
701
702 static void
703 cmd_expect_ok (const char *assign_to, char *arg)
704 {
705   if (opt_verbose)
706     fprintf (stderr, "expecting OK\n");
707   do
708     {
709       char *p = read_assuan (server_recv_fd);
710       if (opt_verbose > 1)
711         fprintf (stderr, "got line `%s'\n", recv_line);
712       if (recv_type == LINE_STAT)
713         handle_status_line (p);
714     }
715   while (recv_type != LINE_OK && recv_type != LINE_ERR);
716   if (recv_type != LINE_OK)
717     die ("expected OK but got `%s'", recv_line);
718 }
719
720 static void
721 cmd_expect_err (const char *assign_to, char *arg)
722 {
723   if (opt_verbose)
724     fprintf (stderr, "expecting ERR\n");
725   do
726     {
727       char *p = read_assuan (server_recv_fd);
728       if (opt_verbose > 1)
729         fprintf (stderr, "got line `%s'\n", recv_line);
730       if (recv_type == LINE_STAT)
731         handle_status_line (p);
732     }
733   while (recv_type != LINE_OK && recv_type != LINE_ERR);
734   if (recv_type != LINE_ERR)
735     die ("expected ERR but got `%s'", recv_line);
736 }
737
738 static void
739 cmd_count_status (const char *assign_to, char *arg)
740 {
741   char *p;
742
743   if (!*assign_to || !*arg)
744     die ("syntax error: count-status requires an argument and a variable");
745
746   for (p=arg; *p && !spacep (p); p++)
747     ;
748   if (*p)
749     {
750       for (*p++ = 0; spacep (p); p++)
751         ;
752       if (*p)
753         die ("cmpfiles: syntax error");
754     }
755   set_type_var (assign_to, arg, VARTYPE_COUNTER);
756 }
757
758 static void
759 cmd_openfile (const char *assign_to, char *arg)
760 {
761   int fd;
762   char numbuf[20];
763
764   do 
765     fd = open (arg, O_RDONLY);
766   while (fd == -1 && errno == EINTR);
767   if (fd == -1)
768     die ("error opening `%s': %s", arg, strerror (errno));
769   
770   sprintf (numbuf, "%d", fd);
771   set_type_var (assign_to, numbuf, VARTYPE_FD);
772 }
773
774 static void
775 cmd_createfile (const char *assign_to, char *arg)
776 {
777   int fd;
778   char numbuf[20];
779
780   do 
781     fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
782   while (fd == -1 && errno == EINTR);
783   if (fd == -1)
784     die ("error creating `%s': %s", arg, strerror (errno));
785
786   sprintf (numbuf, "%d", fd);
787   set_type_var (assign_to, numbuf, VARTYPE_FD);
788 }
789
790
791 static void
792 cmd_pipeserver (const char *assign_to, char *arg)
793 {
794   if (!*arg)
795     die ("syntax error: servername missing");
796
797   start_server (arg);
798 }
799
800
801 static void
802 cmd_quit_if(const char *assign_to, char *arg)
803 {
804   if (eval_boolean (arg))
805     exit (0);
806 }
807
808 static void
809 cmd_fail_if(const char *assign_to, char *arg)
810 {
811   if (eval_boolean (arg))
812     exit (1);
813 }
814
815
816 static void
817 cmd_cmpfiles (const char *assign_to, char *arg)
818 {
819   char *p = arg;
820   char *second;
821   FILE *fp1, *fp2;
822   char buffer1[2048]; /* note: both must be of equal size. */
823   char buffer2[2048];
824   size_t nread1, nread2;
825   int rc = 0;
826
827   set_var (assign_to, "0"); 
828   for (p=arg; *p && !spacep (p); p++)
829     ;
830   if (!*p)
831     die ("cmpfiles: syntax error");
832   for (*p++ = 0; spacep (p); p++)
833     ;
834   second = p;
835   for (; *p && !spacep (p); p++)
836     ;
837   if (*p)
838     {
839       for (*p++ = 0; spacep (p); p++)
840         ;
841       if (*p)
842         die ("cmpfiles: syntax error");
843     }
844   
845   fp1 = fopen (arg, "rb");
846   if (!fp1)
847     {
848       err ("can't open `%s': %s", arg, strerror (errno));
849       return;
850     }
851   fp2 = fopen (second, "rb");
852   if (!fp2)
853     {
854       err ("can't open `%s': %s", second, strerror (errno));
855       fclose (fp1);
856       return;
857     }
858   while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1)))
859     {
860       if (ferror (fp1))
861         break;
862       nread2 = fread (buffer2, 1, sizeof buffer2, fp2);
863       if (ferror (fp2))
864         break;
865       if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1))
866         {
867           rc = 1;
868           break;
869         }
870     }
871   if (feof (fp1) && feof (fp2) && !rc)
872     {
873       if (opt_verbose)
874         err ("files match");
875       set_var (assign_to, "1"); 
876     }
877   else if (!rc)
878     err ("cmpfiles: read error: %s", strerror (errno));
879   else
880     err ("cmpfiles: mismatch");
881   fclose (fp1);
882   fclose (fp2);
883 }
884
885 static void
886 cmd_getenv (const char *assign_to, char *arg)
887 {
888   const char *s;
889   s = *arg? getenv (arg):"";
890   set_var (assign_to, s? s:"");
891 }
892
893
894
895 \f
896 /* Process the current script line LINE. */
897 static int
898 interpreter (char *line)
899 {
900   static struct {
901     const char *name;
902     void (*fnc)(const char*, char*);
903   } cmdtbl[] = {
904     { "let"       , cmd_let },
905     { "echo"      , cmd_echo },
906     { "send"      , cmd_send },
907     { "expect-ok" , cmd_expect_ok },
908     { "expect-err", cmd_expect_err },
909     { "count-status", cmd_count_status },
910     { "openfile"  , cmd_openfile },
911     { "createfile", cmd_createfile },
912     { "pipeserver", cmd_pipeserver },
913     { "quit"      , NULL },
914     { "quit-if"   , cmd_quit_if },
915     { "fail-if"   , cmd_fail_if },
916     { "cmpfiles"  , cmd_cmpfiles },
917     { "getenv"    , cmd_getenv },
918     { NULL }
919   };
920   char *p, *save_p;
921   int i, save_c;
922   char *stmt = NULL;
923   char *assign_to = NULL;
924   char *must_free = NULL;
925
926   for ( ;spacep (line); line++)
927     ;
928   if (!*line || *line == '#')
929     return 0; /* empty or comment */
930   p = expand_line (line);
931   if (p)
932     {
933       must_free = p;
934       line = p;
935       for ( ;spacep (line); line++)
936         ;
937       if (!*line || *line == '#')
938         {
939           free (must_free);
940           return 0; /* empty or comment */
941         }
942     }
943   for (p=line; *p && !spacep (p) && *p != '='; p++)
944     ;
945   if (*p == '=')
946     {
947       *p = 0;
948       assign_to = line;
949     }
950   else if (*p)
951     {
952       for (*p++ = 0; spacep (p); p++)
953         ;
954       if (*p == '=')
955         assign_to = line;
956     }
957   if (!*line)
958     die ("syntax error");
959   stmt = line;
960   save_c = 0;
961   save_p = NULL;
962   if (assign_to)
963     { /* this is an assignment */
964       for (p++; spacep (p); p++)
965         ;
966       if (!*p)
967         {
968           unset_var (assign_to);
969           free (must_free);
970           return 0;
971         }
972       stmt = p;
973       for (; *p && !spacep (p); p++)
974         ;
975       if (*p)
976         {
977           save_p = p;
978           save_c = *p;
979           for (*p++ = 0; spacep (p);  p++)
980             ;
981         }
982     }
983   for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
984     ;
985   if (!cmdtbl[i].name)
986     {
987       if (!assign_to)
988         die ("invalid statement `%s'\n", stmt);
989       if (save_p)
990         *save_p = save_c;
991       set_var (assign_to, stmt);
992       free (must_free);
993       return 0;
994     }
995
996   if (cmdtbl[i].fnc)
997     cmdtbl[i].fnc (assign_to, p);
998   free (must_free);
999   return cmdtbl[i].fnc? 0:1;
1000 }
1001
1002
1003
1004 int
1005 main (int argc, char **argv)
1006 {
1007   char buffer[2048];
1008   char *p, *pend;
1009
1010   if (!argc)
1011     invocation_name = "asschk";
1012   else
1013     {
1014       invocation_name = *argv++;
1015       argc--;
1016       p = strrchr (invocation_name, '/');
1017       if (p)
1018         invocation_name = p+1;
1019     }
1020
1021
1022   set_var ("?","1"); /* defaults to true */
1023
1024   for (; argc; argc--, argv++)
1025     {
1026       p = *argv;
1027       if (*p != '-')
1028         break;
1029       if (!strcmp (p, "--verbose"))
1030         opt_verbose++;
1031       else if (!strcmp (p, "--no-echo"))
1032         opt_no_echo++;
1033       else if (*p == '-' && p[1] == 'D')
1034         {
1035           p += 2;
1036           pend = strchr (p, '=');
1037           if (pend)
1038             {
1039               int tmp = *pend;
1040               *pend = 0;
1041               set_var (p, pend+1);
1042               *pend = tmp;
1043             }
1044           else
1045             set_var (p, "1");
1046         }
1047       else if (*p == '-' && p[1] == '-' && !p[2])
1048         {
1049           argc--; argv++;
1050           break;
1051         }
1052       else
1053         break;
1054     }
1055   if (argc)
1056     die ("usage: asschk [--verbose] {-D<name>[=<value>]}");
1057
1058
1059   while (fgets (buffer, sizeof buffer, stdin))
1060     {
1061       p = strchr (buffer,'\n');
1062       if (!p)
1063         die ("incomplete script line");
1064       *p = 0;
1065       if (interpreter (buffer))
1066         break;
1067       fflush (stdout);
1068     }
1069   return 0;
1070 }
1071