From 9a43a3bea411be5de83c64a2f1d28f365de7133f Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 25 Jul 2022 14:21:23 +0100 Subject: [PATCH] Port to pcre2 The last version of PCRE (v1) 8.45 is end of life and will receive no more updates. PCRE2 is its replacement and is widely available in distros. So port the library to PCRE2. --- Makefile.am | 12 +++++------ README | 6 +++--- configure.ac | 8 +++---- example-sshpass.c | 43 ++++++++++++++++++++++--------------- miniexpect.c | 33 ++++++++++++++-------------- miniexpect.h | 10 ++++----- miniexpect.pod | 63 +++++++++++++++++++----------------------------------- test-ls-version.c | 27 ++++++++++++----------- test-multi-match.c | 15 ++++++------- tests.h | 23 +++++++++++++------- 10 files changed, 118 insertions(+), 122 deletions(-) diff --git a/Makefile.am b/Makefile.am index 811742b..3a685c9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,8 +24,8 @@ EXTRA_DIST = miniexpect.3 lib_LTLIBRARIES = libminiexpect.la libminiexpect_la_SOURCES = miniexpect.c miniexpect.h -libminiexpect_la_CFLAGS = $(PCRE_CFLAGS) -Wall -libminiexpect_la_LIBADD = $(PCRE_LIBS) +libminiexpect_la_CFLAGS = $(PCRE2_CFLAGS) -Wall +libminiexpect_la_LIBADD = $(PCRE2_LIBS) libminiexpect_la_LDFLAGS = -version-info 0:0:0 # Examples. @@ -33,7 +33,7 @@ libminiexpect_la_LDFLAGS = -version-info 0:0:0 noinst_PROGRAMS = example-sshpass example_sshpass_SOURCES = example-sshpass.c -example_sshpass_CFLAGS = $(PCRE_CFLAGS) -Wall +example_sshpass_CFLAGS = $(PCRE2_CFLAGS) -Wall example_sshpass_LDADD = libminiexpect.la # Tests. @@ -45,15 +45,15 @@ check_PROGRAMS = \ test-multi-match test_spawn_SOURCES = test-spawn.c tests.h miniexpect.h -test_spawn_CFLAGS = $(PCRE_CFLAGS) -Wall +test_spawn_CFLAGS = $(PCRE2_CFLAGS) -Wall test_spawn_LDADD = libminiexpect.la test_ls_version_SOURCES = test-ls-version.c tests.h miniexpect.h -test_ls_version_CFLAGS = $(PCRE_CFLAGS) -Wall +test_ls_version_CFLAGS = $(PCRE2_CFLAGS) -Wall test_ls_version_LDADD = libminiexpect.la test_multi_match_SOURCES = test-multi-match.c tests.h miniexpect.h -test_multi_match_CFLAGS = $(PCRE_CFLAGS) -Wall +test_multi_match_CFLAGS = $(PCRE2_CFLAGS) -Wall test_multi_match_LDADD = libminiexpect.la # parallel-tests breaks the ability to put 'valgrind' into diff --git a/README b/README index f1f5a95..828c449 100644 --- a/README +++ b/README @@ -3,11 +3,11 @@ miniexpect is a very simple expect-like library for C. It has a saner interface than libexpect, and doesn't depend on Tcl. It is also thread safe, const-correct and uses modern C standards. -It is standalone, except that it requires the PCRE (Perl Compatible -Regular Expressions) library from http://www.pcre.org/. The PCRE +It is standalone, except that it requires the PCRE2 (Perl Compatible +Regular Expressions) library from http://www.pcre.org/. The PCRE2 dependency is fundamental because we want to offer the most powerful regular expression syntax to match on, but more importantly because -PCRE has a convenient way to detect partial matches which made this +PCRE2 has a convenient way to detect partial matches which made this library very simple to implement. License diff --git a/configure.ac b/configure.ac index 4343686..84392fd 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ # miniexpect -# Copyright (C) 2014 Red Hat Inc. +# Copyright (C) 2014-2022 Red Hat Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,7 +15,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -AC_INIT([miniexpect],[1.0]) +AC_INIT([miniexpect],[1.1]) AM_INIT_AUTOMAKE(foreign) dnl NB: Do not [quote] this parameter. @@ -42,8 +42,8 @@ AM_PROG_CC_C_O dnl Check support for 64 bit file offsets. AC_SYS_LARGEFILE -dnl The only dependency is libpcre (Perl Compatible Regular Expressions). -PKG_CHECK_MODULES([PCRE], [libpcre]) +dnl The only dependency is libpcre2 (Perl Compatible Regular Expressions). +PKG_CHECK_MODULES([PCRE2], [libpcre2-8]) dnl Optional for building the manual page. This is part of Perl. AC_CHECK_PROG([POD2MAN], [pod2man], [pod2man], [no]) diff --git a/example-sshpass.c b/example-sshpass.c index b531496..7130ac8 100644 --- a/example-sshpass.c +++ b/example-sshpass.c @@ -1,5 +1,5 @@ /* miniexpect example. - * Copyright (C) 2014 Red Hat Inc. + * Copyright (C) 2014-2022 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -38,11 +38,12 @@ #include #include -#include +#define PCRE2_CODE_UNIT_WIDTH 8 +#include #include "miniexpect.h" -static pcre *compile_re (const char *rex); +static pcre2_code *compile_re (const char *rex); static void usage (void) @@ -59,9 +60,10 @@ main (int argc, char *argv[]) mexp_h *h; const char *password; int status; - pcre *password_re, *prompt_re, *hello_re; - const int ovecsize = 12; - int ovector[ovecsize]; + pcre2_code *password_re, *prompt_re, *hello_re; + pcre2_match_data *match_data; + + match_data = pcre2_match_data_create (4, NULL); while ((opt = getopt (argc, argv, "d")) != -1) { switch (opt) { @@ -96,7 +98,7 @@ main (int argc, char *argv[]) { 100, .re = password_re }, { 0 } }, - ovector, ovecsize)) { + match_data)) { case 100: break; case MEXP_EOF: @@ -140,7 +142,7 @@ main (int argc, char *argv[]) { 101, .re = prompt_re }, { 0 }, }, - ovector, ovecsize)) { + match_data)) { case 100: /* Password. */ fprintf (stderr, "error: ssh asked for password again, probably the password supplied is wrong\n"); goto error; @@ -175,7 +177,7 @@ main (int argc, char *argv[]) { 100, .re = hello_re }, { 0 }, }, - ovector, ovecsize)) { + match_data)) { case 100: break; case MEXP_EOF: @@ -200,7 +202,7 @@ main (int argc, char *argv[]) goto error; } - switch (mexp_expect (h, NULL, NULL, 0)) { + switch (mexp_expect (h, NULL, NULL)) { case MEXP_EOF: /* This is what we're expecting: ssh will close the connection. */ break; @@ -225,6 +227,7 @@ main (int argc, char *argv[]) printf ("test was successful\n"); + pcre2_match_data_free (match_data); exit (EXIT_SUCCESS); error: @@ -233,17 +236,23 @@ main (int argc, char *argv[]) } /* Helper function to compile a PCRE regexp. */ -static pcre * +static pcre2_code * compile_re (const char *rex) { - const char *errptr; - int erroffset; - pcre *ret; + int errorcode; + PCRE2_SIZE erroroffset; + char errormsg[256]; + pcre2_code *ret; - ret = pcre_compile (rex, 0, &errptr, &erroffset, NULL); + ret = pcre2_compile ((PCRE2_SPTR) rex, PCRE2_ZERO_TERMINATED, + 0, &errorcode, &erroroffset, NULL); if (ret == NULL) { - fprintf (stderr, "error: failed to compile regular expression '%s': %s at offset %d\n", - rex, errptr, erroffset); + pcre2_get_error_message (errorcode, + (PCRE2_UCHAR *) errormsg, sizeof errormsg); + fprintf (stderr, "error: " + "failed to compile regular expression '%s': " + "%s at offset %zu\n", + rex, errormsg, erroroffset); exit (EXIT_FAILURE); } return ret; diff --git a/miniexpect.c b/miniexpect.c index 85826a0..77d781c 100644 --- a/miniexpect.c +++ b/miniexpect.c @@ -35,14 +35,8 @@ #include #include -#include - -/* RHEL 6 pcre did not define PCRE_PARTIAL_SOFT. However PCRE_PARTIAL - * is a synonym so use that. - */ -#ifndef PCRE_PARTIAL_SOFT -#define PCRE_PARTIAL_SOFT PCRE_PARTIAL -#endif +#define PCRE2_CODE_UNIT_WIDTH 8 +#include #include "miniexpect.h" @@ -251,7 +245,8 @@ mexp_spawnvf (unsigned flags, const char *file, char **argv) } enum mexp_status -mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize) +mexp_expect (mexp_h *h, const mexp_regexp *regexps, + pcre2_match_data *match_data) { time_t start_t, now_t; int timeout; @@ -347,17 +342,21 @@ mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize) assert (h->buffer != NULL); for (i = 0; regexps[i].r > 0; ++i) { - const int options = regexps[i].options | PCRE_PARTIAL_SOFT; + const int options = regexps[i].options | PCRE2_PARTIAL_SOFT; - r = pcre_exec (regexps[i].re, regexps[i].extra, - h->buffer, (int)h->len, 0, - options, - ovector, ovecsize); + r = pcre2_match (regexps[i].re, + (PCRE2_SPTR) h->buffer, (int)h->len, 0, + options, match_data, NULL); h->pcre_error = r; if (r >= 0) { /* A full match. */ - if (ovector != NULL && ovecsize >= 1 && ovector[1] >= 0) + const PCRE2_SIZE *ovector = NULL; + + if (match_data) + ovector = pcre2_get_ovector_pointer (match_data); + + if (ovector != NULL && ovector[1] >= 0) h->next_match = ovector[1]; else h->next_match = -1; @@ -367,12 +366,12 @@ mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize) return regexps[i].r; } - else if (r == PCRE_ERROR_NOMATCH) { + else if (r == PCRE2_ERROR_NOMATCH) { /* No match at all. */ /* (nothing here) */ } - else if (r == PCRE_ERROR_PARTIAL) { + else if (r == PCRE2_ERROR_PARTIAL) { /* Partial match. Keep the buffer and keep reading. */ can_clear_buffer = 0; } diff --git a/miniexpect.h b/miniexpect.h index 14d8236..2e9b534 100644 --- a/miniexpect.h +++ b/miniexpect.h @@ -1,5 +1,5 @@ /* miniexpect - * Copyright (C) 2014 Red Hat Inc. + * Copyright (C) 2014-2022 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -32,7 +32,8 @@ #include #include -#include +#define PCRE2_CODE_UNIT_WIDTH 8 +#include /* This handle is created per subprocess that is spawned. */ struct mexp_h { @@ -84,8 +85,7 @@ extern int mexp_close (mexp_h *h); /* Expect. */ struct mexp_regexp { int r; - const pcre *re; - const pcre_extra *extra; + const pcre2_code *re; int options; }; typedef struct mexp_regexp mexp_regexp; @@ -98,7 +98,7 @@ enum mexp_status { }; extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps, - int *ovector, int ovecsize); + pcre2_match_data *match_data); /* Sending commands, keypresses. */ extern int mexp_printf (mexp_h *h, const char *fs, ...) diff --git a/miniexpect.pod b/miniexpect.pod index fe76979..d640484 100644 --- a/miniexpect.pod +++ b/miniexpect.pod @@ -8,17 +8,18 @@ miniexpect - A very simple expect library for C. #include #include - #include + #define PCRE2_CODE_UNIT_WIDTH 8 + #include #include mexp_h *h; h = mexp_spawnl ("ssh", "ssh", "host", NULL); - switch (mexp_expect (h, regexps, ovector, ovecsize)) { + switch (mexp_expect (h, regexps, match_data)) { ... } mexp_close (h); - cc prog.c -o prog -lminiexpect -lpcre + cc prog.c -o prog -lminiexpect -lpcre2-8 =head1 DESCRIPTION @@ -30,10 +31,10 @@ Tcl. It is also thread safe, const-correct and uses modern C standards. Miniexpect is a standalone library, except for a single dependency: it -requires the PCRE (Perl Compatible Regular Expressions) library from -L. The PCRE dependency is fundamental because +requires the PCRE2 (Perl Compatible Regular Expressions) library from +L. The PCRE2 dependency is fundamental because we want to offer the most powerful regular expression syntax to match -on, but more importantly because PCRE has a convenient way to detect +on, but more importantly because PCRE2 has a convenient way to detect partial matches which made this library very simple to implement. This manual page documents the API. Examples of how to use the API @@ -152,30 +153,12 @@ The default is 1024. Most callers will not need to change this. B When C [see below] calls the PCRE function -L, it stashes the return value in the C +L, it stashes the return value in the C field in the handle, and that field is returned by this method. -There are two uses for this: - -=over 4 - -=item 1. - If C returns C, then the actual PCRE -error code returned by L is available by calling this -method. For a list of PCRE error codes, see L. - -=item 2. - -A more unusual use is if you ever need to get the captured substrings -from your regular expression (calling L). The -third parameter of that function (C) is the value -returned by L, and so you can call it like this: - - pcre_get_substring (h->buffer, ovector, - mexp_get_pcre_error (h), 1, &matched); - -=back +error code returned by L is available by calling this +method. For a list of PCRE error codes, see L. B @@ -278,10 +261,10 @@ This is how code should check for and print errors from C: =head1 EXPECT FUNCTION Miniexpect contains a powerful regular expression matching function -based on L: +based on L: B +pcre2_match_data *match_data);> The output of the subprocess is matched against the list of PCRE regular expressions in C. C is a list of regular @@ -289,8 +272,7 @@ expression structures: struct mexp_regexp { int r; - const pcre *re; - const pcre_extra *extra; + const pcre2_code *re; int options; }; typedef struct mexp_regexp mexp_regexp; @@ -343,8 +325,8 @@ for EOF or timeout. =item * -C, C, C, C -and C are passed through to the L function. +C, C and C are passed +through to the L function. =item * @@ -387,9 +369,8 @@ compiler, you can just use a local variable instead. mexp_h *h; char *errptr; int offset; - pcre *password_re, *prompt_re; - const int ovecsize = 12; - int ovector[ovecsize]; + pcre2_code *password_re, *prompt_re; + pcre2_match_data *match_data = pcre2_match_data_create (4, NULL); password_re = pcre_compile ("assword", 0, &errptr, &offset, NULL); prompt_re = pcre_compile ("[$#] ", 0, &errptr, &offset, NULL); @@ -399,7 +380,7 @@ compiler, you can just use a local variable instead. { 100, .re = password_re }, { 101, .re = prompt_re }, { 0 }, - }, ovector, ovecsize)) { + }, match_data)) { case 100: /* here you would send a password */ break; @@ -476,9 +457,9 @@ L =head1 SEE ALSO -L, -L, -L, +L, +L, +L, L, L. @@ -493,4 +474,4 @@ your option any later version. =head1 COPYRIGHT -Copyright (C) 2014 Red Hat Inc. +Copyright (C) 2014-2022 Red Hat Inc. diff --git a/test-ls-version.c b/test-ls-version.c index 8880b5e..550f1a4 100644 --- a/test-ls-version.c +++ b/test-ls-version.c @@ -28,7 +28,8 @@ #include #include -#include +#define PCRE2_CODE_UNIT_WIDTH 8 +#include #include "miniexpect.h" #include "tests.h" @@ -38,11 +39,11 @@ main (int argc, char *argv[]) { mexp_h *h; int status, r; - pcre *ls_coreutils_re; - pcre *ls_busybox_re; - const int ovecsize = 12; - int ovector[ovecsize]; - const char *version; + pcre2_code *ls_coreutils_re; + pcre2_code *ls_busybox_re; + pcre2_match_data *match_data = pcre2_match_data_create (4, NULL); + PCRE2_UCHAR *version; + PCRE2_SIZE verlen; ls_coreutils_re = test_compile_re ("^ls.* ([.\\d]+)"); /* Busybox doesn't actually recognize the --version option, but @@ -58,19 +59,18 @@ main (int argc, char *argv[]) { 100, ls_coreutils_re }, { 101, ls_busybox_re }, { 0 }, - }, ovector, ovecsize)) { + }, match_data)) { case 100: case 101: /* Get the matched version number. */ - r = pcre_get_substring (h->buffer, ovector, - mexp_get_pcre_error (h), 1, &version); + r = pcre2_substring_get_bynumber (match_data, 1, &version, &verlen); if (r < 0) { fprintf (stderr, "error: PCRE error reading version substring: %d\n", r); exit (EXIT_FAILURE); } - printf ("ls version = %s\n", version); - pcre_free_substring (version); + printf ("ls version = %s\n", (char *) version); + pcre2_substring_free (version); break; case MEXP_EOF: fprintf (stderr, "error: EOF before matching version string\n"); @@ -94,8 +94,9 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } - pcre_free (ls_coreutils_re); - pcre_free (ls_busybox_re); + pcre2_code_free (ls_coreutils_re); + pcre2_code_free (ls_busybox_re); + pcre2_match_data_free (match_data); exit (EXIT_SUCCESS); } diff --git a/test-multi-match.c b/test-multi-match.c index 5b4a16c..a133d06 100644 --- a/test-multi-match.c +++ b/test-multi-match.c @@ -34,13 +34,12 @@ main (int argc, char *argv[]) int r; int rv[5]; size_t i; - pcre *multi_re = test_compile_re ("multi"); - pcre *match_re = test_compile_re ("match"); - pcre *ing_re = test_compile_re ("ing"); - pcre *str_re = test_compile_re ("str"); - pcre *s_re = test_compile_re ("s"); - const int ovecsize = 12; - int ovector[ovecsize]; + pcre2_code *multi_re = test_compile_re ("multi"); + pcre2_code *match_re = test_compile_re ("match"); + pcre2_code *ing_re = test_compile_re ("ing"); + pcre2_code *str_re = test_compile_re ("str"); + pcre2_code *s_re = test_compile_re ("s"); + pcre2_match_data *match_data = pcre2_match_data_create (4, NULL); /* If the subprocess prints multiple things, we should be able to * repeatedly call mexp_expect to match on each one. This didn't @@ -58,7 +57,7 @@ main (int argc, char *argv[]) { 103, str_re }, { 104, s_re }, { 0 }, - }, ovector, ovecsize); + }, match_data); switch (r) { case 100: case 101: case 102: case 103: case 104: printf ("iteration %zu: matched %d\n", i, r); diff --git a/tests.h b/tests.h index e9b19f3..dc6abe8 100644 --- a/tests.h +++ b/tests.h @@ -1,5 +1,5 @@ /* miniexpect test suite - * Copyright (C) 2014 Red Hat Inc. + * Copyright (C) 2014-2022 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -39,17 +39,24 @@ test_diagnose (int status) fprintf (stderr, "stopped by signal %d", WSTOPSIG (status)); } -static pcre * +__attribute__((__unused__)) +static pcre2_code * test_compile_re (const char *rex) { - const char *errptr; - int erroffset; - pcre *ret; + int errorcode; + PCRE2_SIZE erroroffset; + char errormsg[256]; + pcre2_code *ret; - ret = pcre_compile (rex, 0, &errptr, &erroffset, NULL); + ret = pcre2_compile ((PCRE2_SPTR) rex, PCRE2_ZERO_TERMINATED, + 0, &errorcode, &erroroffset, NULL); if (ret == NULL) { - fprintf (stderr, "error: failed to compile regular expression '%s': %s at offset %d\n", - rex, errptr, erroffset); + pcre2_get_error_message (errorcode, + (PCRE2_UCHAR *) errormsg, sizeof errormsg); + fprintf (stderr, "error: " + "failed to compile regular expression '%s': " + "%s at offset %zu\n", + rex, errormsg, erroroffset); exit (EXIT_FAILURE); } return ret; -- 1.8.3.1