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