Tuesday June 14, 2005 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):
#include <libtecla.h>
#define MAX_LINE_LEN 1024 #define MAX_CMD_HIST 1024
static GetLine *gl; /* The gl_get_line() resource object */
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.
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.
main():
(void) del_GetLine(gl);
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.
Technorati Tag: OpenSolaris
Technorati Tag: Solaris