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