diff options
Diffstat (limited to 'libtecla-1.6.3/demo3.c')
-rw-r--r-- | libtecla-1.6.3/demo3.c | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/libtecla-1.6.3/demo3.c b/libtecla-1.6.3/demo3.c new file mode 100644 index 0000000..7dff98a --- /dev/null +++ b/libtecla-1.6.3/demo3.c @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2002, 2003, 2004, 2012 by Martin C. Shepherd + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL + * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder + * shall not be used in advertising or otherwise to promote the sale, use + * or other dealings in this Software without prior written authorization + * of the copyright holder. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include <locale.h> +#include <setjmp.h> + +#ifdef HAVE_SELECT +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <signal.h> + +#include "libtecla.h" + +/* + * The SignalActions object provides a way to temporarily install + * a signal handler to a given set of signals, and later restore all + * of the signal handlers that this displaced. + */ +typedef struct { + int nsignal; /* The number of signals on the host OS */ + sigset_t mask; /* The set of signals who's signal handlers */ + /* are stored in the following actions[] */ + /* array. */ + struct sigaction *actions; /* An array of nsignal actions */ +} SignalActions; + +static SignalActions *new_SignalActions(void); +static SignalActions *del_SignalActions(SignalActions *si); +static int displace_signal_handlers(SignalActions *si, sigset_t *mask, + void (*handler)(int)); +static int reinstate_signal_handlers(SignalActions *si); + +/* Return resources, restore the terminal to a usable state and exit */ + +static void cleanup_and_exit(GetLine *gl, SignalActions *si, int status); + +/* The function which displays the introductory text of the demo */ + +static void show_demo_introduction(GetLine *gl); + +/* A signal-aware version of select() */ + +static int demo_sigselect(int n, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout, + sigset_t *mask, SignalActions *si); + +/* + * The following variables are accessed from signal handlers. Note + * that these variables don't need to be either volatile or + * sig_atomic_t because: + * + * 1. Outside of signal handlers we only access them when signal + * delivery is blocked, so we know that no signal handlers can + * be accessing them at that time. + * + * 2. When the signal handlers that set these variables are installed, + * the sa_mask member of the sigaction structure is used to ensure + * that only one instance of these signal handlers can be running + * at a time, so we also know that there can't be simultaneous + * accesses to them by multiple signal handlers. + */ +static GetLine *demo_gl; /* The line editor object */ +static sigjmp_buf demo_setjmp_buffer; /* The sigsetjmp() buffer */ +static int demo_setjmp_signo = -1; /* The signal that was caught */ + +/* Signal handlers */ + +static void demo_signal_handler(int signo); +static void demo_setjmp_handler(int signo); + +/* + * Set the amount of time that gl_get_line() should wait for I/O before + * returning to let the external event loop continue. + */ +#define DEMO_IO_TIMEOUT 100000000 /* ns => 100ms */ + +/* The timeout handler */ + +static GL_TIMEOUT_FN(demo_timeout_fn); + +/*....................................................................... + * This program demonstrates the use of gl_get_line() from an external + * event loop. It takes no arguments. + */ +int main(int argc, char *argv[]) +{ + int major,minor,micro; /* The version number of the library */ + GetLine *gl=NULL; /* The resource object of gl_get_line() */ + SignalActions *si=NULL; /* Temporary storage of displaced signal */ + /* handlers. */ + sigset_t all_signal_mask; /* The set of signals known by gl_get_line() */ +/* + * This program requires select(). + */ +#if !defined(HAVE_SELECT) + fprintf(stderr, "The select() system call isn't available - aborting.\n"); + exit(1); +#else +/* + * Create the line editor, specifying a maximum line length of 500 bytes, + * and 10000 bytes to allocate to storage of historical input lines. + */ + gl = demo_gl = new_GetLine(500, 5000); + if(!gl) + cleanup_and_exit(gl, si, 1); + +/* + * Allocate an object in which to temporarily record displaced + * signal handlers. + */ + si = new_SignalActions(); + if(!si) + cleanup_and_exit(gl, si, 1); +/* + * If the user has the LC_CTYPE or LC_ALL environment variables set, + * enable display of characters corresponding to the specified locale. + */ + (void) setlocale(LC_CTYPE, ""); +/* + * Lookup and display the version number of the library. + */ + libtecla_version(&major, &minor, µ); + printf( + "\n Welcome to the server-mode demo program of libtecla version %d.%d.%d\n", + major, minor, micro); +/* + * Display some introductory text, left-justifying it within the current + * width of the terminal and enclosing it in a box of asterixes. + */ + show_demo_introduction(gl); +/* + * Load history. + */ +#ifndef WITHOUT_FILE_SYSTEM + (void) gl_load_history(gl, "~/.demo_history", "#"); +#endif +/* + * In this demo, rather than having gl_get_line() return immediately + * when it would otherwise have to wait for I/O, we register a timeout + * callback which causes gl_get_line() to give up waiting after a short + * interval. + */ + gl_inactivity_timeout(gl, demo_timeout_fn, NULL, 0, DEMO_IO_TIMEOUT); +/* + * Install our signal handlers for process termination, suspension and + * terminal resize signals. Ignore process continuation signals. + */ + gl_tty_signals(demo_signal_handler, demo_signal_handler, SIG_DFL, + demo_signal_handler); +/* + * Get a list of all of the signals that gl_get_line() currently catches. + */ + gl_list_signals(gl, &all_signal_mask); +/* + * Switch gl_get_line() to non-blocking server mode. + */ + if(gl_io_mode(gl, GL_SERVER_MODE)) + cleanup_and_exit(gl, si, 1); +/* + * Instruct gl_get_line() to unblock any signals that it catches + * while waiting for input. Note that in non-blocking server mode, + * this is only necessary when using gl_inactivity_timeout() to make + * gl_get_line() block for a non-zero amount of time. + */ + gl_catch_blocked(gl); +/* + * Enter the event loop. + */ + while(1) { + int nready; /* The number of file-descriptors that are */ + /* ready for I/O */ + fd_set rfds; /* The set of file descriptors to watch for */ + /* readability */ + fd_set wfds; /* The set of file descriptors to watch for */ + /* writability */ +/* + * Construct the sets of file descriptors to be watched by select(), + * starting from empty sets. + */ + FD_ZERO(&rfds); + FD_ZERO(&wfds); +/* + * To ensure that no signals are received whos handlers might change + * the requirements for the contents of the above signal sets, block + * all of the signals that we are handling. + */ + sigprocmask(SIG_BLOCK, &all_signal_mask, NULL); +/* + * Depending on which direction of I/O gl_get_line()s is currently + * waiting for, add the terminal file descriptor to either the set + * of file descriptors to watch for readability, or those to watch + * for writability. Note that at the start of a new line, such as + * after an error, or the return of a completed line, we need to + * wait for writability, so that a prompt can be written. + */ + switch(gl_pending_io(gl)) { + case GLP_READ: + FD_SET(STDIN_FILENO, &rfds); + break; + default: + FD_SET(STDIN_FILENO, &wfds); + break; + }; +/* + * Wait for I/O to become possible on the selected file descriptors. + * The following is a signal-aware wrapper around the select() system + * call. This wrapper guarantees that if any of the signals marked in + * all_signal_mask arrive after the statement above where we blocked + * these signals, it will detect this and abort with nready=-1 and + * errno=EINTR. If instead, we just unblocked the above signals just + * before calling a normal call to select(), there would be a small + * window of time between those two statements in which a signal could + * arrive without aborting select(). This would be a problem, since + * the functions called by our signal handler may change the type + * of I/O that gl_get_line() wants us to wait for in select(). + */ + nready = demo_sigselect(STDIN_FILENO + 1, &rfds, &wfds, NULL, NULL, + &all_signal_mask, si); +/* + * We can now unblock our signals again. + */ + sigprocmask(SIG_UNBLOCK, &all_signal_mask, NULL); +/* + * Did an I/O error occur? + */ + if(nready < 0 && errno != EINTR) + cleanup_and_exit(gl, si, 1); +/* + * If the terminal file descriptor is now ready for I/O, call + * gl_get_line() to continue editing the current input line. + */ + if(FD_ISSET(STDIN_FILENO, &rfds) || FD_ISSET(STDIN_FILENO, &wfds)) { +/* + * Start or continue editing an input line. + */ + char *line = gl_get_line(gl, "$ ", NULL, 0); +/* + * Did the user finish entering a new line? + */ + if(line) { +/* + * Before writing messages to the terminal, start a new line and + * switch back to normal terminal I/O. + */ + gl_normal_io(gl); +/* + * Display what was entered. + */ + if(printf("You entered: %s", line) < 0 || fflush(stdout)) + break; +/* + * Implement a few simple commands. + */ + if(strcmp(line, "exit\n")==0) + cleanup_and_exit(gl, si, 0); + else if(strcmp(line, "history\n")==0) + gl_show_history(gl, stdout, "%N %T %H\n", 0, -1); + else if(strcmp(line, "size\n")==0) { + GlTerminalSize size = gl_terminal_size(gl, 80, 24); + printf("Terminal size = %d columns x %d lines.\n", size.ncolumn, + size.nline); + } else if(strcmp(line, "clear\n")==0) { + if(gl_erase_terminal(gl)) + return 1; + }; +/* + * To resume command-line editing, return the terminal to raw, + * non-blocking I/O mode. + */ + gl_raw_io(gl); +/* + * If gl_get_line() returned NULL because of an error or end-of-file, + * abort the program. + */ + } else if(gl_return_status(gl) == GLR_ERROR || + gl_return_status(gl) == GLR_EOF) { + cleanup_and_exit(gl, si, 1); + }; + }; + }; +#endif + return 0; +} + +/*....................................................................... + * This function is called to return resources to the system and restore + * the terminal to its original state before exiting the process. + * + * Input: + * gl GetLine * The line editor. + * si SignalActions * The repository for displaced signal handlers. + * status int The exit code of the process. + */ +static void cleanup_and_exit(GetLine *gl, SignalActions *si, int status) +{ +/* + * Restore the terminal to its original state before exiting the program. + */ + gl_normal_io(gl); +/* + * Save historical command lines. + */ +#ifndef WITHOUT_FILE_SYSTEM + (void) gl_save_history(gl, "~/.demo_history", "#", -1); +#endif +/* + * Clean up. + */ + gl = del_GetLine(gl); + si = del_SignalActions(si); +/* + * Exit the process. + */ + exit(status); +} + +/*....................................................................... + * This is a signal-aware wrapper around the select() system call. It + * is designed to facilitate reliable signal handling of a given set + * of signals, without the race conditions that would usually surround + * the use of select(). See the "RELIABLE SIGNAL HANDLING" section of + * the gl_get_line(3) man page for further details. + * + * Provided that the calling function has blocked the specified set of + * signals before calling this function, this function guarantees that + * select() will be aborted by any signal that arrives between the + * time that the caller blocked the specified signals and this + * function returns. On return these signals will again be blocked to + * prevent any signals that arrive after select() returns, from being + * missed by the caller. + * + * Note that this function is written not to be specific to this + * program, and is thus suitable for use in other programs, whether or + * not they use gl_get_line(). + * + * Also note that this function depends on the NSIG preprocessor + * constant being >= the maximum number of signals available on the + * host operating system. Under BSD and SysV, this macro is set + * appropriately in signal.h. On other systems, a reasonably large + * guess should be substituted. Although nothing terrible will happen + * if a value that is too small is chosen, signal numbers that exceed + * the specified value of NSIG will be ignored by this function. A + * more robust method than depending on nsig would be to use the + * POSIX sigismember() function to count valid signals, and use this + * to allocate the array of sigaction structures used to preserve + * + * + * Input: + * n int The number of file descriptors to pay + * attention to at the start of each of the + * following sets of file descriptors. + * readfds fd_set * The set of file descriptors to check for + * readability, or NULL if not pertinent. + * wwritefds fd_set * The set of file descriptors to check for + * writability, or NULL if not pertinent. + * exceptfds fd_set * The set of file descriptors to check for + * the arrival of urgent data, or NULL if + * not pertinent. + * timeout struct timeval * The maximum time that select() should + * wait, or NULL to wait forever. + * mask sigset_t * The set of signals to catch. + * si SignalHandlers * An object in which to preserve temporary + * copies signal handlers. + * Output: + * return int > 0 The number of entries in all of the + * sets of descriptors that are ready + * for I/O. + * 0 Select() timed out. + * -1 Error (see errno). + */ +static int demo_sigselect(int n, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout, + sigset_t *mask, SignalActions *si) +{ +/* + * The reason that the the following variables are marked as volatile + * is to prevent the compiler from placing their values in registers + * that might not be saved and restored by sigsetjmp(). + */ + volatile sigset_t old_mask; /* The displaced process signal mask */ + volatile int status; /* The return value of select() */ +/* + * Make sure that all of the specified signals are blocked. This is + * redundant if the caller has already blocked signals. + */ + if(sigprocmask(SIG_BLOCK, mask, (sigset_t *) &old_mask) < 0) + return -1; +/* + * Record the fact that no signal has been caught yet. + */ + demo_setjmp_signo = -1; +/* + * Now set up the point where our temporary signal handlers will return + * control if a signal is received. + */ + if(sigsetjmp(demo_setjmp_buffer, 1) == 0) { +/* + * Now install the temporary signal handlers that cause the above + * sigsetjmp() to return non-zero when a signal is detected. + */ + if(displace_signal_handlers(si, mask, demo_setjmp_handler)) { + reinstate_signal_handlers(si); + return 1; + }; +/* + * Now that we are ready to catch the signals, unblock them. + */ + sigprocmask(SIG_UNBLOCK, mask, NULL); +/* + * At last, call select(). + */ + status = select(n, readfds, writefds, exceptfds, timeout); +/* + * Block the specified signals again. + */ + sigprocmask(SIG_BLOCK, mask, NULL); +/* + * Record the fact that no signal was caught. + */ + demo_setjmp_signo = -1; + }; +/* + * We can get to this point in one of two ways. Either no signals were + * caught, and the above block ran to completion (with demo_setjmp_signo=-1), + * or a signal was caught that caused the above block to be aborted, + * in which case demo_setjmp_signo will now equal the number of the signal that + * was caught, and sigsetjmp() will have restored the process signal + * mask to how it was before it was called (ie. all of the specified + * signals blocked). + * + * First restore the signal handlers to how they were on entry to + * this function. + */ + reinstate_signal_handlers(si); +/* + * Was a signal caught? + */ + if(demo_setjmp_signo > 0) { + sigset_t new_mask; +/* + * Send the signal again, then unblock its delivery, so that the application's + * signal handler gets invoked. + */ + raise(demo_setjmp_signo); + sigemptyset(&new_mask); + sigaddset(&new_mask, demo_setjmp_signo); + sigprocmask(SIG_UNBLOCK, &new_mask, NULL); +/* + * Set the return status to show that a signal was caught. + */ + errno = EINTR; + status = -1; + }; +/* + * Now restore the process signal mask to how it was on entry to this + * function. + */ + sigprocmask(SIG_SETMASK, (sigset_t *) &old_mask, NULL); + return status; +} + +/*....................................................................... + * This is the main signal handler of this demonstration program. If a + * SIGINT is received by the process, it arranges that the next call + * to gl_get_line() will abort entry of the current line and start + * entering a new one. Otherwise it calls the library function which + * handles terminal resize signals and process suspension and process + * termination signals. Both of the functions called by this signal + * handler are designed to be async-signal safe, provided that the + * rules laid out in the gl_io_mode(3) man page are followed. + */ +static void demo_signal_handler(int signo) +{ + if(signo==SIGINT) + gl_abandon_line(demo_gl); + else + gl_handle_signal(signo, demo_gl, 1); +} + +/*....................................................................... + * The following signal handler is installed while select() is being + * called from within a block of code protected by sigsetjmp(). It + * simply records the signal that was caught in setjmp_signo, then + * causes the sigsetjmp() to return non-zero. + */ +static void demo_setjmp_handler(int signo) +{ + demo_setjmp_signo = signo; + siglongjmp(demo_setjmp_buffer, 1); +} + +/*....................................................................... + * This optional inactivity timeout function is used in this + * demonstration to cause gl_get_line() to wait for a small amount of + * time for I/O, before returning and allowing the event loop to + * continue. This isn't needed if you want gl_get_line() to return + * immediately, rather than blocking. + */ +static GL_TIMEOUT_FN(demo_timeout_fn) +{ + return GLTO_CONTINUE; +} + +/*....................................................................... + * Display introductory text to the user, formatted according to the + * current terminal width and enclosed in a box of asterixes. + * + * Input: + * gl GetLine * The resource object of gl_get_line(). + */ +static void show_demo_introduction(GetLine *gl) +{ + int start; /* The column in which gl_display_text() left the cursor */ + int i; +/* + * Break the indtroductory text into an array of strings, so as to + * avoid overflowing any compiler string limits. + */ + const char *doc[] = { + "To the user this program appears to act identically to the main ", + "demo program. However whereas the code underlying the main demo ", + "program uses gl_get_line() in its default configuration, where each ", + "call blocks the caller until the user has entered a complete input ", + "line, demo3 uses gl_get_line() in its non-blocking server mode, ", + "where it must be called repeatedly from an external ", + "event loop to incrementally accept entry of the input ", + "line, as and when terminal I/O becomes possible. The well commented ", + "source code of demo3, which can be found in demo3.c, thus provides ", + "a working example of how to use gl_get_line() in a manner that ", + "doesn't block the caller. Documentation of this mode can be found ", + "in the gl_io_mode(3) man page.\n" + }; +/* + * Form the top line of the documentation box by filling the area of + * the line between a " *" prefix and a "* " suffix with asterixes. + */ + printf("\n"); + gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); +/* + * Justify the documentation text within margins of asterixes. + */ + for(start=0,i=0; i<sizeof(doc)/sizeof(doc[0]) && start >= 0; i++) + start = gl_display_text(gl, 0, " * ", " * ", ' ', 80, start,doc[i]); +/* + * Draw the bottom line of the documentation box. + */ + gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); + printf("\n"); +} + + +/*....................................................................... + * This is a constructor function for an object who's role is to allow + * a signal handler to be assigned to potentially all available signals, + * while preserving a copy of the original signal handlers, for later + * restration. + * + * Output: + * return SignalActions * The new object, or NULL on error. + */ +static SignalActions *new_SignalActions(void) +{ + SignalActions *si; /* The object to be returned */ +/* + * Allocate the container. + */ + si = malloc(sizeof(SignalActions)); + if(!si) { + fprintf(stderr, "new_SignalActions: Insufficient memory.\n"); + return NULL; + }; +/* + * Before attempting any operation that might fail, initialize the + * container at least up to the point at which it can safely be passed + * to del_SignalActions(). + */ + si->nsignal = 0; + sigemptyset(&si->mask); + si->actions = NULL; +/* + * Count the number of signals that are available of the host + * platform. Note that si->mask has no members set, and that + * sigismember() is defined to return -1 if the signal number + * isn't valid. + */ + for(si->nsignal=1; sigismember(&si->mask, si->nsignal) == 0; si->nsignal++) + ; +/* + * Allocate the array of sigaction structures to use to keep a record + * of displaced signal handlers. + */ + si->actions = (struct sigaction *) malloc(sizeof(*si->actions) * si->nsignal); + if(!si->actions) { + fprintf(stderr, "Insufficient memory for %d sigaction structures.\n", + si->nsignal); + return del_SignalActions(si); + }; + return si; +} + +/*....................................................................... + * Delete a SignalActions object. + * + * Input: + * si SignalActions * The object to be deleted. + * Output: + * return SignalActions * The deleted object (always NULL). + */ +static SignalActions *del_SignalActions(SignalActions *si) +{ + if(si) { + if(si->actions) + free(si->actions); + free(si); + }; + return NULL; +} + +/*....................................................................... + * Replace the signal handlers of all of the signals in 'mask' with + * the signal handler 'handler'. + * + * Input: + * si SignalActions * The object in which to record the displaced + * signal handlers. + * mask sigset_t * The set of signals who's signal handlers + * should be displaced. + * handler void (*handler)(int) The new signal handler to assign to each + * of the signals marked in 'mask'. + * Output: + * return int 0 - OK. + * 1 - Error. + */ +static int displace_signal_handlers(SignalActions *si, sigset_t *mask, + void (*handler)(int)) +{ + int signo; /* A signal number */ + struct sigaction action; /* The new signal handler */ +/* + * Mark the fact that so far we haven't displaced any signal handlers. + */ + sigemptyset(&si->mask); +/* + * Set up the description of the new signal handler. Note that + * we make sa_mask=mask. This ensures that only one instance of the + * signal handler will ever be running at one time. + */ + action.sa_handler = handler; + memcpy(&action.sa_mask, mask, sizeof(*mask)); + action.sa_flags = 0; +/* + * Check each of the available signals to see if it is specified in 'mask'. + * If so, install the new signal handler, record the displaced one in + * the corresponding element of si->actions[], and make a record in + * si->mask that this signal handler has been displaced. + */ + for(signo=1; signo < si->nsignal; signo++) { + if(sigismember(mask, signo)) { + if(sigaction(signo, &action, &si->actions[signo]) < 0) { + fprintf(stderr, "sigaction error (%s)\n", strerror(errno)); + return 1; + }; + sigaddset(&si->mask, signo); + }; + }; + return 0; +} + +/*....................................................................... + * Reinstate any signal handlers displaced by displace_signal_handlers(). + * + * Input: + * sig SignalActions * The object containing the displaced signal + * handlers. + * Output: + * return int 0 - OK. + * 1 - Error. + */ +static int reinstate_signal_handlers(SignalActions *si) +{ + int signo; /* A signal number */ +/* + * Check each of the available signals to see if it is specified in + * si->mask. If so, reinstate the displaced recorded in the + * corresponding element of si->actions[], and make a record in + * si->mask that this signal handler has been reinstated. + */ + for(signo=1; signo < si->nsignal; signo++) { + if(sigismember(&si->mask, signo)) { + if(sigaction(signo, &si->actions[signo], NULL) < 0) { + fprintf(stderr, "sigaction error (%s)\n", strerror(errno)); + return 1; + }; + sigdelset(&si->mask, signo); + }; + }; + return 0; +} |