c - How can I design a signal-safe shell interpreter -
the code have right sends prompt out stdout, reads line stdin. receiving sigint
@ point interrupts execution , exits program. unsure should trap sigint
, , know cannot start new prompt when signal received current code. there proper way accomplish (ultimate goal act shells (sigint cancels current prompt , starts new one))?
this code run on linux, less platform independent, better.
get_line
reads line stdin buffer , generates char[], assigned line.
split_args
takes line , transforms array of char[], splitting on whitespace.
is_command_valid
determines if user typed known internal command. external programs cannot executed.
static int run_interactive(char *user) { int done = 0; { char *line, **args; int (*fn)(char *, char **); fprintf(stderr, "gitorium (%s)> ", user); get_line(&line); if (line[0] == '\0') { free(line); break; } split_args(&args, line); if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") || !strcmp(args[0], "logout") || !strcmp(args[0], "bye")) done = 1; else if (null == args[0] || (!strcmp("help", args[0]) && null == args[1])) interactive_help(); else if ((fn = is_command_valid(args)) != null) (*fn)(user, args); else error("the command not exist."); free(line); free(args); } while (!done); return 0; }
here 2 important helper functions
static int split_args(char ***args, char *str) { char **res = null, *p = strtok(str, " "); int n_spaces = 0, = 0; while (p) { res = realloc(res, sizeof (char*) * ++n_spaces); if (res == null) return gitorium_mem_alloc; res[n_spaces-1] = p; i++; p = strtok(null, " "); } res = realloc(res, sizeof(char*) * (n_spaces+1)); if (res == null) return gitorium_mem_alloc; res[n_spaces] = 0; *args = res; return i; } static int get_line(char **linep) { char *line = malloc(line_buffer_size); int len = line_buffer_size, c; *linep = line; if(line == null) return gitorium_mem_alloc; for(;;) { c = fgetc(stdin); if(c == eof || c == '\n') break; if(--len == 0) { char *linen = realloc(*linep, sizeof *linep + line_buffer_size); if(linen == null) return gitorium_mem_alloc; len = line_buffer_size; line = linen + (line - *linep); *linep = linen; } *line++ = c; } *line = 0; return 0; }
if understand correctly, want know how handle signal once it.
the way establish signal handler sigaction()
. didn't state platform you're on i'm assuming linux, although sigaction()
defined posix standards , should available on other platforms.
there various ways can this. 1 way establish signal handler sets global variable 1, denoting signal caught. in getline()
function establish check see if sigint
caught , if return null
, allow run_interactive()
run again.
here's how catch signal:
#include <signal.h> static int sigint_caught = 0; static void sigint_handler(int sig) { sigint_caught = 1; } struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; // or sa_restart if want automatically restart system calls interrupted signal sa.sa_handler = sigint_handler; if (sigaction(sigint, &sa, null) == -1) { printf("could not establish handler\n"); exit(-1); // or }
and perhaps in getline()
, in infinite loop, establish check see if sigint
has been caught:
for (;;) { if (sigint_caught) { return null; } // ...
and in run_interactive()
call can check return value with check see if sigint
caught:
// ... get_line(&line); if (line == null && sigint_caught) { sigint_caught = 0; // allow caught again free(line); continue; // or something; go next iteration of loop } else if (line[0] == '\0') { free(line); break; } else { // rest of code
didn't test can't guarantee it'll work, since question pretty broad (having through more of code etc.), gives enough of idea can in situation. perhaps pretty naive solution might meet needs. more robust perhaps source code popular shells bash or zsh.
for example, 1 thing can happen fgetc()
might block since there no new data in stdin, , might when signal sent. fgetc()
interrupted , errno
eintr
, add check in getline()
:
c = fgetc(stdin); // make sure #include <errno.h> if (errno == eintr && sigint_caught) return null;
this happen if don't set sa_flags
sa_restart
. if do, fgetc
should automatically restart , continue blocking until new input received, may or may not want.
Comments
Post a Comment