home | help
gl_io_mode(3)		    Library Functions Manual		   gl_io_mode(3)

NAME
      gl_io_mode, gl_raw_io, gl_normal_io, gl_tty_signals, gl_abandon_line,
      gl_handle_signal,  gl_pending_io - How to use gl_get_line() from an exter-
     nal event loop.

SYNOPSIS
     #include <libtecla.h>

     int gl_io_mode(GetLine *gl, GlIOMode mode);

     int gl_raw_io(GetLine *gl);

     int gl_normal_io(GetLine *gl);

     int gl_tty_signals(void (*term_handler)(int),
			void (*susp_handler)(int),
			void (*cont_handler)(int),
			void (*size_handler)(int));

     void gl_abandon_line(GetLine *gl);

     void gl_handle_signal(int signo, GetLine *gl, int ngl);

     GlPendingIO gl_pending_io(GetLine *gl);

DESCRIPTION
     The  gl_get_line()  function,  which  is  documented  separately	in   the
     gl_get_line(3)  man  page, supports two different I/O modes.  These are se-
     lected by calling the gl_io_mode() function.

       int gl_io_mode(GetLine *gl, GlIOMode mode);

     The mode argument of this function specifies the new I/O mode, and must  be
     one of the following.

       GL_NORMAL_MODE	-  Select the normal blocking-I/O mode.
			   In this mode gl_get_line()
			   doesn't return until either an error
			   occurs of the user finishes entering a
			   new line. This mode is the focus of
			   the gl_get_line(3) man page.

       GL_SERVER_MODE	-  Select non-blocking server I/O mode.
			   In this mode, since non-blocking
			   terminal I/O is used, the entry of
			   each new input line typically requires
			   many calls to gl_get_line() from
			   an external I/O-driven event loop.
			   This mode is the focus of this man
			   page.

     Newly  created  GetLine  objects  start in normal I/O mode, so to switch to
     non-blocking server mode requires an initial call to gl_io_mode().

SERVER I/O MODE
     In non-blocking server I/O mode, the application is  required  to	have  an
     event  loop which calls gl_get_line() whenever the terminal file descriptor
     can do the type I/O that gl_get_line() is waiting for. To	determine  which
     type  of  I/O  gl_get_line()  is  waiting	for,  the  application calls the
     gl_pending_io() function.

       GlPendingIO gl_pending_io(GetLine *gl);

     The return value of this function is one of the  following  two  enumerated
     values.

       GLP_READ    -  gl_get_line() is waiting to write a
		      character to the terminal.

       GLP_WRITE   -  gl_get_line() is waiting to read a
		      character from the keyboad.

     If  the  application is using either the select() or poll() system calls to
     watch for I/O on a group of file  descriptors,  then  it  should  call  the
     gl_pending_io()  function	before each call to these functions to see which
     direction of I/O it should tell them to watch for, and configure their  ar-
     guments  accordingly.  In	the case of the select() system call, this means
     using the FD_SET() macro to add the terminal file descriptor either to  the
     set  of  file  descriptors  to be watched for readability, or the set to be
     watched for writability.

     As in normal I/O mode, the  return  value	of  gl_get_line()  is  either  a
     pointer  to a completed input line, or NULL. However, whereas in normal I/O
     mode a NULL return value always means that an error occurred, in non-block-
     ing server mode, NULL is also returned when  gl_get_line()  can't	read  or
     write  to	the terminal without blocking. Thus in non-blocking server mode,
     in order to determine when a NULL return value signifies that an error  oc-
     curred  or not, it is necessary to call the gl_return_status() function. If
     this function returns the enumerated value, GLR_BLOCKED, as  documented  in
     the  gl_get_line(3)  man page, this means that gl_get_line() is waiting for
     I/O, and no error has occurred.

     When gl_get_line() returns NULL and gl_return_status() indicates that  this
     is  due  to blocked terminal I/O, the application should call gl_get_line()
     again when the type of I/O reported by  gl_pending_io()  becomes  possible.
     The prompt, start_line and start_pos arguments of gl_get_line() will be ig-
     nored on these calls.  If you need to change the prompt of the line that is
     currently	being edited, then you can call the gl_replace_prompt() function
     (documented in the gl_get_line(3) man page) between calls to gl_get_line().

GIVING UP THE TERMINAL
     A complication that is unique to non-blocking server mode is  that  it  re-
     quires   that   the   terminal  be  left  in  raw	mode  between  calls  to
     gl_get_line(). If this weren't the case, the external event  loop	wouldn't
     be able to detect individual key-presses, and the basic line editing imple-
     mented  by  the  terminal	driver	would clash with the editing provided by
     gl_get_line(). What this means is that any time that the terminal needs  to
     be used for other things than entering a new input line with gl_get_line(),
     it  needs	to  be	restored  to a usable state. In particular, whenever the
     process is suspended or terminated, the terminal must be returned to a nor-
     mal state. If this isn't done, then depending on the characteristics of the
     shell that was used to invoke the program, the user may end up with a  hung
     terminal.	To this end, the gl_normal_io() function is provided for switch-
     ing the terminal back to the state that it was in when raw  mode  was  last
     established.

       int gl_normal_io(GetLine *gl);

     What  this function does is first flush any pending output to the terminal,
     then move the cursor to the start of the terminal line  which  follows  the
     end  of  the  incompletely  entered input line. At this point it is safe to
     suspend or terminate the process, and it is safe  for  the  application  to
     read  and write to the terminal. To resume entry of the input line, the ap-
     plication should call the gl_raw_io() function.

       int gl_raw_io(GetLine *gl);

     This function starts a new line, redisplays the partially	completed  input
     line  (if	any),  restores the cursor position within this line to where it
     was when gl_normal_io() was called, then switches back to raw, non-blocking
     terminal mode ready to continue entry of the input line when  gl_get_line()
     is next called.

     Note  that  in non-blocking server mode, if gl_get_line() is called after a
     call  to  gl_normal_io(),	without  an  intervening  call	to  gl_raw_io(),
     gl_get_line()  will call gl_raw_mode() itself, and the terminal will remain
     in this mode when gl_get_line() returns.

SIGNAL HANDLING
     In the previous section it was pointed  out  that	in  non-blocking  server
     mode,  the  terminal  must be restored to a sane state whenever a signal is
     received that either suspends or terminates  the  process.  In  normal  I/O
     mode,  this  is  done  for you by gl_get_line(), but in non-blocking server
     mode,  since  the	terminal  is  left  in	raw  mode   between   calls   to
     gl_get_line(),  this  signal  handling  has  to be done by the application.
     Since there are many signals that can suspend or terminate  a  process,  as
     well as other signals that are important to gl_get_line(), such as the SIG-
     WINCH  signal,  which  tells  it  when  the  terminal size has changed, the
     gl_tty_signals() function is provided for installing  signal  handlers  for
     all pertinent signals.

       int gl_tty_signals(void (*term_handler)(int),
			  void (*susp_handler)(int),
			  void (*cont_handler)(int),
			  void (*size_handler)(int));

     What  this  does  is use gl_get_line()'s internal list of signals to assign
     specified signal handlers to groups of signals. The arguments of this func-
     tion are as follows.

       term_handler  -	This is the signal handler that is to be
			used to trap signals that by default
			terminate any process that receives
			them (eg. SIGINT or SIGTERM).

       susp_handler  -	This is the signal handler that is to be
			used to trap signals that by default
			suspend any process that receives them,
			(eg. SIGTSTP or SIGTTOU).

       cont_handler  -	This is the signal handler that is to be
			used to trap signals that are usually
			sent when a process resumes after being
			suspended (usually SIGCONT). Beware that there is
			nothing to stop a user from sending one of these
			signals at other times.

       size_handler  -	This signal handler is used to trap
			signals that are sent to processes when
			their controlling terminals are resized
			by the user (eg. SIGWINCH).

     These arguments can all be the same, if so desired,  and  you  can  specify
     SIG_IGN  (ignore  this  signal) or SIG_DFL (use the system-provided default
     signal handler) instead of a function where pertinent. In particular, it is
     rarely useful to trap SIGCONT, so the cont_handler argument will usually be
     SIG_DFL or SIG_IGN.

     The gl_tty_signals() function uses the POSIX sigaction()  function  to  in-
     stall these signal handlers, and it is careful to use the sa_mask member of
     each  sigaction  structure to ensure that only one of these signals is ever
     delivered at a time. This guards against different instances of these  sig-
     nal  handlers  from  simultaneously  trying to write to common global data,
     such as a shared sigsetjmp() buffer or a signal-received flag.

     The signal handlers that are installed by this function,  should  call  the
     gl_handle_signal().

       void gl_handle_signal(int signo, GetLine *gl, int ngl);

     The  signo  argument  tells this function which signal it is being asked to
     respond to, and the gl argument should be a pointer to the first element of
     an array of ngl GetLine objects. If your application only has one of  these
     objects, just pass its pointer as the gl argument and specify ngl as 1.

     Depending on the signal that is being handled, this function does different
     things.

   Terminal resize signals (SIGWINCH)
     If the signal indicates that the terminal was resized, then it arranges for
     the next call to gl_get_line() to ask the terminal for its new size and re-
     draw  the	input line accordingly. In order that gl_get_line() be called as
     soon as possible to do this, gl_handle_signal() also arranges that the next
     call to gl_pending_io() will return  GLP_WRITE.  Thus  if	the  application
     waits  for  I/O in select() or poll(), then the application needs to ensure
     that these functions will be reliably aborted when a signal is  caught  and
     handled by the application. More on this below.

Process termination signals.
     If  the  signal  that was caught is one of those that by default terminates
     any process that receives it, then gl_handle_signal()  does  the  following
     steps.

     1. First it blocks the delivery of all signals that can be
	blocked (ie. SIGKILL and SIGSTOP can't be blocked)

     2. Next it calls gl_normal_io() for each of the ngl
	GetLine objects. Note that this does nothing to any of the
	GetLine objects that aren't currently in raw mode.

     3. Next it sets the signal handler of the signal to its default,
	process-termination disposition.

     4. Next it re-sends the process the signal that was caught.

     5. Finally it unblocks delivery of this signal, which
	results in the process being terminated.

Process suspension signals.
     If  the  default  disposition  of the signal is to suspend the process, the
     same steps are executed as for process  termination  signals,  except  that
     when  the	process is later resumed, gl_handle_signal() continues, and does
     the following steps.

     6. It re-blocks delivery of the signal.

     7. It reinstates the signal handler of the signal to the one
	that was displaced when its default disposition was substituted.

     8. For any of the GetLine objects that were in raw mode when
	gl_handle_signal() was called, gl_handle_signal() then
	calls gl_raw_io(), to resume entry of the input lines on
	those terminals.

     9. Finally, it restores the signal process mask to how it
	was when gl_handle_signal() was called.

     Note that the process is suspended or terminated using the original  signal
     that was caught, rather than using the uncatchable SIGSTOP and SIGKILL sig-
     nals. This is important, because when a process is suspended or terminated,
     the  parent of the process may wish to use the status value returned by the
     wait() system call to figure out which signal was responsible. In	particu-
     lar,  most  shells use this information to print a corresponding message to
     the terminal. Users would be rightly confused if  when  their  process  re-
     ceived  a SIGPIPE signal, the program responded by sending itself a SIGKILL
     signal,  and  the	shell  then  printed  out  the	provocative   statement,
     "Killed!".

INTERRUPTING THE EVENT LOOP
     If  a  signal  is	caught	and handled when the application's event loop is
     waiting in select() or poll(), these functions will be aborted  with  errno
     set to EINTR. When this happens the event loop should call gl_pending_io(),
     before  calling  select()	or  poll() again. It should then arrange for se-
     lect() or poll() to wait for the type of I/O that	this  reports.	This  is
     necessary,  because any signal handler which calls gl_handle_signal(), will
     frequently change the type of I/O that gl_get_line() is waiting for.

     Unfortunately, if a signal arrives between the statements	which  configure
     the  arguments of select() or poll() and the calls to these functions, then
     the signal will not be seen by these functions,  which  will  then  not  be
     aborted.  If  these  functions are waiting for keyboard input from the user
     when the signal is received, and the signal handler arranges to redraw  the
     input  line  to  accomodate  a  terminal  resize  or  the resumption of the
     process, then this redisplay will be end up being delayed	until  the  user
     hits the next key. Apart from puzzling the user, this clearly isn't a seri-
     ous  problem.  However  there  is	a way, albeit complicated, to completely
     avoid this race condition. The following steps illustrate this.

     1. Block all of the signals that gl_get_line() catches,
	by passing the signal set returned by gl_list_signals() to
	sigprocmask().

     2. Call gl_pending_io() and set up the arguments of
	select() or poll() accordingly.

     3. Call sigsetjmp() with a non-zero savesigs argument.

     4. Initially this sigsetjmp() statement will return zero,
	indicating that control isn't resuming there after a matching
	call to siglongjmp().

     5. Replace all of the handlers of the signals that gl_get_line()
	is configured to catch, with a signal handler that first records
	the number of the signal that was caught, in a file-scope variable,
	then calls siglongjmp() with a non-zero value argument, to
	return execution to the above sigsetjmp()
	statement.  Registering these signal handlers can conveniently be
	done using the gl_tty_signals() function.

     6. Set the file-scope variable that the above signal handler uses to
	record any signal that is caught to -1, so that we can check
	whether a signal was caught by seeing if it contains a valid signal
	number.

     7. Now unblock the signals that were blocked in step 1. Any signal
	that was received by the process in between step 1 and now will
	now be delivered, and trigger our signal handler, as will any
	signal that is received until we block these signals again.

     8. Now call select() or poll().

     9. When select() returns, again block the signals that were
	unblocked in step 7.

     If a signal is arrived any time during the above steps, our signal  handler
     will be triggered and cause control to return to the sigsetjmp() statement,
     where this time, sigsetjmp() will return non-zero, indicating that a signal
     was caught. When this happens we simply skip the above block of statements,
     and  continue  with the following statements, which are executed regardless
     of whether or not a signal is caught. Note that when  sigsetjmp()	returns,
     regardless  of  why it returned, the process signal mask is returned to how
     it was when sigsetjmp() was called. Thus the following statements	are  al-
     ways executed with all of our signals blocked.

     9. Reinstate the signal handlers that were displaced in step 5.

     10. Check wether a signal was caught, by checking the file-scope
	 variable that the signal handler records signal numbers in.

     11. If a signal was caught, send this signal to the application
	 again, and unblock just this signal, so that it invokes the
	 signal handler which we just reinstated in step 10.

     12. Unblock all of the signals that were blocked in step 7.

     Since  this is complicated, note that demo3.c includes a working example of
     how to do this. The method used there however, is	more  general  than  the
     above.  What it provides is a wrapper function around select() which encom-
     passes steps 3 to 11. In this wrapper, rather than use gl_list_signals() to
     figure out the signals to block, and and gl_tty_signals() to assign and re-
     vert signal handlers, one of its arguments is a  sigset_t	which  specifies
     which  signals  to  block and assign signal handlers to. This function thus
     doesn't depend on gl_get_line() and can thus be used  in  other  situations
     where race-condition-free signal handling is required.

SIGNALS CAUGHT BY GL_GET_LINE
     Since  the application is expected to handle signals in non-blocking server
     mode, gl_get_line() doesn't attempt to duplicate  this  when  it  is  being
     called. If one of the signals that it is configured to catch is sent to the
     application  while  gl_get_line() is being called, gl_get_line() reinstates
     the caller's signal handlers, then just before returning, re-sends the sig-
     nal to the process to let the application's signal handler  handle  it.  If
     the  process  isn't  terminated by this signal, gl_get_line() returns NULL,
     and a following call to gl_return_status()  returns  the  enumerated  value
     GLR_SIGNAL.

ABORTING LINE INPUT
     Often,  rather  than letting it terminate the process, applications respond
     to the SIGINT user-interrupt signal by aborting the current input line. The
     way to do this in non-blocking server-I/O	mode  is  to  not  call  gl_han-
     dle_signal()  when  this signal is caught, but instead to call the gl_aban-
     don_line().

       void gl_abandon_line(GetLine *gl);

     This function arranges that when gl_get_line() is	next  called,  it  first
     flushes  any pending output to the terminal, then discardes the current in-
     put line, outputs a new prompt on the next line, and finally starts accept-
     ing input of a new input line from the user.

SIGNAL SAFE FUNCTIONS
     Provided that certain rules are followed, the following functions can  have
     been written to be safely callable from signal handlers. Other functions in
     this library should not be called from signal handlers.

       gl_normal_io()
       gl_raw_io()
       gl_handle_signal()
       gl_abandon_line()

     In order for this to be true, all signal handlers that call these functions
     must  be registered in such a way that only one instance of any one of them
     can be running at one time. The way to do this is to use the  POSIX  sigac-
     tion()  function  to register all signal handlers, and when doing this, use
     the sa_mask member of the corresponding sigaction	structure,  to	indicate
     that  all	of the signals who's handlers invoke the above functions, should
     be blocked when the current signal is being handled. This prevents two sig-
     nal handlers from operating on a GetLine object at the same time.

     To  prevent  signal  handlers  from  accessing  a	GetLine   object   while
     gl_get_line()  or	any  of its associated public functions are operating on
     it,  all  public  functions  associated   with   gl_get_line(),   including
     gl_get_line()  itself,  temporarily block the delivery of signals when they
     are accessing GetLine objects. Beware that the only signals that they block
     are the signals that gl_get_line() is currently configured to catch, so  be
     sure that if you call any of the above functions from signal handlers, that
     the signals that these handlers are assigned to are configured to be caught
     by gl_get_line() (see gl_trap_signal()).

USING TIMEOUTS TO POLL
     If  instead  of  using select() or poll() to wait for I/O, your application
     just needs to get out of gl_get_line() periodically to briefly do something
     else before returning to accept input from the user, this can  be	done  in
     non-blocking server mode by using the gl_inactivity_timeout() function (see
     gl_get_line(3)), to specify that a callback function that returns GLTO_CON-
     TINUE  should be called whenever gl_get_line() has been waiting for I/O for
     more than a specified amount of time.

     When this callback is triggered, gl_get_line() will return NULL, and a fol-
     lowing call to gl_return_status() will return GLR_BLOCKED.

     Beware that gl_get_line() won't return until the user hasn't  typed  a  key
     for  the specified interval, so if the interval is long, and the user keeps
     typing, gl_get_line() may not return for a while. In other words  there  is
     no guarantee that it will return in the time specified.

THE SERVER DEMO PROGRAM
     The  demo3 program that is distributed with the library, provides a working
     example of how to use non-blocking server I/O mode in a  real  program.  As
     far as the user is concerned, this program operates identically to the main
     demo  program (called demo), except that whereas the main demo program uses
     the normal blocking I/O mode, demo3 using non-blocking I/O and an	external
     event  loop.  The	source	code  can  be found in demo3.c, and the comments
     therein explain the various steps.

FILES
     libtecla.a      -	  The tecla library
     libtecla.h      -	  The tecla header file.

SEE ALSO
     libtecla(3), gl_get_line(3), tecla(7), ef_expand_file(3),
     cpl_complete_word(3), pca_lookup_file(3)

AUTHOR
     Martin Shepherd  (mcs@astro.caltech.edu)

								   gl_io_mode(3)

home | help