gpg,sm: Implement keybox compression run and release lock in gpgsm
[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 <https://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 <fcntl.h>
27 #include <gcrypt.h>
28 #include <gpg-error.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35
36 #if HAVE_MMAP
37 #include <sys/mman.h>
38 #endif
39
40 #include "private.h"
41 #include "scheme.h"
42 #include "scheme-private.h"
43 #include "ffi.h"
44 #include "../common/i18n.h"
45 #include "../../common/argparse.h"
46 #include "../../common/init.h"
47 #include "../../common/logging.h"
48 #include "../../common/strlist.h"
49 #include "../../common/sysutils.h"
50 #include "../../common/util.h"
51
52 /* The TinyScheme banner.  Unfortunately, it isn't in the header
53    file.  */
54 #define ts_banner "TinyScheme 1.41"
55
56 int verbose;
57
58 \f
59
60 /* Constants to identify the commands and options. */
61 enum cmd_and_opt_values
62   {
63     aNull       = 0,
64     oVerbose    = 'v',
65   };
66
67 /* The list of commands and options. */
68 static ARGPARSE_OPTS opts[] =
69   {
70     ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
71     ARGPARSE_end (),
72   };
73
74 char *scmpath = "";
75 size_t scmpath_len = 0;
76
77 /* Command line parsing.  */
78 static void
79 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
80 {
81   int no_more_options = 0;
82
83   while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
84     {
85       switch (pargs->r_opt)
86         {
87         case oVerbose:
88           verbose++;
89           break;
90
91         default:
92           pargs->err = 2;
93           break;
94         }
95     }
96 }
97
98 /* Print usage information and provide strings for help. */
99 static const char *
100 my_strusage( int level )
101 {
102   const char *p;
103
104   switch (level)
105     {
106     case 11: p = "gpgscm (@GNUPG@)";
107       break;
108     case 13: p = VERSION; break;
109     case 17: p = PRINTABLE_OS_NAME; break;
110     case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
111
112     case 1:
113     case 40:
114       p = _("Usage: gpgscm [options] [file] (-h for help)");
115       break;
116     case 41:
117       p = _("Syntax: gpgscm [options] [file]\n"
118             "Execute the given Scheme program, or spawn interactive shell.\n");
119       break;
120
121     default: p = NULL; break;
122     }
123   return p;
124 }
125
126 \f
127
128 static int
129 path_absolute_p (const char *p)
130 {
131 #if _WIN32
132   return ((strlen (p) > 2 && p[1] == ':' && (p[2] == '\\' || p[2] == '/'))
133           || p[0] == '\\' || p[0] == '/');
134 #else
135   return p[0] == '/';
136 #endif
137 }
138
139
140 /* Load the Scheme program from FILE_NAME.  If FILE_NAME is not an
141    absolute path, and LOOKUP_IN_PATH is given, then it is qualified
142    with the values in scmpath until the file is found.  */
143 static gpg_error_t
144 load (scheme *sc, char *file_name,
145       int lookup_in_cwd, int lookup_in_path)
146 {
147   gpg_error_t err = 0;
148   size_t n;
149   const char *directory;
150   char *qualified_name = file_name;
151   int use_path;
152   FILE *h = NULL;
153
154   use_path =
155     lookup_in_path && ! (path_absolute_p (file_name) || scmpath_len == 0);
156
157   if (path_absolute_p (file_name) || lookup_in_cwd || scmpath_len == 0)
158     {
159       h = fopen (file_name, "r");
160       if (! h)
161         err = gpg_error_from_syserror ();
162     }
163
164   if (h == NULL && use_path)
165     for (directory = scmpath, n = scmpath_len; n;
166          directory += strlen (directory) + 1, n--)
167       {
168         if (asprintf (&qualified_name, "%s/%s", directory, file_name) < 0)
169           return gpg_error_from_syserror ();
170
171         h = fopen (qualified_name, "r");
172         if (h)
173           {
174             err = 0;
175             break;
176           }
177
178         if (n > 1)
179           {
180             free (qualified_name);
181             continue;   /* Try again!  */
182           }
183
184         err = gpg_error_from_syserror ();
185       }
186
187   if (h == NULL)
188     {
189       /* Failed and no more elements in scmpath to try.  */
190       fprintf (stderr, "Could not read %s: %s.\n",
191                qualified_name, gpg_strerror (err));
192       if (lookup_in_path)
193         fprintf (stderr,
194                  "Consider using GPGSCM_PATH to specify the location "
195                  "of the Scheme library.\n");
196       goto leave;
197     }
198   if (verbose > 2)
199     fprintf (stderr, "Loading %s...\n", qualified_name);
200
201 #if HAVE_MMAP
202   /* Always try to mmap the file.  This allows the pages to be shared
203    * between processes.  If anything fails, we fall back to using
204    * buffered streams.  */
205   if (1)
206     {
207       struct stat st;
208       void *map;
209       size_t len;
210       int fd = fileno (h);
211
212       if (fd < 0)
213         goto fallback;
214
215       if (fstat (fd, &st))
216         goto fallback;
217
218       len = (size_t) st.st_size;
219       if ((off_t) len != st.st_size)
220         goto fallback;  /* Truncated.  */
221
222       map = mmap (NULL, len, PROT_READ, MAP_SHARED, fd, 0);
223       if (map == MAP_FAILED)
224         goto fallback;
225
226       scheme_load_memory (sc, map, len, qualified_name);
227       munmap (map, len);
228     }
229   else
230   fallback:
231 #endif
232     scheme_load_named_file (sc, h, qualified_name);
233   fclose (h);
234
235   if (sc->retcode && sc->nesting)
236     {
237       fprintf (stderr, "%s: Unbalanced parenthesis\n", qualified_name);
238       err = gpg_error (GPG_ERR_GENERAL);
239     }
240
241  leave:
242   if (file_name != qualified_name)
243     free (qualified_name);
244   return err;
245 }
246
247 \f
248
249 int
250 main (int argc, char **argv)
251 {
252   int retcode;
253   gpg_error_t err;
254   char *argv0;
255   ARGPARSE_ARGS pargs;
256   scheme *sc;
257   char *p;
258 #if _WIN32
259   char pathsep = ';';
260 #else
261   char pathsep = ':';
262 #endif
263   char *script = NULL;
264
265   /* Save argv[0] so that we can re-exec.  */
266   argv0 = argv[0];
267
268   /* Parse path.  */
269   if (getenv ("GPGSCM_PATH"))
270     scmpath = getenv ("GPGSCM_PATH");
271
272   p = scmpath = strdup (scmpath);
273   if (p == NULL)
274     return 2;
275
276   if (*p)
277     scmpath_len++;
278   for (; *p; p++)
279     if (*p == pathsep)
280       *p = 0, scmpath_len++;
281
282   set_strusage (my_strusage);
283   log_set_prefix ("gpgscm", GPGRT_LOG_WITH_PREFIX);
284
285   /* Make sure that our subsystems are ready.  */
286   i18n_init ();
287   init_common_subsystems (&argc, &argv);
288
289   if (!gcry_check_version (NEED_LIBGCRYPT_VERSION))
290     {
291       fputs ("libgcrypt version mismatch\n", stderr);
292       exit (2);
293     }
294
295   /* Parse the command line. */
296   pargs.argc  = &argc;
297   pargs.argv  = &argv;
298   pargs.flags = 0;
299   parse_arguments (&pargs, opts);
300
301   if (log_get_errorcount (0))
302     exit (2);
303
304   sc = scheme_init_new_custom_alloc (gcry_malloc, gcry_free);
305   if (! sc) {
306     fprintf (stderr, "Could not initialize TinyScheme!\n");
307     return 2;
308   }
309   scheme_set_input_port_file (sc, stdin);
310   scheme_set_output_port_file (sc, stderr);
311
312   if (argc)
313     {
314       script = argv[0];
315       argc--, argv++;
316     }
317
318   err = load (sc, "init.scm", 0, 1);
319   if (! err)
320     err = load (sc, "ffi.scm", 0, 1);
321   if (! err)
322     err = ffi_init (sc, argv0, script ? script : "interactive",
323                     argc, (const char **) argv);
324   if (! err)
325     err = load (sc, "lib.scm", 0, 1);
326   if (! err)
327     err = load (sc, "repl.scm", 0, 1);
328   if (! err)
329     err = load (sc, "xml.scm", 0, 1);
330   if (! err)
331     err = load (sc, "tests.scm", 0, 1);
332   if (! err)
333     err = load (sc, "gnupg.scm", 0, 1);
334   if (err)
335     {
336       fprintf (stderr, "Error initializing gpgscm: %s.\n",
337                gpg_strerror (err));
338       exit (2);
339     }
340
341   if (script == NULL)
342     {
343       /* Interactive shell.  */
344       fprintf (stderr, "gpgscm/"ts_banner".\n");
345       scheme_load_string (sc, "(interactive-repl)");
346     }
347   else
348     {
349       err = load (sc, script, 1, 1);
350       if (err)
351         log_fatal ("%s: %s", script, gpg_strerror (err));
352     }
353
354   retcode = sc->retcode;
355   scheme_load_string (sc, "(*run-atexit-handlers*)");
356   scheme_deinit (sc);
357   xfree (sc);
358   return retcode;
359 }