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