Merge branch 'master' into STABLE-BRANCH-2-2
[gnupg.git] / tests / gpgscm / main.c
1 /* TinyScheme-based test driver.
2  *
3  * Copyright (C) 2016 g10 code GmbH
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include <config.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <gcrypt.h>
27 #include <gpg-error.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "private.h"
34 #include "scheme.h"
35 #include "ffi.h"
36 #include "i18n.h"
37 #include "../../common/argparse.h"
38 #include "../../common/init.h"
39 #include "../../common/logging.h"
40 #include "../../common/strlist.h"
41 #include "../../common/sysutils.h"
42 #include "../../common/util.h"
43
44 /* The TinyScheme banner.  Unfortunately, it isn't in the header
45    file.  */
46 #define ts_banner "TinyScheme 1.41"
47
48 int verbose;
49
50 \f
51
52 /* Constants to identify the commands and options. */
53 enum cmd_and_opt_values
54   {
55     aNull       = 0,
56     oVerbose    = 'v',
57   };
58
59 /* The list of commands and options. */
60 static ARGPARSE_OPTS opts[] =
61   {
62     ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
63     ARGPARSE_end (),
64   };
65
66 char *scmpath = "";
67 size_t scmpath_len = 0;
68
69 /* Command line parsing.  */
70 static void
71 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
72 {
73   int no_more_options = 0;
74
75   while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
76     {
77       switch (pargs->r_opt)
78         {
79         case oVerbose:
80           verbose++;
81           break;
82
83         default:
84           pargs->err = 2;
85           break;
86         }
87     }
88 }
89
90 /* Print usage information and and provide strings for help. */
91 static const char *
92 my_strusage( int level )
93 {
94   const char *p;
95
96   switch (level)
97     {
98     case 11: p = "gpgscm (@GNUPG@)";
99       break;
100     case 13: p = VERSION; break;
101     case 17: p = PRINTABLE_OS_NAME; break;
102     case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
103
104     case 1:
105     case 40:
106       p = _("Usage: gpgscm [options] [file] (-h for help)");
107       break;
108     case 41:
109       p = _("Syntax: gpgscm [options] [file]\n"
110             "Execute the given Scheme program, or spawn interactive shell.\n");
111       break;
112
113     default: p = NULL; break;
114     }
115   return p;
116 }
117
118 \f
119 /* Load the Scheme program from FILE_NAME.  If FILE_NAME is not an
120    absolute path, and LOOKUP_IN_PATH is given, then it is qualified
121    with the values in scmpath until the file is found.  */
122 static gpg_error_t
123 load (scheme *sc, char *file_name,
124       int lookup_in_cwd, int lookup_in_path)
125 {
126   gpg_error_t err = 0;
127   size_t n;
128   const char *directory;
129   char *qualified_name = file_name;
130   int use_path;
131   FILE *h = NULL;
132
133   use_path =
134     lookup_in_path && ! (file_name[0] == '/' || scmpath_len == 0);
135
136   if (file_name[0] == '/' || lookup_in_cwd || scmpath_len == 0)
137     {
138       h = fopen (file_name, "r");
139       if (! h)
140         err = gpg_error_from_syserror ();
141     }
142
143   if (h == NULL && use_path)
144     for (directory = scmpath, n = scmpath_len; n;
145          directory += strlen (directory) + 1, n--)
146       {
147         if (asprintf (&qualified_name, "%s/%s", directory, file_name) < 0)
148           return gpg_error_from_syserror ();
149
150         h = fopen (qualified_name, "r");
151         if (h)
152           break;
153
154         if (n > 1)
155           {
156             free (qualified_name);
157             continue;   /* Try again!  */
158           }
159
160         err = gpg_error_from_syserror ();
161       }
162
163   if (h == NULL)
164     {
165       /* Failed and no more elements in scmpath to try.  */
166       fprintf (stderr, "Could not read %s: %s.\n",
167                qualified_name, gpg_strerror (err));
168       if (lookup_in_path)
169         fprintf (stderr,
170                  "Consider using GPGSCM_PATH to specify the location "
171                  "of the Scheme library.\n");
172       return err;
173     }
174   if (verbose > 1)
175     fprintf (stderr, "Loading %s...\n", qualified_name);
176   scheme_load_named_file (sc, h, qualified_name);
177   fclose (h);
178
179   if (file_name != qualified_name)
180     free (qualified_name);
181   return 0;
182 }
183
184 \f
185
186 int
187 main (int argc, char **argv)
188 {
189   gpg_error_t err;
190   char *argv0;
191   ARGPARSE_ARGS pargs;
192   scheme *sc;
193   char *p;
194 #if _WIN32
195   char pathsep = ';';
196 #else
197   char pathsep = ':';
198 #endif
199   char *script = NULL;
200
201   /* Save argv[0] so that we can re-exec.  */
202   argv0 = argv[0];
203
204   /* Parse path.  */
205   if (getenv ("GPGSCM_PATH"))
206     scmpath = getenv ("GPGSCM_PATH");
207
208   p = scmpath = strdup (scmpath);
209   if (p == NULL)
210     return 2;
211
212   if (*p)
213     scmpath_len++;
214   for (; *p; p++)
215     if (*p == pathsep)
216       *p = 0, scmpath_len++;
217
218   set_strusage (my_strusage);
219   log_set_prefix ("gpgscm", 1);
220
221   /* Make sure that our subsystems are ready.  */
222   i18n_init ();
223   init_common_subsystems (&argc, &argv);
224
225   if (!gcry_check_version (GCRYPT_VERSION))
226     {
227       fputs ("libgcrypt version mismatch\n", stderr);
228       exit (2);
229     }
230
231   /* Parse the command line. */
232   pargs.argc  = &argc;
233   pargs.argv  = &argv;
234   pargs.flags = 0;
235   parse_arguments (&pargs, opts);
236
237   if (log_get_errorcount (0))
238     exit (2);
239
240   sc = scheme_init_new_custom_alloc (gcry_malloc, gcry_free);
241   if (! sc) {
242     fprintf (stderr, "Could not initialize TinyScheme!\n");
243     return 2;
244   }
245   scheme_set_input_port_file (sc, stdin);
246   scheme_set_output_port_file (sc, stderr);
247
248   if (argc)
249     {
250       script = argv[0];
251       argc--, argv++;
252     }
253
254   err = load (sc, "init.scm", 0, 1);
255   if (! err)
256     err = load (sc, "ffi.scm", 0, 1);
257   if (! err)
258     err = ffi_init (sc, argv0, argc, (const char **) argv);
259   if (! err)
260     err = load (sc, "lib.scm", 0, 1);
261   if (! err)
262     err = load (sc, "repl.scm", 0, 1);
263   if (! err)
264     err = load (sc, "tests.scm", 0, 1);
265   if (err)
266     {
267       fprintf (stderr, "Error initializing gpgscm: %s.\n",
268                gpg_strerror (err));
269       exit (2);
270     }
271
272   if (script == NULL)
273     {
274       /* Interactive shell.  */
275       fprintf (stderr, "gpgscm/"ts_banner".\n");
276       scheme_load_string (sc, "(interactive-repl)");
277     }
278   else
279     {
280       err = load (sc, script, 1, 1);
281       if (err)
282         log_fatal ("%s: %s", script, gpg_strerror (err));
283     }
284
285   scheme_deinit (sc);
286   xfree (sc);
287   return EXIT_SUCCESS;
288 }