chiark / gitweb /
gnupg2 (2.1.17-3) unstable; urgency=medium
[gnupg2.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 <https://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     {
518       free (var->value);
519       var->value = NULL;
520     }
521
522   if (var->type == VARTYPE_FD && var->value)
523     {
524       int fd;
525
526       fd = atoi (var->value);
527       if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
528           close (fd);
529     }
530
531   var->type = type;
532   var->count = 0;
533   if (var->type == VARTYPE_COUNTER)
534     {
535       /* We need some extra sapce as scratch area for get_var. */
536       var->value = xmalloc (strlen (value) + 1 + 20);
537       strcpy (var->value, value);
538     }
539   else
540     var->value = xstrdup (value);
541 }
542
543 static void
544 set_var (const char *name, const char *value)
545 {
546   set_type_var (name, value, 0);
547 }
548
549
550 static const char *
551 get_var (const char *name)
552 {
553   VARIABLE var;
554
555   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
556     ;
557   if (!var)
558     return NULL;
559   if (var->type == VARTYPE_COUNTER && var->value)
560     { /* Use the scratch space allocated by set_var. */
561       char *p = var->value + strlen(var->value)+1;
562       sprintf (p, "%u", var->count);
563       return p;
564     }
565   else
566     return var->value;
567 }
568
569
570 /* Incremente all counter type variables with NAME in their VALUE. */
571 static void
572 inc_counter (const char *name)
573 {
574   VARIABLE var;
575
576   if (!*name)
577     return;
578   for (var=variable_list; var; var = var->next)
579     {
580       if (var->type == VARTYPE_COUNTER
581           && var->value && !strcmp (var->value, name))
582         var->count++;
583     }
584 }
585
586
587 /* Expand variables in LINE and return a new allocated buffer if
588    required.  The function might modify LINE if the expanded version
589    fits into it. */
590 static char *
591 expand_line (char *buffer)
592 {
593   char *line = buffer;
594   char *p, *pend;
595   const char *value;
596   size_t valuelen, n;
597   char *result = NULL;
598
599   while (*line)
600     {
601       p = strchr (line, '$');
602       if (!p)
603         return result; /* nothing more to expand */
604
605       if (p[1] == '$') /* quoted */
606         {
607           memmove (p, p+1, strlen (p+1)+1);
608           line = p + 1;
609           continue;
610         }
611       for (pend=p+1; *pend && !spacep (pend)
612            && *pend != '$' && *pend != '/'; pend++)
613         ;
614       if (*pend)
615         {
616           int save = *pend;
617           *pend = 0;
618           value = get_var (p+1);
619           *pend = save;
620         }
621       else
622         value = get_var (p+1);
623       if (!value)
624         value = "";
625       valuelen = strlen (value);
626       if (valuelen <= pend - p)
627         {
628           memcpy (p, value, valuelen);
629           p += valuelen;
630           n = pend - p;
631           if (n)
632             memmove (p, p+n, strlen (p+n)+1);
633           line = p;
634         }
635       else
636         {
637           char *src = result? result : buffer;
638           char *dst;
639
640           dst = xmalloc (strlen (src) + valuelen + 1);
641           n = p - src;
642           memcpy (dst, src, n);
643           memcpy (dst + n, value, valuelen);
644           n += valuelen;
645           strcpy (dst + n, pend);
646           line = dst + n;
647           free (result);
648           result = dst;
649         }
650     }
651   return result;
652 }
653
654
655 /* Evaluate COND and return the result. */
656 static int
657 eval_boolean (const char *cond)
658 {
659   int true = 1;
660
661   for ( ; *cond == '!'; cond++)
662     true = !true;
663   if (!*cond || (*cond == '0' && !cond[1]))
664     return !true;
665   return true;
666 }
667
668
669
670
671 \f
672 static void
673 cmd_let (const char *assign_to, char *arg)
674 {
675   set_var (assign_to, arg);
676 }
677
678
679 static void
680 cmd_echo (const char *assign_to, char *arg)
681 {
682   (void)assign_to;
683   if (!opt_no_echo)
684     printf ("%s\n", arg);
685 }
686
687 static void
688 cmd_send (const char *assign_to, char *arg)
689 {
690   (void)assign_to;
691   if (opt_verbose)
692     fprintf (stderr, "sending '%s'\n", arg);
693   write_assuan (server_send_fd, arg);
694 }
695
696 static void
697 handle_status_line (char *arg)
698 {
699   char *p;
700
701   for (p=arg; *p && !spacep (p); p++)
702     ;
703   if (*p)
704     {
705       int save = *p;
706       *p = 0;
707       inc_counter (arg);
708       *p = save;
709     }
710   else
711     inc_counter (arg);
712 }
713
714 static void
715 cmd_expect_ok (const char *assign_to, char *arg)
716 {
717   (void)assign_to;
718   (void)arg;
719
720   if (opt_verbose)
721     fprintf (stderr, "expecting OK\n");
722   do
723     {
724       char *p = read_assuan (server_recv_fd);
725       if (opt_verbose > 1)
726         fprintf (stderr, "got line '%s'\n", recv_line);
727       if (recv_type == LINE_STAT)
728         handle_status_line (p);
729     }
730   while (recv_type != LINE_OK && recv_type != LINE_ERR);
731   if (recv_type != LINE_OK)
732     die_1 ("expected OK but got '%s'", recv_line);
733 }
734
735 static void
736 cmd_expect_err (const char *assign_to, char *arg)
737 {
738   (void)assign_to;
739   (void)arg;
740
741   if (opt_verbose)
742     fprintf (stderr, "expecting ERR\n");
743   do
744     {
745       char *p = read_assuan (server_recv_fd);
746       if (opt_verbose > 1)
747         fprintf (stderr, "got line '%s'\n", recv_line);
748       if (recv_type == LINE_STAT)
749         handle_status_line (p);
750     }
751   while (recv_type != LINE_OK && recv_type != LINE_ERR);
752   if (recv_type != LINE_ERR)
753     die_1 ("expected ERR but got '%s'", recv_line);
754 }
755
756 static void
757 cmd_count_status (const char *assign_to, char *arg)
758 {
759   char *p;
760
761   if (!*assign_to || !*arg)
762     die_0 ("syntax error: count-status requires an argument and a variable");
763
764   for (p=arg; *p && !spacep (p); p++)
765     ;
766   if (*p)
767     {
768       for (*p++ = 0; spacep (p); p++)
769         ;
770       if (*p)
771         die_0 ("cmpfiles: syntax error");
772     }
773   set_type_var (assign_to, arg, VARTYPE_COUNTER);
774 }
775
776 static void
777 cmd_openfile (const char *assign_to, char *arg)
778 {
779   int fd;
780   char numbuf[20];
781
782   do
783     fd = open (arg, O_RDONLY);
784   while (fd == -1 && errno == EINTR);
785   if (fd == -1)
786     die_2 ("error opening '%s': %s", arg, strerror (errno));
787
788   sprintf (numbuf, "%d", fd);
789   set_type_var (assign_to, numbuf, VARTYPE_FD);
790 }
791
792 static void
793 cmd_createfile (const char *assign_to, char *arg)
794 {
795   int fd;
796   char numbuf[20];
797
798   do
799     fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
800   while (fd == -1 && errno == EINTR);
801   if (fd == -1)
802     die_2 ("error creating '%s': %s", arg, strerror (errno));
803
804   sprintf (numbuf, "%d", fd);
805   set_type_var (assign_to, numbuf, VARTYPE_FD);
806 }
807
808
809 static void
810 cmd_pipeserver (const char *assign_to, char *arg)
811 {
812   (void)assign_to;
813
814   if (!*arg)
815     die_0 ("syntax error: servername missing");
816
817   start_server (arg);
818 }
819
820
821 static void
822 cmd_quit_if(const char *assign_to, char *arg)
823 {
824   (void)assign_to;
825
826   if (eval_boolean (arg))
827     exit (0);
828 }
829
830 static void
831 cmd_fail_if(const char *assign_to, char *arg)
832 {
833   (void)assign_to;
834
835   if (eval_boolean (arg))
836     exit (1);
837 }
838
839
840 static void
841 cmd_cmpfiles (const char *assign_to, char *arg)
842 {
843   char *p = arg;
844   char *second;
845   FILE *fp1, *fp2;
846   char buffer1[2048]; /* note: both must be of equal size. */
847   char buffer2[2048];
848   size_t nread1, nread2;
849   int rc = 0;
850
851   set_var (assign_to, "0");
852   for (p=arg; *p && !spacep (p); p++)
853     ;
854   if (!*p)
855     die_0 ("cmpfiles: syntax error");
856   for (*p++ = 0; spacep (p); p++)
857     ;
858   second = p;
859   for (; *p && !spacep (p); p++)
860     ;
861   if (*p)
862     {
863       for (*p++ = 0; spacep (p); p++)
864         ;
865       if (*p)
866         die_0 ("cmpfiles: syntax error");
867     }
868
869   fp1 = fopen (arg, "rb");
870   if (!fp1)
871     {
872       err ("can't open '%s': %s", arg, strerror (errno));
873       return;
874     }
875   fp2 = fopen (second, "rb");
876   if (!fp2)
877     {
878       err ("can't open '%s': %s", second, strerror (errno));
879       fclose (fp1);
880       return;
881     }
882   while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1)))
883     {
884       if (ferror (fp1))
885         break;
886       nread2 = fread (buffer2, 1, sizeof buffer2, fp2);
887       if (ferror (fp2))
888         break;
889       if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1))
890         {
891           rc = 1;
892           break;
893         }
894     }
895   if (feof (fp1) && feof (fp2) && !rc)
896     {
897       if (opt_verbose)
898         err ("files match");
899       set_var (assign_to, "1");
900     }
901   else if (!rc)
902     err ("cmpfiles: read error: %s", strerror (errno));
903   else
904     err ("cmpfiles: mismatch");
905   fclose (fp1);
906   fclose (fp2);
907 }
908
909 static void
910 cmd_getenv (const char *assign_to, char *arg)
911 {
912   const char *s;
913   s = *arg? getenv (arg):"";
914   set_var (assign_to, s? s:"");
915 }
916
917
918
919 \f
920 /* Process the current script line LINE. */
921 static int
922 interpreter (char *line)
923 {
924   static struct {
925     const char *name;
926     void (*fnc)(const char*, char*);
927   } cmdtbl[] = {
928     { "let"       , cmd_let },
929     { "echo"      , cmd_echo },
930     { "send"      , cmd_send },
931     { "expect-ok" , cmd_expect_ok },
932     { "expect-err", cmd_expect_err },
933     { "count-status", cmd_count_status },
934     { "openfile"  , cmd_openfile },
935     { "createfile", cmd_createfile },
936     { "pipeserver", cmd_pipeserver },
937     { "quit"      , NULL },
938     { "quit-if"   , cmd_quit_if },
939     { "fail-if"   , cmd_fail_if },
940     { "cmpfiles"  , cmd_cmpfiles },
941     { "getenv"    , cmd_getenv },
942     { NULL }
943   };
944   char *p, *save_p;
945   int i, save_c;
946   char *stmt = NULL;
947   char *assign_to = NULL;
948   char *must_free = NULL;
949
950   for ( ;spacep (line); line++)
951     ;
952   if (!*line || *line == '#')
953     return 0; /* empty or comment */
954   p = expand_line (line);
955   if (p)
956     {
957       must_free = p;
958       line = p;
959       for ( ;spacep (line); line++)
960         ;
961       if (!*line || *line == '#')
962         {
963           free (must_free);
964           return 0; /* empty or comment */
965         }
966     }
967   for (p=line; *p && !spacep (p) && *p != '='; p++)
968     ;
969   if (*p == '=')
970     {
971       *p = 0;
972       assign_to = line;
973     }
974   else if (*p)
975     {
976       for (*p++ = 0; spacep (p); p++)
977         ;
978       if (*p == '=')
979         assign_to = line;
980     }
981   if (!*line)
982     die_0 ("syntax error");
983   stmt = line;
984   save_c = 0;
985   save_p = NULL;
986   if (assign_to)
987     { /* this is an assignment */
988       for (p++; spacep (p); p++)
989         ;
990       if (!*p)
991         {
992           unset_var (assign_to);
993           free (must_free);
994           return 0;
995         }
996       stmt = p;
997       for (; *p && !spacep (p); p++)
998         ;
999       if (*p)
1000         {
1001           save_p = p;
1002           save_c = *p;
1003           for (*p++ = 0; spacep (p);  p++)
1004             ;
1005         }
1006     }
1007   for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
1008     ;
1009   if (!cmdtbl[i].name)
1010     {
1011       if (!assign_to)
1012         die_1 ("invalid statement '%s'\n", stmt);
1013       if (save_p)
1014         *save_p = save_c;
1015       set_var (assign_to, stmt);
1016       free (must_free);
1017       return 0;
1018     }
1019
1020   if (cmdtbl[i].fnc)
1021     cmdtbl[i].fnc (assign_to, p);
1022   free (must_free);
1023   return cmdtbl[i].fnc? 0:1;
1024 }
1025
1026
1027
1028 int
1029 main (int argc, char **argv)
1030 {
1031   char buffer[2048];
1032   char *p, *pend;
1033
1034   if (!argc)
1035     invocation_name = "asschk";
1036   else
1037     {
1038       invocation_name = *argv++;
1039       argc--;
1040       p = strrchr (invocation_name, '/');
1041       if (p)
1042         invocation_name = p+1;
1043     }
1044
1045
1046   set_var ("?","1"); /* defaults to true */
1047
1048   for (; argc; argc--, argv++)
1049     {
1050       p = *argv;
1051       if (*p != '-')
1052         break;
1053       if (!strcmp (p, "--verbose"))
1054         opt_verbose++;
1055       else if (!strcmp (p, "--no-echo"))
1056         opt_no_echo++;
1057       else if (*p == '-' && p[1] == 'D')
1058         {
1059           p += 2;
1060           pend = strchr (p, '=');
1061           if (pend)
1062             {
1063               int tmp = *pend;
1064               *pend = 0;
1065               set_var (p, pend+1);
1066               *pend = tmp;
1067             }
1068           else
1069             set_var (p, "1");
1070         }
1071       else if (*p == '-' && p[1] == '-' && !p[2])
1072         {
1073           argc--; argv++;
1074           break;
1075         }
1076       else
1077         break;
1078     }
1079   if (argc)
1080     die ("usage: asschk [--verbose] {-D<name>[=<value>]}");
1081
1082
1083   while (fgets (buffer, sizeof buffer, stdin))
1084     {
1085       p = strchr (buffer,'\n');
1086       if (!p)
1087         die_0 ("incomplete script line");
1088       *p = 0;
1089       if (interpreter (buffer))
1090         break;
1091       fflush (stdout);
1092     }
1093   return 0;
1094 }