Improve shell line reading.

This commit is contained in:
Jonas 'Sortie' Termansen 2014-09-15 23:43:26 +02:00
parent 186ed27576
commit 2bed2f7ce2
1 changed files with 153 additions and 84 deletions

229
sh/sh.cpp
View File

@ -1083,7 +1083,7 @@ bool matches_simple_pattern(const char* string, const char* pattern)
pattern + wildcard_index + 1) == 0; pattern + wildcard_index + 1) == 0;
} }
int runcommandline(const char** tokens, bool* exitexec, bool interactive) int runcommandline(const char** tokens, bool* script_exited, bool interactive)
{ {
int result = 127; int result = 127;
size_t cmdnext = 0; size_t cmdnext = 0;
@ -1240,7 +1240,7 @@ readcmd:
if ( strcmp(argv[0], "exit") == 0 ) if ( strcmp(argv[0], "exit") == 0 )
{ {
int exitcode = argv[1] ? atoi(argv[1]) : 0; int exitcode = argv[1] ? atoi(argv[1]) : 0;
*exitexec = true; *script_exited = true;
return exitcode; return exitcode;
} }
if ( strcmp(argv[0], "unset") == 0 ) if ( strcmp(argv[0], "unset") == 0 )
@ -1362,7 +1362,7 @@ out:
int run_command(char* command, int run_command(char* command,
bool interactive, bool interactive,
bool exit_on_error, bool exit_on_error,
bool* exitexec) bool* script_exited)
{ {
size_t commandused = strlen(command); size_t commandused = strlen(command);
@ -1430,9 +1430,9 @@ int run_command(char* command,
return status; return status;
argv[argc] = NULL; argv[argc] = NULL;
status = runcommandline(argv, exitexec, interactive); status = runcommandline(argv, script_exited, interactive);
if ( status && exit_on_error ) if ( status && exit_on_error )
*exitexec = true; *script_exited = true;
return status; return status;
} }
@ -1678,13 +1678,20 @@ size_t do_complete(char*** completions_ptr,
return *completions_ptr = completions, num_completions; return *completions_ptr = completions, num_completions;
} }
int get_and_run_command_interactive(bool exit_on_error, bool* exitexec) struct sh_read_command
{
char* command;
bool abort_condition;
bool eof_condition;
bool error_condition;
};
void read_command_interactive(struct sh_read_command* sh_read_command)
{ {
update_pwd(); update_pwd();
update_env(); update_env();
static struct edit_line edit_state; static struct edit_line edit_state; // static to preserve command history.
edit_state.in_fd = 0; edit_state.in_fd = 0;
edit_state.out_fd = 1; edit_state.out_fd = 1;
edit_state.check_input_incomplete_context = NULL; edit_state.check_input_incomplete_context = NULL;
@ -1726,116 +1733,172 @@ int get_and_run_command_interactive(bool exit_on_error, bool* exitexec)
edit_line(&edit_state); edit_line(&edit_state);
int result = status; free(ps1);
if ( edit_state.abort_editing )
{
sh_read_command->abort_condition = true;
return;
}
if ( edit_state.eof_condition ) if ( edit_state.eof_condition )
{ {
if ( is_outermost_shell() ) sh_read_command->eof_condition = true;
printf("Type exit to close the outermost shell.\n"); return;
else
*exitexec = true;
} }
else if ( !edit_state.abort_editing )
{
char* command = edit_line_result(&edit_state); char* command = edit_line_result(&edit_state);
assert(command);
for ( size_t i = 0; command[i]; i++ ) for ( size_t i = 0; command[i]; i++ )
if ( command[i + 0] == '\\' && command[i + 1] == '\n' ) if ( command[i + 0] == '\\' && command[i + 1] == '\n' )
command[i + 0] = ' ', command[i + 0] = ' ',
command[i + 1] = ' '; command[i + 1] = ' ';
result = run_command(command, true, exit_on_error, exitexec); sh_read_command->command = command;
free(command);
} }
free(ps1); void read_command_non_interactive(struct sh_read_command* sh_read_command,
FILE* fp)
return result;
}
int get_and_run_command_non_interactive(FILE* fp,
const char* fpname,
bool exit_on_error,
bool* exitexec)
{ {
int fd = fileno(fp); int fd = fileno(fp);
static char* command = NULL; size_t command_used = 0;
static size_t commandlen = 1024; size_t command_length = 1024;
if ( !command ) char* command = (char*) malloc(command_length + 1);
commandlen = 1024,
command = (char*) malloc((commandlen+1) * sizeof(char));
if ( !command ) if ( !command )
error(64, errno, "malloc"); error(64, errno, "malloc");
command[0] = '\0'; command[0] = '\0';
size_t commandused = 0;
while ( true ) while ( true )
{ {
char c; char c;
ssize_t bytesread;
if ( 0 <= fd ) if ( 0 <= fd )
bytesread = read(fd, &c, sizeof(c)); {
ssize_t bytes_read = read(fd, &c, sizeof(c));
if ( bytes_read < 0 )
{
sh_read_command->error_condition = true;
free(command);
return;
}
else if ( bytes_read == 0 )
{
if ( command_used == 0 )
{
sh_read_command->eof_condition = true;
free(command);
return;
}
else
{
c = '\n';
}
}
else
{
assert(bytes_read == 1);
if ( c == '\0' )
continue;
}
}
else else
{ {
int ic = fgetc(fp); int ic = fgetc(fp);
if ( ic == EOF && ferror(fp) ) if ( ic == EOF && ferror(fp) )
bytesread = -1;
else if ( ic == EOF )
bytesread = 0;
else
c = (char) (unsigned char) ic, bytesread = 1;
}
if ( bytesread < 0 && errno == EINTR )
return status;
if ( bytesread < 0 )
error(64, errno, "read %s", fpname);
if ( !bytesread )
{ {
if ( commandused ) sh_read_command->error_condition = true;
break; free(command);
*exitexec = true; return;
return status;
} }
if ( !c ) else if ( ic == EOF )
{
if ( command_used == 0 )
{
sh_read_command->eof_condition = true;
free(command);
return;
}
else
{
c = '\n';
}
}
else
{
c = (char) (unsigned char) ic;
if ( c == '\0' )
continue; continue;
}
}
if ( c == '\n' && is_shell_input_ready(command) ) if ( c == '\n' && is_shell_input_ready(command) )
break; break;
if ( commandused == commandlen ) if ( command_used == command_length )
{ {
size_t newlen = commandlen * 2; size_t new_length = command_length * 2;
size_t newsize = (newlen+1) * sizeof(char); char* new_command = (char*) realloc(command, new_length + 1);
char* newcommand = (char*) realloc(command, newsize); if ( !new_command )
if ( !newcommand )
error(64, errno, "realloc"); error(64, errno, "realloc");
command = newcommand; command = new_command;
commandlen = newsize; command_length = new_length;
} }
command[commandused++] = c; command[command_used++] = c;
command[commandused] = '\0'; command[command_used] = '\0';
} }
return run_command(command, false, exit_on_error, exitexec); sh_read_command->command = command;
} }
int run(FILE* fp, const char* name, bool interactive, bool exit_on_error) int run(FILE* fp,
const char* fp_name,
bool interactive,
bool exit_on_error,
bool* script_exited,
int status)
{ {
// TODO: The interactive read code should cope when the input is not a // TODO: The interactive read code should cope when the input is not a
// terminal; it should print the prompt and then read normally without // terminal; it should print the prompt and then read normally without
// any line editing features. // any line editing features.
if ( !isatty(fileno(fp)) ) if ( !isatty(fileno(fp)) )
interactive = false; interactive = false;
bool exitexec = false;
int exitstatus; while ( true )
do
{ {
struct sh_read_command sh_read_command;
memset(&sh_read_command, 0, sizeof(sh_read_command));
if ( interactive ) if ( interactive )
exitstatus = get_and_run_command_interactive(exit_on_error, &exitexec); read_command_interactive(&sh_read_command);
else else
exitstatus = read_command_non_interactive(&sh_read_command, fp);
get_and_run_command_non_interactive(fp, name, exit_on_error, &exitexec);
if ( sh_read_command.abort_condition )
continue;
if ( sh_read_command.eof_condition )
{
if ( interactive && is_outermost_shell() )
{
printf("Type exit to close the outermost shell.\n");
continue;
} }
while ( !exitexec ); break;
return exitstatus; }
if ( sh_read_command.error_condition )
{
error(0, errno, "read: %s", fp_name);
return *script_exited = true, 2;
}
status = run_command(sh_read_command.command, interactive,
exit_on_error, script_exited);
free(sh_read_command.command);
if ( *script_exited || (status == 0 && exit_on_error) )
break;
}
return status;
} }
void compact_arguments(int* argc, char*** argv) void compact_arguments(int* argc, char*** argv)
@ -1979,6 +2042,7 @@ int main(int argc, char* argv[])
setenv("0", argv[0], 1); setenv("0", argv[0], 1);
bool script_exited = false;
int status = 0; int status = 0;
if ( flag_c_first_operand_is_command ) if ( flag_c_first_operand_is_command )
@ -2000,18 +2064,20 @@ int main(int argc, char* argv[])
if ( !fp ) if ( !fp )
error(2, errno, "fmemopen"); error(2, errno, "fmemopen");
status = run(fp, "<command-line>", false, flag_e_exit_on_error); status = run(fp, "<command-line>", false, flag_e_exit_on_error,
&script_exited, status);
fclose(fp); fclose(fp);
if ( status != 0 && flag_e_exit_on_error ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
if ( flag_s_stdin ) if ( flag_s_stdin )
{ {
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error); status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
if ( status != 0 && flag_e_exit_on_error ) &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }
} }
@ -2025,8 +2091,9 @@ int main(int argc, char* argv[])
} }
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error); status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
if ( status != 0 && flag_e_exit_on_error ) &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }
else if ( 2 <= argc ) else if ( 2 <= argc )
@ -2042,16 +2109,18 @@ int main(int argc, char* argv[])
FILE* fp = fopen(path, "r"); FILE* fp = fopen(path, "r");
if ( !fp ) if ( !fp )
error(127, errno, "%s", path); error(127, errno, "%s", path);
status = run(fp, path, false, flag_e_exit_on_error); status = run(fp, path, false, flag_e_exit_on_error, &script_exited,
status);
fclose(fp); fclose(fp);
if ( status != 0 && flag_e_exit_on_error ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }
else else
{ {
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error); status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
if ( status != 0 && flag_e_exit_on_error ) &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }