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