Adding command-line editing/history to an application

As soon as Zones integrated into Solaris 10, we started getting some RFEs for ways to make it better. One of them was to add command history to zonecfg; now that OpenSolaris has been launched, I thought I would share how I did it.

Some existing libraries were suggested for enabling this; after studying them, I recommended libtecla, written by Martin Shepherd, a staff scientist / programmer in the Astronomy department at Caltech, because of its feature set (command-line editing and history, highly customizable, etc.), quality, and license.

Getting libtecla ported to Solaris was trivial, as it already compiled and linked fine. I just had to massage it into the Solaris format, which did not involve any significant code changes, but mainly Solaris Makefiles, lint library stubs, etc. These can be found under usr/src/lib/libtecla for the curious.

The more interesting work was tweaking zonecfg to use libtecla, which included the following steps, which are listed here in a way intended to make it easy to follow for other people wishing to do the same with their application(s):

  • header file:
    #include <libtecla.h>
  • some constant definitions:
    #define	MAX_LINE_LEN	1024
    #define	MAX_CMD_HIST	1024
    
  • global variable:
    static GetLine \*gl; /\* The gl_get_line() resource object \*/
  • initialization: near the beginning of main():
    	if ((gl = new_GetLine(MAX_LINE_LEN, MAX_CMD_HIST)) == NULL)
    		exit(Z_ERR);
    
    Note that Z_ERR is a constant with the value of 1, but the important thing for the general case is that it is some non-zero value.
  • (optional) customization of tab-completion:
    	if (gl_customize_completion(gl, NULL, cmd_cpl_fn) != 0)
    		exit(Z_ERR);
    
    Note that in the case of zonecfg, cmd_cpl_fn() is not particularly interesting, mainly consisting of lots of calls to cpl_add_completion() to cover various cases in the grammar; see the cpl_complete_word(3TECLA) man page for details.
  • clean-up: near the end of main():
    	(void) del_GetLine(gl);
    
  • the heart of it:
    If your program has some sort of construct like:
    	for (;;) {
    		...
    		prompt = prompt_value();
    		print_prompt(prompt);
    		line = read_input();
    		if (line == NULL)
    			break;
    		handle_input(line);
    		...
    	}
    
    then it would be rewritten thus:
    	for (;;) {
    		...
    		prompt = prompt_value();
    		line = gl_get_line(gl, prompt, NULL, -1);
    		if (gl_return_status(gl) == GLR_SIGNAL) {
    			gl_abandon_line(gl);
    			continue;
    		}
    		if (line == NULL)
    			break;
    		handle_input(line);
    		...
    	}
    
    In the case of zonecfg, however, it was slightly more complicated because of its use of lex(1)/yacc(1), so I needed to convert the char \*line into the FILE \*yyin, then call yyparse(). So the code became:
    	for (;;) {
    		...
    		prompt = prompt_value();
    		line = gl_get_line(gl, prompt, NULL, -1);
    		if (gl_return_status(gl) == GLR_SIGNAL) {
    			gl_abandon_line(gl);
    			continue;
    		}
    		if (line == NULL)
    			break;
    		(void) string_to_yyin(line);
    		yyparse();
    		...
    	}
    
    where the string_to_yyin() function is thus:
    static int
    string_to_yyin(char \*string)
    {
    	if ((yyin = tmpfile()) == NULL) {
    		zone_perror(execname, Z_TEMP_FILE, TRUE);
    		return (Z_ERR);
    	}
    	if (fwrite(string, strlen(string), 1, yyin) != 1) {
    		zone_perror(execname, Z_TEMP_FILE, TRUE);
    		return (Z_ERR);
    	}
    	if (fseek(yyin, 0, SEEK_SET) != 0) {
    		zone_perror(execname, Z_TEMP_FILE, TRUE);
    		return (Z_ERR);
    	}
    	return (Z_OK);
    }
    
    where execname is a global variable, zone_perror() is a perror(3C)-like function, and Z_TEMP_FILE is a constant which maps to an error message indicating "Problem creating temporary file", localized appropriately.
For those who would like to see all of zonecfg.c in context; it can be found at usr/src/cmd/zonecfg/zonecfg.c.

Technorati Tag:
Technorati Tag:

Comments:

Looks like adding the Solaris specific stuff (e.g. library versioning) to libtecla beforehand paid off... ;-)

Posted by guest on June 17, 2005 at 08:51 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

jbeck

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today