sh: Make it much more versatile

This commit implements:
Argument matching, to show help and version
Support for running scripts
Support for parsing and running commands (via -c)

This is the first step to implementing the system() libc function.
This commit is contained in:
apio 2022-10-30 19:09:18 +01:00
parent e58aa361c8
commit a7e4f2bdd2

View File

@ -16,7 +16,8 @@ typedef struct
char* buffer; char* buffer;
size_t size; size_t size;
size_t capacity; size_t capacity;
} command; int interactive;
} command_t;
char** split_command_into_argv(const char* cmd) char** split_command_into_argv(const char* cmd)
{ {
@ -78,30 +79,19 @@ void shell_execvp(char* pathname, char* const argv[])
void show_prompt() void show_prompt()
{ {
if (!username)
{
struct passwd* user = getpwuid(getuid());
if (!user)
{
if (errno) perror("getpwuid");
username = "??";
}
else { username = user->pw_name; }
atexit(endpwent);
}
if (WEXITSTATUS(status)) { printf("%d [%s]> ", WEXITSTATUS(status), username); } if (WEXITSTATUS(status)) { printf("%d [%s]> ", WEXITSTATUS(status), username); }
else else
printf("[%s]> ", username); printf("[%s]> ", username);
} }
int command_matches(command* cmd, const char* string) int command_matches(command_t* cmd, const char* string)
{ {
if (cmd->size <= strlen(string)) // cmd->size includes null terminator if (cmd->size <= strlen(string)) // cmd->size includes null terminator
return 0; return 0;
return strncmp(cmd->buffer, string, strlen(string)) == 0; return strncmp(cmd->buffer, string, strlen(string)) == 0;
} }
int command_matches_exactly(command* cmd, const char* string) int command_matches_exactly(command_t* cmd, const char* string)
{ {
if (cmd->size <= strlen(string)) // cmd->size includes null terminator if (cmd->size <= strlen(string)) // cmd->size includes null terminator
return 0; return 0;
@ -109,7 +99,7 @@ int command_matches_exactly(command* cmd, const char* string)
return strncmp(cmd->buffer, string, strlen(string)) == 0; return strncmp(cmd->buffer, string, strlen(string)) == 0;
} }
int command_match_builtins(command* cmd) int command_match_builtins(command_t* cmd)
{ {
if (command_matches(cmd, "exit ")) { exit(atoi(cmd->buffer + 5)); } if (command_matches(cmd, "exit ")) { exit(atoi(cmd->buffer + 5)); }
if (command_matches_exactly(cmd, "exit")) { exit(0); } if (command_matches_exactly(cmd, "exit")) { exit(0); }
@ -126,7 +116,7 @@ int command_match_builtins(command* cmd)
return 0; return 0;
} }
void command_expand(command* cmd, long new_capacity) void command_expand(command_t* cmd, long new_capacity)
{ {
char* buffer = realloc(cmd->buffer, new_capacity); char* buffer = realloc(cmd->buffer, new_capacity);
if (!buffer) if (!buffer)
@ -138,38 +128,46 @@ void command_expand(command* cmd, long new_capacity)
cmd->capacity = new_capacity; cmd->capacity = new_capacity;
} }
void command_push(command* cmd, char c) void command_push(command_t* cmd, char c)
{ {
if (cmd->size == cmd->capacity) command_expand(cmd, cmd->capacity + 8); if (cmd->size == cmd->capacity) command_expand(cmd, cmd->capacity + 8);
cmd->buffer[cmd->size] = c; cmd->buffer[cmd->size] = c;
cmd->size++; cmd->size++;
} }
void command_pop(command* cmd) void command_pop(command_t* cmd)
{ {
cmd->size--; cmd->size--;
} }
void command_init(command* cmd) void command_init(command_t* cmd)
{ {
cmd->buffer = malloc(5); cmd->buffer = malloc(5);
cmd->capacity = 5; cmd->capacity = 5;
cmd->size = 0; cmd->size = 0;
} }
void command_clear(command* cmd) void command_clear(command_t* cmd)
{ {
free(cmd->buffer); free(cmd->buffer);
return command_init(cmd); return command_init(cmd);
} }
void command_execute(command* cmd) void process_execute_command(const char* command)
{
char** argv = split_command_into_argv(command);
shell_execvp(argv[0], argv);
perror(argv[0]);
exit(127);
}
void command_execute(command_t* cmd)
{ {
command_push(cmd, '\0'); command_push(cmd, '\0');
if (command_match_builtins(cmd)) if (command_match_builtins(cmd))
{ {
command_clear(cmd); command_clear(cmd);
show_prompt(); if (cmd->interactive) show_prompt();
return; return;
} }
pid_t child = fork(); pid_t child = fork();
@ -177,60 +175,54 @@ void command_execute(command* cmd)
{ {
perror("fork"); perror("fork");
command_clear(cmd); command_clear(cmd);
show_prompt(); if (cmd->interactive) show_prompt();
return; return;
} }
if (child == 0) if (child == 0) process_execute_command(cmd->buffer);
{
char** argv = split_command_into_argv(cmd->buffer);
shell_execvp(argv[0], argv);
perror(argv[0]);
exit(127);
}
pid_t result = waitpid(child, &status, 0); pid_t result = waitpid(child, &status, 0);
if (result < 0) if (result < 0)
{ {
perror("waitpid"); perror("waitpid");
command_clear(cmd); command_clear(cmd);
show_prompt(); if (cmd->interactive) show_prompt();
return; return;
} }
int exit_status = WEXITSTATUS(status); int exit_status = WEXITSTATUS(status);
if (exit_status == -2 || exit_status == -3) printf("(PID %ld) Segmentation fault\n", result); if (exit_status == -2 || exit_status == -3) printf("(PID %ld) Segmentation fault\n", result);
if (exit_status == -1) printf("(PID %ld) Aborted\n", result); if (exit_status == -1) printf("(PID %ld) Aborted\n", result);
command_clear(cmd); command_clear(cmd);
show_prompt(); if (cmd->interactive) show_prompt();
} }
void command_concat_char(command* cmd, char c) void command_concat_char(command_t* cmd, char c)
{ {
if (c == '\b') if (c == '\b')
{ {
if (cmd->size != 0) if (cmd->size != 0)
{ {
putchar(c); if (cmd->interactive) putchar(c);
command_pop(cmd); command_pop(cmd);
} }
} }
else if (c == '\n') else if (c == '\n')
{ {
putchar(c); if (cmd->interactive) putchar(c);
if (cmd->size == 0) if (cmd->size == 0)
{ {
status = 0; status = 0;
show_prompt(); if (cmd->interactive) show_prompt();
} }
else else
command_execute(cmd); command_execute(cmd);
} }
else else
{ {
putchar(c); if (cmd->interactive) putchar(c);
command_push(cmd, c); command_push(cmd, c);
} }
} }
void command_concat(command* cmd, const char* str) void command_concat(command_t* cmd, const char* str)
{ {
while (*str) while (*str)
{ {
@ -239,13 +231,15 @@ void command_concat(command* cmd, const char* str)
} }
} }
int main() void shell_interactive()
{ {
show_prompt(); show_prompt();
command shell_command; command_t shell_command;
command_init(&shell_command); command_init(&shell_command);
shell_command.interactive = 1;
while (1) while (1)
{ {
int c = getchar(); int c = getchar();
@ -254,11 +248,112 @@ int main()
if (ferror(stdin)) if (ferror(stdin))
{ {
perror("getchar"); perror("getchar");
return 1; exit(EXIT_FAILURE);
} }
if (feof(stdin)) return 0; if (feof(stdin)) exit(EXIT_SUCCESS);
assert(false); // we should never get here assert(false); // we should never get here
} }
command_concat_char(&shell_command, (char)c); command_concat_char(&shell_command, (char)c);
} }
} }
void shell_read_from_file(const char* pathname)
{
FILE* fp = fopen(pathname, "r");
if (!fp)
{
perror("sh");
exit(EXIT_FAILURE);
}
command_t file_command;
command_init(&file_command);
file_command.interactive = 0;
char buffer[BUFSIZ];
while (fgets(buffer, BUFSIZ, fp))
{
command_concat(&file_command, buffer);
if (feof(fp)) break;
}
if (file_command.size > 0) // last line of file, does not end with newline
{
command_execute(&file_command);
}
fclose(fp);
}
void shell_execute_command(const char* command)
{
command_t cmd;
cmd.buffer = strdup(command);
cmd.size = strlen(command) + 1;
if (command_match_builtins(&cmd)) return;
command_clear(&cmd);
process_execute_command(command);
}
void fetch_username()
{
struct passwd* user = getpwuid(getuid());
if (!user)
{
perror("getpwuid");
exit(EXIT_FAILURE);
}
username = user->pw_name;
endpwent();
}
int main(int argc, char** argv)
{
fetch_username();
if (argc == 1) shell_interactive();
else if (argc == 2)
{
if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version"))
{
puts("Luna sh version 0.1"); // FIXME: Store the version somewhere, or use the kernel's version.
return 0;
}
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
{
printf("To use interactively: %s\n", argv[0]);
printf("To run a script: %s [script-name]\n", argv[0]);
printf("To get help: %s --help\n", argv[0]);
printf("To show the version: %s --version\n", argv[0]);
printf("To run a command: %s -c [command]\n", argv[0]);
return 0;
}
if (!strcmp(argv[1], "-c") || !strcmp(argv[1], "--command"))
{
fprintf(stderr, "Usage: %s %s [command]\n", argv[0], argv[1]);
fprintf(stderr, "Use the --help flag for more help.\n");
return 1;
}
shell_read_from_file(argv[1]);
}
else if (argc == 3)
{
if (!strcmp(argv[1], "-c") || !strcmp(argv[1], "--command")) shell_execute_command(argv[2]);
else
{
fprintf(stderr, "%s: too many arguments\n", argv[0]);
fprintf(stderr, "Use the --help flag for more help.\n");
return 1;
}
}
else
{
fprintf(stderr, "%s: too many arguments\n", argv[0]);
fprintf(stderr, "Use the --help flag for more help.\n");
return 1;
}
}