From f5d365ec44b9e04c03c7e97e94080bcd134f319a Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 26 Apr 2014 11:50:13 +0100 Subject: [PATCH] Add a man page. Move all the documentation from the header file to the man page. --- .gitignore | 1 + Makefile.am | 16 ++- README | 8 +- configure.ac | 4 + miniexpect.h | 125 ++++------------------ miniexpect.pod | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 380 insertions(+), 106 deletions(-) create mode 100644 miniexpect.pod diff --git a/.gitignore b/.gitignore index 60d02d0..e8814ae 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ /libtool /ltmain.sh /m4/ +/miniexpect.3 /missing /stamp-h1 /test-driver diff --git a/Makefile.am b/Makefile.am index 80e4053..6139816 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ ACLOCAL_AMFLAGS = -I m4 -EXTRA_DIST = +EXTRA_DIST = miniexpect.3 # The library. @@ -45,6 +45,20 @@ test_spawn_SOURCES = test-spawn.c test_spawn_CFLAGS = $(PCRE_CFLAGS) -Wall test_spawn_LDADD = libminiexpect.la +# Man pages. + +man_MANS = miniexpect.3 + +if HAVE_POD2MAN +miniexpect.3: miniexpect.pod + $(POD2MAN) \ + --section 3 \ + --release "$(PACKAGE)-$(VERSION)" \ + -c "Library functions" \ + $< > $@-t + mv $@-t $@ +endif + # Clean. CLEANFILES = *~ diff --git a/README b/README index f739940..f1f5a95 100644 --- a/README +++ b/README @@ -21,7 +21,11 @@ Source is available from: http://git.annexia.org/?p=miniexpect.git;a=summary Using the library ----------------- -The API is described in miniexpect.h. +If you wanted to copy the library into your own code (instead of +linking to it as a dependency), you only need to copy the two files: +miniexpect.h, miniexpect.c. -To understand how to use the API, take a look at the examples and +The API is documented in the manual page (miniexpect.pod / miniexpect.3). + +For examples of how to use the API in reality, see the examples and tests in the source directory. diff --git a/configure.ac b/configure.ac index 4edc425..6051e3e 100644 --- a/configure.ac +++ b/configure.ac @@ -45,6 +45,10 @@ AC_SYS_LARGEFILE dnl The only dependency is libpcre (Perl Compatible Regular Expressions). PKG_CHECK_MODULES([PCRE], [libpcre]) +dnl Optional for building the manual page. This is part of Perl. +AC_CHECK_PROG([POD2MAN], [pod2man], [pod2man], [no]) +AM_CONDITIONAL([HAVE_POD2MAN], [test "x$POD2MAN" != "xno"]) + dnl Produce output files. AC_CONFIG_HEADERS([config.h]) diff --git a/miniexpect.h b/miniexpect.h index ba3607a..31b7269 100644 --- a/miniexpect.h +++ b/miniexpect.h @@ -16,6 +16,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/* ** NOTE ** All API documentation is in the manual page. + * + * To read the manual page from the source directory, do: + * man ./miniexpect.3 + * If you have installed miniexpect, do: + * man 3 miniexpect + * + * The source for the manual page is miniexpect.pod. + */ + #ifndef MINIEXPECT_H_ #define MINIEXPECT_H_ @@ -25,87 +35,33 @@ /* This handle is created per subprocess that is spawned. */ struct mexp_h { - int fd; /* File descriptor pointing to pty. */ - pid_t pid; /* Subprocess PID. */ - - /* Timeout (milliseconds, 1/1000th seconds). The caller may set - * this before calling mexp_expect. Set it to -1 to mean no - * timeout. The default is 60000 (= 60 seconds). - */ + int fd; + pid_t pid; int timeout; - - /* The read buffer is allocated by the library when mexp_expect is - * called. It is available so you can examine the buffer to see - * what part of the regexp matched. Note this buffer does not - * contain the full input from the process, but it will contain at - * least the part matched by the regular expression (and maybe some - * more). - */ - char *buffer; /* Read buffer. */ - size_t len; /* Length of data in the buffer. */ - size_t alloc; /* Allocated size of the buffer. */ - - /* The caller may set this to set the size (in bytes) for reads from - * the subprocess. The default is 1024. - */ + char *buffer; + size_t len; + size_t alloc; size_t read_size; - - /* If mexp_expect returns MEXP_PCRE_ERROR, then the actual PCRE - * error code is returned here. See pcre_exec(3) for details. - */ int pcre_error; - - /* Opaque pointers for use of the caller. The library will not - * touch these. - */ void *user1; void *user2; void *user3; }; typedef struct mexp_h mexp_h; -/* Spawn a subprocess. - * - * If successful it returns a handle. If it fails, it returns NULL - * and sets errno. - */ +/* Spawn a subprocess. */ extern mexp_h *mexp_spawnv (const char *file, char **argv); - -/* Same as mexp_spawnv, but it uses a NULL-terminated variable length - * list of arguments. - */ extern mexp_h *mexp_spawnl (const char *file, const char *arg, ...); -/* Close the handle and clean up the subprocess. - * - * This returns: - * 0: successful close, subprocess exited cleanly. - * -1: error in system call, see errno. - * > 0: exit status of subprocess if it didn't exit cleanly. Use - * WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG etc macros to - * examine this. - * - * Notes: - * - * - Even in the error cases, the handle is always closed and - * freed by this call. - * - * - It is normal for the kernel to send SIGHUP to the subprocess. - * If the subprocess doesn't catch the SIGHUP, then it will die - * (WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP). This - * case should not necessarily be considered an error. - */ +/* Close the handle. */ extern int mexp_close (mexp_h *h); -/* The list of regular expressions passed to mexp_expect. */ +/* Expect. */ struct mexp_regexp { - int r; /* The returned value from mexp_expect - * if this regular expression matches. - * MUST be > 0. - */ - const pcre *re; /* The compiled regular expression. */ - const pcre_extra *extra; /* See pcre_exec. */ - int options; /* See pcre_exec. */ + int r; + const pcre *re; + const pcre_extra *extra; + int options; }; typedef struct mexp_regexp mexp_regexp; @@ -116,46 +72,9 @@ enum mexp_status { MEXP_PCRE_ERROR = -3, }; -/* Expect some output from the subprocess. Match the output against - * the PCRE regular expression(s) in the list, and return which one - * matched. - * - * See example-sshpass.c for an example of how to pass in regular - * expressions. - * - * This can return: - * - * MEXP_TIMEOUT: - * No input matched before the timeout (mexp_h->timeout) was reached. - * MEXP_EOF: - * The subprocess closed the connection. - * MEXP_ERROR: - * There was a system call error (eg. from the read call). See errno. - * MEXP_PCRE_ERROR - * There was a pcre_exec error. h->pcre_error is set to the error code - * (see pcreapi(3) for a list of PCRE_* error codes and what they mean). - * - * Notes: - * - * - 'regexps' may be NULL or an empty list, which means we don't - * match against a regular expression. This is useful if you just - * want to wait for EOF or timeout. - * - * - 'regexps[].re', 'regexps[].extra', 'regexps[].options', 'ovector' - * and 'ovecsize' are passed through to the pcre_exec function. - */ extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize); -/* This is a convenience function for writing something (eg. a - * password or command) to the subprocess. You could do this by - * writing directly to 'h->fd', but this function does all the error - * checking for you. - * - * Returns the number of bytes if the whole message was written OK - * (partial writes are not possible with this function), or -1 if - * there was an error (check errno). - */ extern int mexp_printf (mexp_h *h, const char *fs, ...) __attribute__((format(printf,2,3))); diff --git a/miniexpect.pod b/miniexpect.pod new file mode 100644 index 0000000..f17cda9 --- /dev/null +++ b/miniexpect.pod @@ -0,0 +1,332 @@ +=encoding utf8 + +=head1 NAME + +miniexpect - A very simple expect library for C. + +=head1 SYNOPSIS + + #include + #include + #include + #include + + mexp_h *h; + h = mexp_spawnl ("ssh", "ssh", "host"); + switch (mexp_expect (h, regexps, ovector, ovecsize)) { + ... + } + mexp_close (h); + + cc prog.c -o prog -lminiexpect + +=head1 DESCRIPTION + +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 +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 +library very simple to implement. + +This manual page documents the API. Examples of how to use the API +can be found in the source directory. + +=head1 CONCEPTS + +Miniexpect lets you start up an external program, control it (by +sending commands to it), and close it down gracefully. Two things +make this different from other APIs like L and L: +Firstly miniexpect creates a pseudoterminal (pty). Secondly +miniexpect lets you match the output of the program using regular +expressions. Both of these are handy for controlling interactive +programs that might (for example) ask for passwords, but you can use +miniexpect on just about any external program. + +You can control multiple programs at the same time. + +=head1 SPAWNING THE SUBPROCESS + +There are two calls for creating a subprocess: + +B + +This creates a subprocess running the external program C (the +current C<$PATH> is searched unless you give an absolute path). +C are the arguments to the program. Usually the first +argument should be the name of the program. + +The return value is a handle (see next section). + +If there was an error running the subprocess, C is returned and +the error is available in C. + +For example, to run an ssh subprocess you could do: + + h = mexp_spawnl ("ssh", "ssh", "-l", "root", "host"); + +or to run a particular ssh binary: + + h = mexp_spawnl ("/usr/local/bin/ssh", "ssh", "-l", "root", "host"); + +An alternative to C is: + +B + +This is the same as C except that you pass the arguments +in a NULL-terminated array. + +=head1 HANDLES + +After spawning a subprocess, you get back a handle. There are various +fields in this handle which you can read or write: + + struct mexp_h { + int fd; + pid_t pid; + +C is the pty of the subprocess. You can read and write to this if +you want, although convenience functions are also provided (see +below). C is the process ID of the subprocess. You can send it +signals if you want. + + int timeout; + +C is the timeout in milliseconds (1/1000th of a second) used +by C (see below). You can set this before calling +C if you want. Setting it to -1 means no timeout. The +default setting is 60000 (60 seconds). + + char *buffer; + size_t len; + size_t alloc; + +If C returns a match then these variables contain the +read buffer. Note this buffer does not contain the full input from +the process, but it will contain at least the part matched by the +regular expression (and maybe some more). C is the read +buffer and C is the number of bytes of data in the buffer. + + size_t read_size; + +Callers may set this to the natural size (in bytes) for reads from the +subprocess. The default is 1024. Most callers will not need to +change this. + + int pcre_error; + +If C returns C, then the actual PCRE +error code returned by L is available here. For a list +of PCRE error codes, see L. + + void *user1; + void *user2; + void *user3; + +Opaque pointers for use by the caller. The library will not touch +these. + + }; + + typedef struct mexp_h mexp_h; + +=head1 CLOSING THE HANDLE + +To close the handle and clean up the subprocess, call: + +B + +This returns the status code from the subprocess. This is in the form +of a L/L status so you have to use the macros +C, C, C, C etc defined +in Csys/wait.hE> to parse it. + +If there was a system call error, then C<-1> is returned. The error +will be in C. + +Notes: + +=over 4 + +=item * + +Even in error cases, the handle is always closed and its memory is +freed by this call. + +=item * + +It is normal for the kernel to send SIGHUP to the subprocess. + +If the subprocess doesn't catch the SIGHUP, then it will die +with status: + + WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP + +This case should not necessarily be considered an error. + +=back + +=head1 EXPECT FUNCTION + +Miniexpect contains a powerful regular expression matching function +based on L: + +B + +The output of the subprocess is matched against the list of PCRE +regular expressions in C. C is a list of regular +expression structures: + + struct mexp_regexp { + int r; + const pcre *re; + const pcre_extra *extra; + int options; + }; + typedef struct mexp_regexp mexp_regexp; + +C is the integer code returned from C if this regular +expression matches. It B be E 0. C indicates the +end of the list of regular expressions. C is the compiled regular +expression. + +Possible return values are: + +=over 4 + +=item C + +No input matched before the timeout (Ctimeout>) was +reached. + +=item C + +The subprocess closed the connection. + +=item C + +There was a system call error (eg. from the read call). The error is +returned in C. + +=item C + +There was a C error. Cpcre_error> is set to the +error code. See L for a list of the C error codes +and what they mean. + +=item C E 0 + +If any regexp matches, the associated integer code (C) +is returned. + +=back + +Notes: + +=over 4 + +=item * + +C may be NULL or an empty list, which means we don't match +against a regular expression. This is useful if you just want to wait +for EOF or timeout. + +=item * + +C, C, C, C +and C are passed through to the L function. + +=back + +=head2 mexp_expect example + +It is easier to understand C by considering a simple +example. + +In this example we are waiting for ssh to either send us a password +prompt, or (if no password was required) a command prompt, and based +on the output we will either send back a password or a command. + +The unusual C<(mexp_regexp[]){...}> syntax is called a "compound +literal" and is available in C99. If you need to use an older +compiler, you can just use a local variable instead. + + mexp_h *h; + char *errptr; + int offset; + pcre *password_re, *prompt_re; + int ovecsize = 12; + int ovector[ovecsize]; + + password_re = pcre_compile ("assword", 0, &errptr, &offset, NULL); + prompt_re = pcre_compile ("[$#] ", 0, &errptr, &offset, NULL); + + switch (mexp_expect (h, + (mexp_regexp[]) { + { 100, .re = password_re }, + { 101, .re = prompt_re }, + { 0 }, + }, ovector, ovecsize)) { + case 100: + /* here you would send a password */ + break; + case 101: + /* here you would send a command */ + break; + case MEXP_EOF: + fprintf (stderr, "error: ssh closed the connection unexpectedly\n"); + exit (EXIT_FAILURE); + case MEXP_TIMEOUT: + fprintf (stderr, "error: timeout before reaching the prompt\n"); + exit (EXIT_FAILURE); + case MEXP_ERROR: + perror ("mexp_expect"); + exit (EXIT_FAILURE); + case MEXP_PCRE_ERROR: + fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error); + exit (EXIT_FAILURE); + } + +=head1 SENDING COMMANDS TO THE SUBPROCESS + +You can write to the subprocess simply by writing to Cfd>. +However we also provide a convenience function: + +B + +This returns the number of bytes, if the whole message was written OK. +If there was an error, -1 is returned and the error is available in +C. Note that this function will not do a partial write. If it +cannot write all the data, then it will return an error. + +=head1 SOURCE + +Source is available from: +L + +=head1 SEE ALSO + +L, +L, +L, +L, +L. + +=head1 AUTHORS + +Richard W.M. Jones (C) + +=head1 LICENSE + +The library is released under the Library GPL (LGPL) version 2 or at +your option any later version. + +=head1 COPYRIGHT + +Copyright (C) 2014 Red Hat Inc. -- 1.8.3.1