mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
tests/gpgscm: Add a TinySCHEME-based test driver.
* configure.ac: Add new component. * tests/Makefile.am: Likewise. * tests/gpgscm/Makefile.am: New file. * tests/gpgscm/ffi-private.h: Likewise. * tests/gpgscm/ffi.c: Likewise. * tests/gpgscm/ffi.h: Likewise. * tests/gpgscm/ffi.scm: Likewise. * tests/gpgscm/lib.scm: Likewise. * tests/gpgscm/main.c: Likewise. * tests/gpgscm/private.h: Likewise. * tests/gpgscm/repl.scm: Likewise. * tests/gpgscm/scheme-config.h: Likewise. * tests/gpgscm/t-child.c: Likewise. * tests/gpgscm/t-child.scm: Likewise. * tests/gpgscm/tests.scm: Likewise. Signed-off-by: Justus Winter <justus@g10code.com>
This commit is contained in:
parent
e86e90cc03
commit
e3e51316f1
@ -1898,6 +1898,7 @@ tools/gpg-zip
|
||||
tools/Makefile
|
||||
doc/Makefile
|
||||
tests/Makefile
|
||||
tests/gpgscm/Makefile
|
||||
tests/openpgp/Makefile
|
||||
tests/migrations/Makefile
|
||||
tests/pkits/Makefile
|
||||
|
@ -25,7 +25,7 @@ else
|
||||
openpgp =
|
||||
endif
|
||||
|
||||
SUBDIRS = ${openpgp} . migrations pkits
|
||||
SUBDIRS = gpgscm ${openpgp} . migrations pkits
|
||||
|
||||
GPGSM = ../sm/gpgsm
|
||||
|
||||
|
57
tests/gpgscm/Makefile.am
Normal file
57
tests/gpgscm/Makefile.am
Normal file
@ -0,0 +1,57 @@
|
||||
# TinyScheme-based test driver.
|
||||
#
|
||||
# Copyright (C) 2016 g10 Code GmbH
|
||||
#
|
||||
# This file is part of GnuPG.
|
||||
#
|
||||
# GnuPG is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# GnuPG is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
EXTRA_DIST = \
|
||||
COPYING \
|
||||
Manual.txt \
|
||||
ffi.scm \
|
||||
init.scm \
|
||||
lib.scm \
|
||||
t-child.scm \
|
||||
tests.scm
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/common
|
||||
include $(top_srcdir)/am/cmacros.am
|
||||
|
||||
AM_CFLAGS =
|
||||
|
||||
bin_PROGRAMS = gpgscm
|
||||
noinst_PROGRAMS = t-child
|
||||
|
||||
common_libs = ../$(libcommon)
|
||||
commonpth_libs = ../$(libcommonpth)
|
||||
|
||||
gpgscm_CFLAGS = -imacros scheme-config.h \
|
||||
$(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
|
||||
gpgscm_SOURCES = main.c private.h ffi.c ffi.h ffi-private.h \
|
||||
scheme-config.h opdefines.h scheme.c scheme.h scheme-private.h
|
||||
gpgscm_LDADD = $(LDADD) $(common_libs) \
|
||||
$(NETLIBS) $(LIBICONV) $(LIBREADLINE) \
|
||||
$(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS)
|
||||
|
||||
t_child_SOURCES = t-child.c
|
||||
|
||||
# Make sure that all libs are build before we use them. This is
|
||||
# important for things like make -j2.
|
||||
$(PROGRAMS): $(common_libs)
|
||||
|
||||
.PHONY: check
|
||||
check: gpgscm$(EXEEXT) t-child$(EXEEXT)
|
||||
EXEEXT=$(EXEEXT) GPGSCM_PATH=$(srcdir) \
|
||||
./gpgscm$(EXEEXT) $(srcdir)/t-child.scm
|
132
tests/gpgscm/ffi-private.h
Normal file
132
tests/gpgscm/ffi-private.h
Normal file
@ -0,0 +1,132 @@
|
||||
/* FFI interface for TinySCHEME.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GPGSCM_FFI_PRIVATE_H
|
||||
#define GPGSCM_FFI_PRIVATE_H
|
||||
|
||||
#include <gpg-error.h>
|
||||
#include "scheme.h"
|
||||
#include "scheme-private.h"
|
||||
|
||||
#define FFI_PROLOG() \
|
||||
unsigned int ffi_arg_index GPGRT_ATTR_UNUSED = 1; \
|
||||
int err GPGRT_ATTR_UNUSED = 0 \
|
||||
|
||||
int ffi_bool_value (scheme *sc, pointer p);
|
||||
|
||||
#define CONVERSION_number(SC, X) (SC)->vptr->ivalue (X)
|
||||
#define CONVERSION_string(SC, X) (SC)->vptr->string_value (X)
|
||||
#define CONVERSION_list(SC, X) (X)
|
||||
#define CONVERSION_bool(SC, X) ffi_bool_value ((SC), (X))
|
||||
#define CONVERSION_path(SC, X) (((SC)->vptr->is_string (X) \
|
||||
? (SC)->vptr->string_value \
|
||||
: (SC)->vptr->symname) (X))
|
||||
|
||||
#define IS_A_number(SC, X) (SC)->vptr->is_number (X)
|
||||
#define IS_A_string(SC, X) (SC)->vptr->is_string (X)
|
||||
#define IS_A_list(SC, X) (SC)->vptr->is_list ((SC), X)
|
||||
#define IS_A_bool(SC, X) ((X) == (SC)->F || (X) == (SC)->T)
|
||||
#define IS_A_path(SC, X) ((SC)->vptr->is_string (X) \
|
||||
|| (SC)->vptr->is_symbol (X))
|
||||
|
||||
#define FFI_ARG_OR_RETURN(SC, CTYPE, TARGET, WANT, ARGS) \
|
||||
do { \
|
||||
if ((ARGS) == (SC)->NIL) \
|
||||
return (SC)->vptr->mk_string ((SC), \
|
||||
"too few arguments: want " \
|
||||
#TARGET "("#WANT"/"#CTYPE")\n"); \
|
||||
if (! IS_A_##WANT ((SC), pair_car (ARGS))) { \
|
||||
char ffi_error_message[256]; \
|
||||
snprintf (ffi_error_message, sizeof ffi_error_message, \
|
||||
"argument %d must be: " #WANT "\n", ffi_arg_index); \
|
||||
return (SC)->vptr->mk_string ((SC), ffi_error_message); \
|
||||
} \
|
||||
TARGET = CONVERSION_##WANT (SC, pair_car (ARGS)); \
|
||||
ARGS = pair_cdr (ARGS); \
|
||||
ffi_arg_index += 1; \
|
||||
} while (0)
|
||||
|
||||
#define FFI_ARGS_DONE_OR_RETURN(SC, ARGS) \
|
||||
do { \
|
||||
if ((ARGS) != (SC)->NIL) \
|
||||
return (SC)->vptr->mk_string ((SC), "too many arguments"); \
|
||||
} while (0)
|
||||
|
||||
#define FFI_RETURN_ERR(SC, ERR) \
|
||||
return _cons ((SC), mk_integer ((SC), (ERR)), (SC)->NIL, 1)
|
||||
|
||||
#define FFI_RETURN(SC) FFI_RETURN_ERR (SC, err)
|
||||
|
||||
#define FFI_RETURN_POINTER(SC, X) \
|
||||
return _cons ((SC), mk_integer ((SC), err), \
|
||||
_cons ((SC), (X), (SC)->NIL, 1), 1)
|
||||
#define FFI_RETURN_INT(SC, X) \
|
||||
FFI_RETURN_POINTER ((SC), mk_integer ((SC), (X)))
|
||||
#define FFI_RETURN_STRING(SC, X) \
|
||||
FFI_RETURN_POINTER ((SC), mk_string ((SC), (X)))
|
||||
|
||||
const char *ffi_schemify_name (const char *s, int macro);
|
||||
|
||||
void ffi_scheme_eval (scheme *sc, const char *format, ...)
|
||||
GPGRT_ATTR_PRINTF (2, 3);
|
||||
pointer ffi_sprintf (scheme *sc, const char *format, ...)
|
||||
GPGRT_ATTR_PRINTF (2, 3);
|
||||
|
||||
#define ffi_define_function_name(SC, NAME, F) \
|
||||
do { \
|
||||
scheme_define ((SC), \
|
||||
(SC)->global_env, \
|
||||
mk_symbol ((SC), ffi_schemify_name ("_" #F, 0)), \
|
||||
mk_foreign_func ((SC), (do_##F))); \
|
||||
ffi_scheme_eval ((SC), \
|
||||
"(define (%s . a) (ffi-apply \"%s\" %s a))", \
|
||||
(NAME), (NAME), ffi_schemify_name ("_" #F, 0)); \
|
||||
} while (0)
|
||||
|
||||
#define ffi_define_function(SC, F) \
|
||||
ffi_define_function_name ((SC), ffi_schemify_name (#F, 0), F)
|
||||
|
||||
#define ffi_define_constant(SC, C) \
|
||||
scheme_define ((SC), \
|
||||
(SC)->global_env, \
|
||||
mk_symbol ((SC), ffi_schemify_name (#C, 1)), \
|
||||
mk_integer ((SC), (C)))
|
||||
|
||||
#define ffi_define(SC, SYM, EXP) \
|
||||
scheme_define ((SC), (SC)->global_env, mk_symbol ((SC), (SYM)), EXP)
|
||||
|
||||
#define ffi_define_variable_pointer(SC, C, P) \
|
||||
scheme_define ((SC), \
|
||||
(SC)->global_env, \
|
||||
mk_symbol ((SC), ffi_schemify_name (#C, 0)), \
|
||||
(P))
|
||||
|
||||
#define ffi_define_variable_integer(SC, C) \
|
||||
ffi_define_variable_pointer ((SC), C, (SC)->vptr->mk_integer ((SC), C))
|
||||
|
||||
#define ffi_define_variable_string(SC, C) \
|
||||
ffi_define_variable_pointer ((SC), C, (SC)->vptr->mk_string ((SC), C ?: ""))
|
||||
|
||||
gpg_error_t ffi_list2argv (scheme *sc, pointer list,
|
||||
char ***argv, size_t *len);
|
||||
gpg_error_t ffi_list2intv (scheme *sc, pointer list,
|
||||
int **intv, size_t *len);
|
||||
|
||||
#endif /* GPGSCM_FFI_PRIVATE_H */
|
1167
tests/gpgscm/ffi.c
Normal file
1167
tests/gpgscm/ffi.c
Normal file
File diff suppressed because it is too large
Load Diff
30
tests/gpgscm/ffi.h
Normal file
30
tests/gpgscm/ffi.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* FFI interface for TinySCHEME.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GPGSCM_FFI_H
|
||||
#define GPGSCM_FFI_H
|
||||
|
||||
#include <gpg-error.h>
|
||||
#include "scheme.h"
|
||||
|
||||
gpg_error_t ffi_init (scheme *sc, const char *argv0,
|
||||
int argc, const char **argv);
|
||||
|
||||
#endif /* GPGSCM_FFI_H */
|
40
tests/gpgscm/ffi.scm
Normal file
40
tests/gpgscm/ffi.scm
Normal file
@ -0,0 +1,40 @@
|
||||
;; FFI interface for TinySCHEME.
|
||||
;;
|
||||
;; Copyright (C) 2016 g10 Code GmbH
|
||||
;;
|
||||
;; This file is part of GnuPG.
|
||||
;;
|
||||
;; GnuPG is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; GnuPG is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;; Foreign function wrapper. Expects F to return a list with the
|
||||
;; first element being the `error_t' value returned by the foreign
|
||||
;; function. The error is thrown, or the cdr of the result is
|
||||
;; returned.
|
||||
(define (ffi-apply name f args)
|
||||
(let ((result (apply f args)))
|
||||
(cond
|
||||
((string? result)
|
||||
(ffi-fail name args result))
|
||||
((not (= (car result) 0))
|
||||
(ffi-fail name args (strerror (car result))))
|
||||
((and (= (car result) 0) (pair? (cdr result))) (cadr result))
|
||||
((= (car result) 0) '())
|
||||
(else
|
||||
(throw (list "Result violates FFI calling convention: " result))))))
|
||||
|
||||
(define (ffi-fail name args message)
|
||||
(let ((args' (open-output-string)))
|
||||
(write (cons (string->symbol name) args) args')
|
||||
(throw (string-append
|
||||
(get-output-string args') ": " message))))
|
163
tests/gpgscm/lib.scm
Normal file
163
tests/gpgscm/lib.scm
Normal file
@ -0,0 +1,163 @@
|
||||
;; Additional library functions for TinySCHEME.
|
||||
;;
|
||||
;; Copyright (C) 2016 g10 Code GmbH
|
||||
;;
|
||||
;; This file is part of GnuPG.
|
||||
;;
|
||||
;; GnuPG is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; GnuPG is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(macro (assert form)
|
||||
`(if (not ,(cadr form))
|
||||
(begin
|
||||
(display (list "Assertion failed:" (quote ,(cadr form))))
|
||||
(newline)
|
||||
(exit 1))))
|
||||
(assert #t)
|
||||
|
||||
(define (filter pred lst)
|
||||
(cond ((null? lst) '())
|
||||
((pred (car lst))
|
||||
(cons (car lst) (filter pred (cdr lst))))
|
||||
(else (filter pred (cdr lst)))))
|
||||
|
||||
(define (any p l)
|
||||
(cond ((null? l) #f)
|
||||
((p (car l)) #t)
|
||||
(else (any p (cdr l)))))
|
||||
|
||||
(define (all p l)
|
||||
(cond ((null? l) #t)
|
||||
((not (p (car l))) #f)
|
||||
(else (all p (cdr l)))))
|
||||
|
||||
;; Is PREFIX a prefix of S?
|
||||
(define (string-prefix? s prefix)
|
||||
(and (>= (string-length s) (string-length prefix))
|
||||
(string=? prefix (substring s 0 (string-length prefix)))))
|
||||
(assert (string-prefix? "Scheme" "Sch"))
|
||||
|
||||
;; Is SUFFIX a suffix of S?
|
||||
(define (string-suffix? s suffix)
|
||||
(and (>= (string-length s) (string-length suffix))
|
||||
(string=? suffix (substring s (- (string-length s)
|
||||
(string-length suffix))
|
||||
(string-length s)))))
|
||||
(assert (string-suffix? "Scheme" "eme"))
|
||||
|
||||
;; Locate the first occurrence of needle in haystack.
|
||||
(define (string-index haystack needle)
|
||||
(define (index i haystack needle)
|
||||
(if (= (length haystack) 0)
|
||||
#f
|
||||
(if (char=? (car haystack) needle)
|
||||
i
|
||||
(index (+ i 1) (cdr haystack) needle))))
|
||||
(index 0 (string->list haystack) needle))
|
||||
|
||||
;; Locate the last occurrence of needle in haystack.
|
||||
(define (string-rindex haystack needle)
|
||||
(let ((rindex (string-index (list->string (reverse (string->list haystack)))
|
||||
needle)))
|
||||
(if rindex (- (string-length haystack) rindex 1) #f)))
|
||||
|
||||
;; Split haystack at delimiter at most n times.
|
||||
(define (string-splitn haystack delimiter n)
|
||||
(define (split acc haystack delimiter n)
|
||||
(if (= (string-length haystack) 0)
|
||||
(reverse acc)
|
||||
(let ((i (string-index haystack delimiter)))
|
||||
(if (not (or (eq? i #f) (= 0 n)))
|
||||
(split (cons (substring haystack 0 i) acc)
|
||||
(substring haystack (+ i 1) (string-length haystack))
|
||||
delimiter (- n 1))
|
||||
(split (cons haystack acc) "" delimiter 0)
|
||||
))))
|
||||
(split '() haystack delimiter n))
|
||||
|
||||
;; Split haystack at delimiter.
|
||||
(define (string-split haystack delimiter)
|
||||
(string-splitn haystack delimiter -1))
|
||||
|
||||
;; Trim the prefix of S containing only characters that make PREDICATE
|
||||
;; true. For example (string-ltrim char-whitespace? " foo") =>
|
||||
;; "foo".
|
||||
(define (string-ltrim predicate s)
|
||||
(let loop ((s' (string->list s)))
|
||||
(if (predicate (car s'))
|
||||
(loop (cdr s'))
|
||||
(list->string s'))))
|
||||
|
||||
;; Trim the suffix of S containing only characters that make PREDICATE
|
||||
;; true.
|
||||
(define (string-rtrim predicate s)
|
||||
(let loop ((s' (reverse (string->list s))))
|
||||
(if (predicate (car s'))
|
||||
(loop (cdr s'))
|
||||
(list->string (reverse s')))))
|
||||
|
||||
;; Trim both the prefix and suffix of S containing only characters
|
||||
;; that make PREDICATE true.
|
||||
(define (string-trim predicate s)
|
||||
(string-ltrim predicate (string-rtrim predicate s)))
|
||||
|
||||
(define (string-contains? s contained)
|
||||
(let loop ((offset 0))
|
||||
(if (<= (+ offset (string-length contained)) (string-length s))
|
||||
(if (string=? (substring s offset (+ offset (string-length contained)))
|
||||
contained)
|
||||
#t
|
||||
(loop (+ 1 offset)))
|
||||
#f)))
|
||||
|
||||
(define (echo . msg)
|
||||
(for-each (lambda (x) (display x) (display " ")) msg)
|
||||
(newline))
|
||||
|
||||
;; Read a word from port P.
|
||||
(define (read-word . p)
|
||||
(list->string
|
||||
(let f ()
|
||||
(let ((c (apply peek-char p)))
|
||||
(cond
|
||||
((eof-object? c) '())
|
||||
((char-alphabetic? c)
|
||||
(apply read-char p)
|
||||
(cons c (f)))
|
||||
(else
|
||||
(apply read-char p)
|
||||
'()))))))
|
||||
|
||||
;; Read a line from port P.
|
||||
(define (read-line . p)
|
||||
(list->string
|
||||
(let f ()
|
||||
(let ((c (apply peek-char p)))
|
||||
(cond
|
||||
((eof-object? c) '())
|
||||
((char=? c #\newline)
|
||||
(apply read-char p)
|
||||
'())
|
||||
(else
|
||||
(apply read-char p)
|
||||
(cons c (f))))))))
|
||||
|
||||
;; Read everything from port P.
|
||||
(define (read-all . p)
|
||||
(list->string
|
||||
(let f ()
|
||||
(let ((c (apply peek-char p)))
|
||||
(cond
|
||||
((eof-object? c) '())
|
||||
(else (apply read-char p)
|
||||
(cons c (f))))))))
|
286
tests/gpgscm/main.c
Normal file
286
tests/gpgscm/main.c
Normal file
@ -0,0 +1,286 @@
|
||||
/* TinyScheme-based test driver.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <gcrypt.h>
|
||||
#include <gpg-error.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "private.h"
|
||||
#include "scheme.h"
|
||||
#include "ffi.h"
|
||||
#include "i18n.h"
|
||||
#include "../../common/argparse.h"
|
||||
#include "../../common/init.h"
|
||||
#include "../../common/logging.h"
|
||||
#include "../../common/strlist.h"
|
||||
#include "../../common/sysutils.h"
|
||||
|
||||
/* The TinyScheme banner. Unfortunately, it isn't in the header
|
||||
file. */
|
||||
#define ts_banner "TinyScheme 1.41"
|
||||
|
||||
int verbose;
|
||||
|
||||
|
||||
|
||||
/* Constants to identify the commands and options. */
|
||||
enum cmd_and_opt_values
|
||||
{
|
||||
aNull = 0,
|
||||
oVerbose = 'v',
|
||||
};
|
||||
|
||||
/* The list of commands and options. */
|
||||
static ARGPARSE_OPTS opts[] =
|
||||
{
|
||||
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
|
||||
ARGPARSE_end (),
|
||||
};
|
||||
|
||||
char *scmpath = "";
|
||||
size_t scmpath_len = 0;
|
||||
|
||||
/* Command line parsing. */
|
||||
static void
|
||||
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
|
||||
{
|
||||
int no_more_options = 0;
|
||||
|
||||
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
|
||||
{
|
||||
switch (pargs->r_opt)
|
||||
{
|
||||
case oVerbose:
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
default:
|
||||
pargs->err = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print usage information and and provide strings for help. */
|
||||
static const char *
|
||||
my_strusage( int level )
|
||||
{
|
||||
const char *p;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 11: p = "gpgscm (@GNUPG@)";
|
||||
break;
|
||||
case 13: p = VERSION; break;
|
||||
case 17: p = PRINTABLE_OS_NAME; break;
|
||||
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
|
||||
|
||||
case 1:
|
||||
case 40:
|
||||
p = _("Usage: gpgscm [options] [file] (-h for help)");
|
||||
break;
|
||||
case 41:
|
||||
p = _("Syntax: gpgscm [options] [file]\n"
|
||||
"Execute the given Scheme program, or spawn interactive shell.\n");
|
||||
break;
|
||||
|
||||
default: p = NULL; break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/* Load the Scheme program from FILE_NAME. If FILE_NAME is not an
|
||||
absolute path, and LOOKUP_IN_PATH is given, then it is qualified
|
||||
with the values in scmpath until the file is found. */
|
||||
static gpg_error_t
|
||||
load (scheme *sc, char *file_name,
|
||||
int lookup_in_cwd, int lookup_in_path)
|
||||
{
|
||||
gpg_error_t err = 0;
|
||||
size_t n;
|
||||
const char *directory;
|
||||
char *qualified_name = file_name;
|
||||
int use_path;
|
||||
FILE *h = NULL;
|
||||
|
||||
use_path =
|
||||
lookup_in_path && ! (file_name[0] == '/' || scmpath_len == 0);
|
||||
|
||||
if (file_name[0] == '/' || lookup_in_cwd || scmpath_len == 0)
|
||||
{
|
||||
h = fopen (file_name, "r");
|
||||
if (! h)
|
||||
err = gpg_error_from_syserror ();
|
||||
}
|
||||
|
||||
if (h == NULL && use_path)
|
||||
for (directory = scmpath, n = scmpath_len; n;
|
||||
directory += strlen (directory) + 1, n--)
|
||||
{
|
||||
if (asprintf (&qualified_name, "%s/%s", directory, file_name) < 0)
|
||||
return gpg_error_from_syserror ();
|
||||
|
||||
h = fopen (qualified_name, "r");
|
||||
if (h)
|
||||
break;
|
||||
|
||||
if (n > 1)
|
||||
{
|
||||
free (qualified_name);
|
||||
continue; /* Try again! */
|
||||
}
|
||||
|
||||
err = gpg_error_from_syserror ();
|
||||
}
|
||||
|
||||
if (h == NULL)
|
||||
{
|
||||
/* Failed and no more elements in scmpath to try. */
|
||||
fprintf (stderr, "Could not read %s: %s.\n",
|
||||
qualified_name, gpg_strerror (err));
|
||||
if (lookup_in_path)
|
||||
fprintf (stderr,
|
||||
"Consider using GPGSCM_PATH to specify the location "
|
||||
"of the Scheme library.\n");
|
||||
return err;
|
||||
}
|
||||
if (verbose > 1)
|
||||
fprintf (stderr, "Loading %s...\n", qualified_name);
|
||||
scheme_load_named_file (sc, h, qualified_name);
|
||||
fclose (h);
|
||||
|
||||
if (file_name != qualified_name)
|
||||
free (qualified_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
gpg_error_t err;
|
||||
char *argv0;
|
||||
ARGPARSE_ARGS pargs;
|
||||
scheme *sc;
|
||||
char *p;
|
||||
#if _WIN32
|
||||
char pathsep = ';';
|
||||
#else
|
||||
char pathsep = ':';
|
||||
#endif
|
||||
char *script = NULL;
|
||||
|
||||
/* Save argv[0] so that we can re-exec. */
|
||||
argv0 = argv[0];
|
||||
|
||||
/* Parse path. */
|
||||
if (getenv ("GPGSCM_PATH"))
|
||||
scmpath = getenv ("GPGSCM_PATH");
|
||||
|
||||
p = scmpath = strdup (scmpath);
|
||||
if (p == NULL)
|
||||
return 2;
|
||||
|
||||
if (*p)
|
||||
scmpath_len++;
|
||||
for (; *p; p++)
|
||||
if (*p == pathsep)
|
||||
*p = 0, scmpath_len++;
|
||||
|
||||
set_strusage (my_strusage);
|
||||
log_set_prefix ("gpgscm", 1);
|
||||
|
||||
/* Make sure that our subsystems are ready. */
|
||||
i18n_init ();
|
||||
init_common_subsystems (&argc, &argv);
|
||||
|
||||
if (!gcry_check_version (GCRYPT_VERSION))
|
||||
{
|
||||
fputs ("libgcrypt version mismatch\n", stderr);
|
||||
exit (2);
|
||||
}
|
||||
|
||||
/* Parse the command line. */
|
||||
pargs.argc = &argc;
|
||||
pargs.argv = &argv;
|
||||
pargs.flags = 0;
|
||||
parse_arguments (&pargs, opts);
|
||||
|
||||
if (log_get_errorcount (0))
|
||||
exit (2);
|
||||
|
||||
sc = scheme_init_new ();
|
||||
if (! sc) {
|
||||
fprintf (stderr, "Could not initialize TinyScheme!\n");
|
||||
return 2;
|
||||
}
|
||||
scheme_set_input_port_file (sc, stdin);
|
||||
scheme_set_output_port_file (sc, stderr);
|
||||
|
||||
if (argc)
|
||||
{
|
||||
script = argv[0];
|
||||
argc--, argv++;
|
||||
}
|
||||
|
||||
err = load (sc, "init.scm", 0, 1);
|
||||
if (! err)
|
||||
err = load (sc, "ffi.scm", 0, 1);
|
||||
if (! err)
|
||||
err = ffi_init (sc, argv0, argc, (const char **) argv);
|
||||
if (! err)
|
||||
err = load (sc, "lib.scm", 0, 1);
|
||||
if (! err)
|
||||
err = load (sc, "repl.scm", 0, 1);
|
||||
if (! err)
|
||||
err = load (sc, "tests.scm", 0, 1);
|
||||
if (err)
|
||||
{
|
||||
fprintf (stderr, "Error initializing gpgscm: %s.\n",
|
||||
gpg_strerror (err));
|
||||
exit (2);
|
||||
}
|
||||
|
||||
if (script == NULL)
|
||||
{
|
||||
/* Interactive shell. */
|
||||
fprintf (stderr, "gpgscm/"ts_banner".\n");
|
||||
scheme_load_string (sc, "(interactive-repl)");
|
||||
}
|
||||
else
|
||||
{
|
||||
err = load (sc, script, 1, 1);
|
||||
if (err)
|
||||
log_fatal ("%s: %s", script, gpg_strerror (err));
|
||||
}
|
||||
|
||||
scheme_deinit (sc);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
26
tests/gpgscm/private.h
Normal file
26
tests/gpgscm/private.h
Normal file
@ -0,0 +1,26 @@
|
||||
/* TinyScheme-based test driver.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GPGSCM_PRIVATE_H__
|
||||
#define __GPGSCM_PRIVATE_H__
|
||||
|
||||
extern int verbose;
|
||||
|
||||
#endif /* __GPGSCM_PRIVATE_H__ */
|
50
tests/gpgscm/repl.scm
Normal file
50
tests/gpgscm/repl.scm
Normal file
@ -0,0 +1,50 @@
|
||||
;; A read-evaluate-print-loop for gpgscm.
|
||||
;;
|
||||
;; Copyright (C) 2016 g10 Code GmbH
|
||||
;;
|
||||
;; This file is part of GnuPG.
|
||||
;;
|
||||
;; GnuPG is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; GnuPG is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;; Interactive repl using 'prompt' function. P must be a function
|
||||
;; that given the current entered prefix returns the prompt to
|
||||
;; display.
|
||||
(define (repl p)
|
||||
(let ((repl-environment (make-environment)))
|
||||
(call/cc
|
||||
(lambda (exit)
|
||||
(let loop ((prefix ""))
|
||||
(let ((line (prompt (p prefix))))
|
||||
(if (and (not (eof-object? line)) (= 0 (string-length line)))
|
||||
(exit (loop prefix)))
|
||||
(if (not (eof-object? line))
|
||||
(let* ((next (string-append prefix line))
|
||||
(c (catch (begin (echo "Parse error:" *error*)
|
||||
(loop prefix))
|
||||
(read (open-input-string next)))))
|
||||
(if (not (eof-object? c))
|
||||
(begin
|
||||
(catch (echo "Error:" *error*)
|
||||
(echo " ===>" (eval c repl-environment)))
|
||||
(exit (loop ""))))
|
||||
(exit (loop next))))))))))
|
||||
|
||||
(define (prompt-append-prefix prompt prefix)
|
||||
(string-append prompt (if (> (string-length prefix) 0)
|
||||
(string-append prefix "...")
|
||||
"> ")))
|
||||
|
||||
;; Default repl run by main.c.
|
||||
(define (interactive-repl)
|
||||
(repl (lambda (p) (prompt-append-prefix "gpgscm " p))))
|
36
tests/gpgscm/scheme-config.h
Normal file
36
tests/gpgscm/scheme-config.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* TinyScheme configuration.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define STANDALONE 0
|
||||
#define USE_MATH 0
|
||||
#define USE_CHAR_CLASSIFIERS 1
|
||||
#define USE_ASCII_NAMES 1
|
||||
#define USE_STRING_PORTS 1
|
||||
#define USE_ERROR_HOOK 1
|
||||
#define USE_TRACING 1
|
||||
#define USE_COLON_HOOK 1
|
||||
#define USE_DL 0
|
||||
#define USE_PLIST 0
|
||||
#define USE_INTERFACE 1
|
||||
#define SHOW_ERROR_LINE 1
|
||||
|
||||
#if __MINGW32__
|
||||
# define USE_STRLWR 0
|
||||
#endif /* __MINGW32__ */
|
66
tests/gpgscm/t-child.c
Normal file
66
tests/gpgscm/t-child.c
Normal file
@ -0,0 +1,66 @@
|
||||
/* Sanity check for the process and IPC primitives.
|
||||
*
|
||||
* Copyright (C) 2016 g10 code GmbH
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
* GnuPG is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GnuPG is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <io.h>
|
||||
#endif
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
#if _WIN32
|
||||
if (! setmode (stdin, O_BINARY))
|
||||
return 23;
|
||||
if (! setmode (stdout, O_BINARY))
|
||||
return 23;
|
||||
#endif
|
||||
|
||||
if (argc == 1)
|
||||
return 2;
|
||||
else if (strcmp (argv[1], "return0") == 0)
|
||||
return 0;
|
||||
else if (strcmp (argv[1], "return1") == 0)
|
||||
return 1;
|
||||
else if (strcmp (argv[1], "return77") == 0)
|
||||
return 77;
|
||||
else if (strcmp (argv[1], "hello_stdout") == 0)
|
||||
fprintf (stdout, "hello");
|
||||
else if (strcmp (argv[1], "hello_stderr") == 0)
|
||||
fprintf (stderr, "hello");
|
||||
else if (strcmp (argv[1], "cat") == 0)
|
||||
while (! feof (stdin))
|
||||
{
|
||||
char buffer[4096];
|
||||
size_t bytes_read;
|
||||
bytes_read = fread (buffer, 1, sizeof buffer, stdin);
|
||||
fwrite (buffer, 1, bytes_read, stdout);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "unknown command %s\n", argv[1]);
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
74
tests/gpgscm/t-child.scm
Normal file
74
tests/gpgscm/t-child.scm
Normal file
@ -0,0 +1,74 @@
|
||||
(echo "Testing process and IPC primitives...")
|
||||
|
||||
(define (qualify executable)
|
||||
(string-append executable (getenv "EXEEXT")))
|
||||
|
||||
(assert (= 0 (call `(,(qualify "t-child") "return0"))))
|
||||
(assert (= 1 (call `(,(qualify "t-child") "return1"))))
|
||||
(assert (= 77 (call `(,(qualify "t-child") "return77"))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "return0") "")))
|
||||
(assert (= 0 (:retcode r)))
|
||||
(assert (string=? "" (:stdout r)))
|
||||
(assert (string=? "" (:stderr r))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "return1") "")))
|
||||
(assert (= 1 (:retcode r)))
|
||||
(assert (string=? "" (:stdout r)))
|
||||
(assert (string=? "" (:stderr r))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "return77") "")))
|
||||
(assert (= 77 (:retcode r)))
|
||||
(assert (string=? "" (:stdout r)))
|
||||
(assert (string=? "" (:stderr r))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "hello_stdout") "")))
|
||||
(assert (= 0 (:retcode r)))
|
||||
(assert (string=? "hello" (:stdout r)))
|
||||
(assert (string=? "" (:stderr r))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "hello_stderr") "")))
|
||||
(assert (= 0 (:retcode r)))
|
||||
(assert (string=? "" (:stdout r)))
|
||||
(assert (string=? "hello" (:stderr r))))
|
||||
|
||||
(let ((r (call-with-io `(,(qualify "t-child") "cat") "hellohello")))
|
||||
(assert (= 0 (:retcode r)))
|
||||
(assert (string=? "hellohello" (:stdout r)))
|
||||
(assert (string=? "" (:stderr r))))
|
||||
|
||||
(define (spawn what)
|
||||
(spawn-process-fd what CLOSED_FD STDOUT_FILENO STDERR_FILENO))
|
||||
|
||||
(let ((pid0 (spawn `(,(qualify "t-child") "return0")))
|
||||
(pid1 (spawn `(,(qualify "t-child") "return0"))))
|
||||
(assert (equal? '(0 0)
|
||||
(wait-processes '("child0" "child1") (list pid0 pid1) #t))))
|
||||
|
||||
(let ((pid0 (spawn `(,(qualify "t-child") "return1")))
|
||||
(pid1 (spawn `(,(qualify "t-child") "return0"))))
|
||||
(assert (equal? '(1 0)
|
||||
(wait-processes '("child0" "child1") (list pid0 pid1) #t))))
|
||||
|
||||
(let ((pid0 (spawn `(,(qualify "t-child") "return0")))
|
||||
(pid1 (spawn `(,(qualify "t-child") "return77")))
|
||||
(pid2 (spawn `(,(qualify "t-child") "return1"))))
|
||||
(assert (equal? '(0 77 1)
|
||||
(wait-processes '("child0" "child1" "child2")
|
||||
(list pid0 pid1 pid2) #t))))
|
||||
|
||||
(let* ((p (pipe))
|
||||
(pid0 (spawn-process-fd
|
||||
`(,(qualify "t-child") "hello_stdout")
|
||||
CLOSED_FD (:write-end p) STDERR_FILENO))
|
||||
(_ (close (:write-end p)))
|
||||
(pid1 (spawn-process-fd
|
||||
`(,(qualify "t-child") "cat")
|
||||
(:read-end p) STDOUT_FILENO STDERR_FILENO)))
|
||||
(close (:read-end p))
|
||||
(assert
|
||||
(equal? '(0 0)
|
||||
(wait-processes '("child0" "child1") (list pid0 pid1) #t))))
|
||||
(echo " world.")
|
||||
|
||||
(echo "All good.")
|
402
tests/gpgscm/tests.scm
Normal file
402
tests/gpgscm/tests.scm
Normal file
@ -0,0 +1,402 @@
|
||||
;; Common definitions for writing tests.
|
||||
;;
|
||||
;; Copyright (C) 2016 g10 Code GmbH
|
||||
;;
|
||||
;; This file is part of GnuPG.
|
||||
;;
|
||||
;; GnuPG is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; GnuPG is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;; Trace displays and returns the given value. A debugging aid.
|
||||
(define (trace x)
|
||||
(display x)
|
||||
(newline)
|
||||
x)
|
||||
|
||||
;; Stringification.
|
||||
(define (stringify expression)
|
||||
(let ((p (open-output-string)))
|
||||
(write expression p)
|
||||
(get-output-string p)))
|
||||
|
||||
;; Reporting.
|
||||
(define (info msg)
|
||||
(display msg)
|
||||
(newline)
|
||||
(flush-stdio))
|
||||
|
||||
(define (error msg)
|
||||
(info msg)
|
||||
(exit 1))
|
||||
|
||||
(define (skip msg)
|
||||
(info msg)
|
||||
(exit 77))
|
||||
|
||||
(define (make-counter)
|
||||
(let ((c 0))
|
||||
(lambda ()
|
||||
(let ((r c))
|
||||
(set! c (+ 1 c))
|
||||
r))))
|
||||
|
||||
(define *progress-nesting* 0)
|
||||
|
||||
(define (call-with-progress msg what)
|
||||
(set! *progress-nesting* (+ 1 *progress-nesting*))
|
||||
(if (= 1 *progress-nesting*)
|
||||
(begin
|
||||
(info msg)
|
||||
(display " > ")
|
||||
(flush-stdio)
|
||||
(what (lambda (item)
|
||||
(display item)
|
||||
(display " ")
|
||||
(flush-stdio)))
|
||||
(info "< "))
|
||||
(begin
|
||||
(what (lambda (item) (display ".") (flush-stdio)))
|
||||
(display " ")
|
||||
(flush-stdio)))
|
||||
(set! *progress-nesting* (- *progress-nesting* 1)))
|
||||
|
||||
(define (for-each-p msg proc lst)
|
||||
(for-each-p' msg proc (lambda (x) x) lst))
|
||||
|
||||
(define (for-each-p' msg proc fmt lst)
|
||||
(call-with-progress
|
||||
msg
|
||||
(lambda (progress)
|
||||
(for-each (lambda (a)
|
||||
(progress (fmt a))
|
||||
(proc a))
|
||||
lst))))
|
||||
|
||||
;; Process management.
|
||||
(define CLOSED_FD -1)
|
||||
(define (call-with-fds what infd outfd errfd)
|
||||
(wait-process (stringify what) (spawn-process-fd what infd outfd errfd) #t))
|
||||
(define (call what)
|
||||
(call-with-fds what
|
||||
CLOSED_FD
|
||||
(if (< *verbose* 0) STDOUT_FILENO CLOSED_FD)
|
||||
(if (< *verbose* 0) STDERR_FILENO CLOSED_FD)))
|
||||
(define (call-check what)
|
||||
(if (not (= 0 (call what)))
|
||||
(throw (list what "failed"))))
|
||||
|
||||
;; Accessor functions for the results of 'spawn-process'.
|
||||
(define :stdin car)
|
||||
(define :stdout cadr)
|
||||
(define :stderr caddr)
|
||||
(define :pid cadddr)
|
||||
|
||||
(define (call-with-io what in)
|
||||
(let ((h (spawn-process what 0)))
|
||||
(es-write (:stdin h) in)
|
||||
(es-fclose (:stdin h))
|
||||
(let* ((out (es-read-all (:stdout h)))
|
||||
(err (es-read-all (:stderr h)))
|
||||
(result (wait-process (car what) (:pid h) #t)))
|
||||
(es-fclose (:stdout h))
|
||||
(es-fclose (:stderr h))
|
||||
(list result out err))))
|
||||
|
||||
;; Accessor function for the results of 'call-with-io'. ':stdout' and
|
||||
;; ':stderr' can also be used.
|
||||
(define :retcode car)
|
||||
|
||||
(define (call-popen command input-string)
|
||||
(let ((result (call-with-io command input-string)))
|
||||
(if (= 0 (:retcode result))
|
||||
(:stdout result)
|
||||
(throw (:stderr result)))))
|
||||
|
||||
;;
|
||||
;; estream helpers.
|
||||
;;
|
||||
|
||||
(define (es-read-all stream)
|
||||
(let loop
|
||||
((acc ""))
|
||||
(if (es-feof stream)
|
||||
acc
|
||||
(loop (string-append acc (es-read stream 4096))))))
|
||||
|
||||
;;
|
||||
;; File management.
|
||||
;;
|
||||
(define (file=? a b)
|
||||
(file-equal a b #t))
|
||||
|
||||
(define (text-file=? a b)
|
||||
(file-equal a b #f))
|
||||
|
||||
(define (file-copy from to)
|
||||
(catch '() (unlink to))
|
||||
(letfd ((source (open from (logior O_RDONLY O_BINARY)))
|
||||
(sink (open to (logior O_WRONLY O_CREAT O_BINARY) #o600)))
|
||||
(splice source sink)))
|
||||
|
||||
(define (text-file-copy from to)
|
||||
(catch '() (unlink to))
|
||||
(letfd ((source (open from O_RDONLY))
|
||||
(sink (open to (logior O_WRONLY O_CREAT) #o600)))
|
||||
(splice source sink)))
|
||||
|
||||
(define (canonical-path path)
|
||||
(if (char=? #\/ (string-ref path 0))
|
||||
path
|
||||
(string-append (getcwd) "/" path)))
|
||||
|
||||
(define (in-srcdir what)
|
||||
(canonical-path (string-append (getenv "srcdir") "/" what)))
|
||||
|
||||
(define (with-path name)
|
||||
(let loop ((path (string-split (getenv "GPGSCM_PATH") #\:)))
|
||||
(if (null? path)
|
||||
name
|
||||
(let* ((qualified-name (string-append (car path) "/" name))
|
||||
(file-exists (call-with-input-file qualified-name
|
||||
(lambda (x) #t))))
|
||||
(if file-exists
|
||||
qualified-name
|
||||
(loop (cdr path)))))))
|
||||
|
||||
(define (basename path)
|
||||
(let ((i (string-index path #\/)))
|
||||
(if (equal? i #f)
|
||||
path
|
||||
(basename (substring path (+ 1 i) (string-length path))))))
|
||||
|
||||
;; Helper for (pipe).
|
||||
(define :read-end car)
|
||||
(define :write-end cadr)
|
||||
|
||||
;; let-like macro that manages file descriptors.
|
||||
;;
|
||||
;; (letfd <bindings> <body>)
|
||||
;;
|
||||
;; Bind all variables given in <bindings> and initialize each of them
|
||||
;; to the given initial value, and close them after evaluting <body>.
|
||||
(macro (letfd form)
|
||||
(let ((result-sym (gensym)))
|
||||
`((lambda (,(caaadr form))
|
||||
(let ((,result-sym
|
||||
,(if (= 1 (length (cadr form)))
|
||||
`(begin ,@(cddr form))
|
||||
`(letfd ,(cdadr form) ,@(cddr form)))))
|
||||
(close ,(caaadr form))
|
||||
,result-sym)) ,@(cdaadr form))))
|
||||
|
||||
(macro (with-working-directory form)
|
||||
(let ((result-sym (gensym)) (cwd-sym (gensym)))
|
||||
`(let* ((,cwd-sym (getcwd))
|
||||
(_ (if ,(cadr form) (chdir ,(cadr form))))
|
||||
(,result-sym (begin ,@(cddr form))))
|
||||
(chdir ,cwd-sym)
|
||||
,result-sym)))
|
||||
|
||||
(macro (with-temporary-working-directory form)
|
||||
(let ((result-sym (gensym)) (cwd-sym (gensym)) (tmp-sym (gensym)))
|
||||
`(let* ((,cwd-sym (getcwd))
|
||||
(,tmp-sym (mkdtemp "gpgscm-XXXXXX"))
|
||||
(_ (chdir ,tmp-sym))
|
||||
(,result-sym (begin ,@(cdr form))))
|
||||
(chdir ,cwd-sym)
|
||||
(unlink-recursively ,tmp-sym)
|
||||
,result-sym)))
|
||||
|
||||
(define (make-temporary-file . args)
|
||||
(canonical-path (string-append (mkdtemp "gpgscm-XXXXXX")
|
||||
"/"
|
||||
(if (null? args) "a" (car args)))))
|
||||
|
||||
(define (remove-temporary-file filename)
|
||||
(catch '()
|
||||
(unlink filename))
|
||||
(let ((dirname (substring filename 0 (string-rindex filename #\/))))
|
||||
(catch (echo "removing temporary directory" dirname "failed")
|
||||
(rmdir dirname))))
|
||||
|
||||
;; let-like macro that manages temporary files.
|
||||
;;
|
||||
;; (lettmp <bindings> <body>)
|
||||
;;
|
||||
;; Bind all variables given in <bindings>, initialize each of them to
|
||||
;; a string representing an unique path in the filesystem, and delete
|
||||
;; them after evaluting <body>.
|
||||
(macro (lettmp form)
|
||||
(let ((result-sym (gensym)))
|
||||
`((lambda (,(caadr form))
|
||||
(let ((,result-sym
|
||||
,(if (= 1 (length (cadr form)))
|
||||
`(begin ,@(cddr form))
|
||||
`(lettmp ,(cdadr form) ,@(cddr form)))))
|
||||
(remove-temporary-file ,(caadr form))
|
||||
,result-sym)) (make-temporary-file ,(symbol->string (caadr form))))))
|
||||
|
||||
(define (check-execution source transformer)
|
||||
(lettmp (sink)
|
||||
(transformer source sink)))
|
||||
|
||||
(define (check-identity source transformer)
|
||||
(lettmp (sink)
|
||||
(transformer source sink)
|
||||
(if (not (file=? source sink))
|
||||
(error "mismatch"))))
|
||||
|
||||
;;
|
||||
;; Monadic pipe support.
|
||||
;;
|
||||
|
||||
(define pipeM
|
||||
(package
|
||||
(define (new procs source sink producer)
|
||||
(package
|
||||
(define (dump)
|
||||
(write (list procs source sink producer))
|
||||
(newline))
|
||||
(define (add-proc command pid)
|
||||
(new (cons (list command pid) procs) source sink producer))
|
||||
(define (commands)
|
||||
(map car procs))
|
||||
(define (pids)
|
||||
(map cadr procs))
|
||||
(define (set-source source')
|
||||
(new procs source' sink producer))
|
||||
(define (set-sink sink')
|
||||
(new procs source sink' producer))
|
||||
(define (set-producer producer')
|
||||
(if producer
|
||||
(throw "producer already set"))
|
||||
(new procs source sink producer'))))))
|
||||
|
||||
|
||||
(define (pipe:do . commands)
|
||||
(let loop ((M (pipeM::new '() CLOSED_FD CLOSED_FD #f)) (cmds commands))
|
||||
(if (null? cmds)
|
||||
(begin
|
||||
(if M::producer (M::producer))
|
||||
(if (not (null? M::procs))
|
||||
(let* ((retcodes (wait-processes (map stringify (M::commands))
|
||||
(M::pids) #t))
|
||||
(results (map (lambda (p r) (append p (list r)))
|
||||
M::procs retcodes))
|
||||
(failed (filter (lambda (x) (not (= 0 (caddr x))))
|
||||
results)))
|
||||
(if (not (null? failed))
|
||||
(throw failed))))) ; xxx nicer reporting
|
||||
(if (and (= 2 (length cmds)) (number? (cadr cmds)))
|
||||
;; hack: if it's an fd, use it as sink
|
||||
(let ((M' ((car cmds) (M::set-sink (cadr cmds)))))
|
||||
(if (> M::source 2) (close M::source))
|
||||
(if (> (cadr cmds) 2) (close (cadr cmds)))
|
||||
(loop M' '()))
|
||||
(let ((M' ((car cmds) M)))
|
||||
(if (> M::source 2) (close M::source))
|
||||
(loop M' (cdr cmds)))))))
|
||||
|
||||
(define (pipe:open pathname flags)
|
||||
(lambda (M)
|
||||
(M::set-source (open pathname flags))))
|
||||
|
||||
(define (pipe:defer producer)
|
||||
(lambda (M)
|
||||
(let* ((p (outbound-pipe))
|
||||
(M' (M::set-source (:read-end p))))
|
||||
(M'::set-producer (lambda ()
|
||||
(producer (:write-end p))
|
||||
(close (:write-end p)))))))
|
||||
(define (pipe:echo data)
|
||||
(pipe:defer (lambda (sink) (display data (fdopen sink "wb")))))
|
||||
|
||||
(define (pipe:spawn command)
|
||||
(lambda (M)
|
||||
(define (do-spawn M new-source)
|
||||
(let ((pid (spawn-process-fd command M::source M::sink
|
||||
(if (> *verbose* 0)
|
||||
STDERR_FILENO CLOSED_FD)))
|
||||
(M' (M::set-source new-source)))
|
||||
(M'::add-proc command pid)))
|
||||
(if (= CLOSED_FD M::sink)
|
||||
(let* ((p (pipe))
|
||||
(M' (do-spawn (M::set-sink (:write-end p)) (:read-end p))))
|
||||
(close (:write-end p))
|
||||
(M'::set-sink CLOSED_FD))
|
||||
(do-spawn M CLOSED_FD))))
|
||||
|
||||
(define (pipe:splice sink)
|
||||
(lambda (M)
|
||||
(splice M::source sink)
|
||||
(M::set-source CLOSED_FD)))
|
||||
|
||||
(define (pipe:write-to pathname flags mode)
|
||||
(open pathname flags mode))
|
||||
|
||||
;;
|
||||
;; Monadic transformer support.
|
||||
;;
|
||||
|
||||
(define (tr:do . commands)
|
||||
(let loop ((tmpfiles '()) (source #f) (cmds commands))
|
||||
(if (null? cmds)
|
||||
(for-each remove-temporary-file tmpfiles)
|
||||
(let ((v ((car cmds) tmpfiles source)))
|
||||
(loop (car v) (cadr v) (cdr cmds))))))
|
||||
|
||||
(define (tr:open pathname)
|
||||
(lambda (tmpfiles source)
|
||||
(list tmpfiles pathname)))
|
||||
|
||||
(define (tr:spawn input command)
|
||||
(lambda (tmpfiles source)
|
||||
(let* ((t (make-temporary-file))
|
||||
(cmd (map (lambda (x)
|
||||
(cond
|
||||
((equal? '**in** x) source)
|
||||
((equal? '**out** x) t)
|
||||
(else x))) command)))
|
||||
(call-popen cmd input)
|
||||
(list (cons t tmpfiles) t))))
|
||||
|
||||
(define (tr:write-to pathname)
|
||||
(lambda (tmpfiles source)
|
||||
(rename source pathname)
|
||||
(list tmpfiles pathname)))
|
||||
|
||||
(define (tr:pipe-do . commands)
|
||||
(lambda (tmpfiles source)
|
||||
(let ((t (make-temporary-file)))
|
||||
(apply pipe:do
|
||||
`(,@(if source `(,(pipe:open source (logior O_RDONLY O_BINARY))) '())
|
||||
,@commands
|
||||
,(pipe:write-to t (logior O_WRONLY O_BINARY O_CREAT) #o600)))
|
||||
(list (cons t tmpfiles) t))))
|
||||
|
||||
(define (tr:assert-identity reference)
|
||||
(lambda (tmpfiles source)
|
||||
(if (not (file=? source reference))
|
||||
(error "mismatch"))
|
||||
(list tmpfiles source)))
|
||||
|
||||
(define (tr:assert-weak-identity reference)
|
||||
(lambda (tmpfiles source)
|
||||
(if (not (text-file=? source reference))
|
||||
(error "mismatch"))
|
||||
(list tmpfiles source)))
|
||||
|
||||
(define (tr:call-with-content function)
|
||||
(lambda (tmpfiles source)
|
||||
(function (call-with-input-file source read-all))
|
||||
(list tmpfiles source)))
|
Loading…
x
Reference in New Issue
Block a user