/* * 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 #include #include #include #include #include #include #ifdef HAVE_SELECT #ifdef HAVE_SYS_SELECT_H #include #endif #endif #include #include #include #include #include #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= 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; }