Add a sample key.
[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 \"", __func__);
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   (void)assign_to;
677   if (!opt_no_echo)
678     printf ("%s\n", arg);
679 }
680
681 static void
682 cmd_send (const char *assign_to, char *arg)
683 {
684   (void)assign_to;
685   if (opt_verbose)
686     fprintf (stderr, "sending `%s'\n", arg);
687   write_assuan (server_send_fd, arg); 
688 }
689
690 static void
691 handle_status_line (char *arg)
692 {
693   char *p;
694
695   for (p=arg; *p && !spacep (p); p++)
696     ;
697   if (*p)
698     {
699       int save = *p;
700       *p = 0;
701       inc_counter (arg);
702       *p = save;
703     }
704   else
705     inc_counter (arg);
706 }
707
708 static void
709 cmd_expect_ok (const char *assign_to, char *arg)
710 {
711   (void)assign_to;
712   (void)arg;
713
714   if (opt_verbose)
715     fprintf (stderr, "expecting OK\n");
716   do
717     {
718       char *p = read_assuan (server_recv_fd);
719       if (opt_verbose > 1)
720         fprintf (stderr, "got line `%s'\n", recv_line);
721       if (recv_type == LINE_STAT)
722         handle_status_line (p);
723     }
724   while (recv_type != LINE_OK && recv_type != LINE_ERR);
725   if (recv_type != LINE_OK)
726     die ("expected OK but got `%s'", recv_line);
727 }
728
729 static void
730 cmd_expect_err (const char *assign_to, char *arg)
731 {
732   (void)assign_to;
733   (void)arg;
734
735   if (opt_verbose)
736     fprintf (stderr, "expecting ERR\n");
737   do
738     {
739       char *p = read_assuan (server_recv_fd);
740       if (opt_verbose > 1)
741         fprintf (stderr, "got line `%s'\n", recv_line);
742       if (recv_type == LINE_STAT)
743         handle_status_line (p);
744     }
745   while (recv_type != LINE_OK && recv_type != LINE_ERR);
746   if (recv_type != LINE_ERR)
747     die ("expected ERR but got `%s'", recv_line);
748 }
749
750 static void
751 cmd_count_status (const char *assign_to, char *arg)
752 {
753   char *p;
754
755   if (!*assign_to || !*arg)
756     die ("syntax error: count-status requires an argument and a variable");
757
758   for (p=arg; *p && !spacep (p); p++)
759     ;
760   if (*p)
761     {
762       for (*p++ = 0; spacep (p); p++)
763         ;
764       if (*p)
765         die ("cmpfiles: syntax error");
766     }
767   set_type_var (assign_to, arg, VARTYPE_COUNTER);
768 }
769
770 static void
771 cmd_openfile (const char *assign_to, char *arg)
772 {
773   int fd;
774   char numbuf[20];
775
776   do 
777     fd = open (arg, O_RDONLY);
778   while (fd == -1 && errno == EINTR);
779   if (fd == -1)
780     die ("error opening `%s': %s", arg, strerror (errno));
781   
782   sprintf (numbuf, "%d", fd);
783   set_type_var (assign_to, numbuf, VARTYPE_FD);
784 }
785
786 static void
787 cmd_createfile (const char *assign_to, char *arg)
788 {
789   int fd;
790   char numbuf[20];
791
792   do 
793     fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
794   while (fd == -1 && errno == EINTR);
795   if (fd == -1)
796     die ("error creating `%s': %s", arg, strerror (errno));
797
798   sprintf (numbuf, "%d", fd);
799   set_type_var (assign_to, numbuf, VARTYPE_FD);
800 }
801
802
803 static void
804 cmd_pipeserver (const char *assign_to, char *arg)
805 {
806   (void)assign_to;
807
808   if (!*arg)
809     die ("syntax error: servername missing");
810
811   start_server (arg);
812 }
813
814
815 static void
816 cmd_quit_if(const char *assign_to, char *arg)
817 {
818   (void)assign_to;
819
820   if (eval_boolean (arg))
821     exit (0);
822 }
823
824 static void
825 cmd_fail_if(const char *assign_to, char *arg)
826 {
827   (void)assign_to;
828
829   if (eval_boolean (arg))
830     exit (1);
831 }
832
833
834 static void
835 cmd_cmpfiles (const char *assign_to, char *arg)
836 {
837   char *p = arg;
838   char *second;
839   FILE *fp1, *fp2;
840   char buffer1[2048]; /* note: both must be of equal size. */
841   char buffer2[2048];
842   size_t nread1, nread2;
843   int rc = 0;
844
845   set_var (assign_to, "0"); 
846   for (p=arg; *p && !spacep (p); p++)
847     ;
848   if (!*p)
849     die ("cmpfiles: syntax error");
850   for (*p++ = 0; spacep (p); p++)
851     ;
852   second = p;
853   for (; *p && !spacep (p); p++)
854     ;
855   if (*p)
856     {
857       for (*p++ = 0; spacep (p); p++)
858         ;
859       if (*p)
860         die ("cmpfiles: syntax error");
861     }
862   
863   fp1 = fopen (arg, "rb");
864   if (!fp1)
865     {
866       err ("can't open `%s': %s", arg, strerror (errno));
867       return;
868     }
869   fp2 = fopen (second, "rb");
870   if (!fp2)
871     {
872       err ("can't open `%s': %s", second, strerror (errno));
873       fclose (fp1);
874       return;
875     }
876   while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1)))
877     {
878       if (ferror (fp1))
879         break;
880       nread2 = fread (buffer2, 1, sizeof buffer2, fp2);
881       if (ferror (fp2))
882         break;
883       if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1))
884         {
885           rc = 1;
886           break;
887         }
888     }
889   if (feof (fp1) && feof (fp2) && !rc)
890     {
891       if (opt_verbose)
892         err ("files match");
893       set_var (assign_to, "1"); 
894     }
895   else if (!rc)
896     err ("cmpfiles: read error: %s", strerror (errno));
897   else
898     err ("cmpfiles: mismatch");
899   fclose (fp1);
900   fclose (fp2);
901 }
902
903 static void
904 cmd_getenv (const char *assign_to, char *arg)
905 {
906   const char *s;
907   s = *arg? getenv (arg):"";
908   set_var (assign_to, s? s:"");
909 }
910
911
912
913 \f
914 /* Process the current script line LINE. */
915 static int
916 interpreter (char *line)
917 {
918   static struct {
919     const char *name;
920     void (*fnc)(const char*, char*);
921   } cmdtbl[] = {
922     { "let"       , cmd_let },
923     { "echo"      , cmd_echo },
924     { "send"      , cmd_send },
925     { "expect-ok" , cmd_expect_ok },
926     { "expect-err", cmd_expect_err },
927     { "count-status", cmd_count_status },
928     { "openfile"  , cmd_openfile },
929     { "createfile", cmd_createfile },
930     { "pipeserver", cmd_pipeserver },
931     { "quit"      , NULL },
932     { "quit-if"   , cmd_quit_if },
933     { "fail-if"   , cmd_fail_if },
934     { "cmpfiles"  , cmd_cmpfiles },
935     { "getenv"    , cmd_getenv },
936     { NULL }
937   };
938   char *p, *save_p;
939   int i, save_c;
940   char *stmt = NULL;
941   char *assign_to = NULL;
942   char *must_free = NULL;
943
944   for ( ;spacep (line); line++)
945     ;
946   if (!*line || *line == '#')
947     return 0; /* empty or comment */
948   p = expand_line (line);
949   if (p)
950     {
951       must_free = p;
952       line = p;
953       for ( ;spacep (line); line++)
954         ;
955       if (!*line || *line == '#')
956         {
957           free (must_free);
958           return 0; /* empty or comment */
959         }
960     }
961   for (p=line; *p && !spacep (p) && *p != '='; p++)
962     ;
963   if (*p == '=')
964     {
965       *p = 0;
966       assign_to = line;
967     }
968   else if (*p)
969     {
970       for (*p++ = 0; spacep (p); p++)
971         ;
972       if (*p == '=')
973         assign_to = line;
974     }
975   if (!*line)
976     die ("syntax error");
977   stmt = line;
978   save_c = 0;
979   save_p = NULL;
980   if (assign_to)
981     { /* this is an assignment */
982       for (p++; spacep (p); p++)
983         ;
984       if (!*p)
985         {
986           unset_var (assign_to);
987           free (must_free);
988           return 0;
989         }
990       stmt = p;
991       for (; *p && !spacep (p); p++)
992         ;
993       if (*p)
994         {
995           save_p = p;
996           save_c = *p;
997           for (*p++ = 0; spacep (p);  p++)
998             ;
999         }
1000     }
1001   for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
1002     ;
1003   if (!cmdtbl[i].name)
1004     {
1005       if (!assign_to)
1006         die ("invalid statement `%s'\n", stmt);
1007       if (save_p)
1008         *save_p = save_c;
1009       set_var (assign_to, stmt);
1010       free (must_free);
1011       return 0;
1012     }
1013
1014   if (cmdtbl[i].fnc)
1015     cmdtbl[i].fnc (assign_to, p);
1016   free (must_free);
1017   return cmdtbl[i].fnc? 0:1;
1018 }
1019
1020
1021
1022 int
1023 main (int argc, char **argv)
1024 {
1025   char buffer[2048];
1026   char *p, *pend;
1027
1028   if (!argc)
1029     invocation_name = "asschk";
1030   else
1031     {
1032       invocation_name = *argv++;
1033       argc--;
1034       p = strrchr (invocation_name, '/');
1035       if (p)
1036         invocation_name = p+1;
1037     }
1038
1039
1040   set_var ("?","1"); /* defaults to true */
1041
1042   for (; argc; argc--, argv++)
1043     {
1044       p = *argv;
1045       if (*p != '-')
1046         break;
1047       if (!strcmp (p, "--verbose"))
1048         opt_verbose++;
1049       else if (!strcmp (p, "--no-echo"))
1050         opt_no_echo++;
1051       else if (*p == '-' && p[1] == 'D')
1052         {
1053           p += 2;
1054           pend = strchr (p, '=');
1055           if (pend)
1056             {
1057               int tmp = *pend;
1058               *pend = 0;
1059               set_var (p, pend+1);
1060               *pend = tmp;
1061             }
1062           else
1063             set_var (p, "1");
1064         }
1065       else if (*p == '-' && p[1] == '-' && !p[2])
1066         {
1067           argc--; argv++;
1068           break;
1069         }
1070       else
1071         break;
1072     }
1073   if (argc)
1074     die ("usage: asschk [--verbose] {-D<name>[=<value>]}");
1075
1076
1077   while (fgets (buffer, sizeof buffer, stdin))
1078     {
1079       p = strchr (buffer,'\n');
1080       if (!p)
1081         die ("incomplete script line");
1082       *p = 0;
1083       if (interpreter (buffer))
1084         break;
1085       fflush (stdout);
1086     }
1087   return 0;
1088 }
1089