diff --git a/bats/dshlib.c b/bats/dshlib.c new file mode 100644 index 0000000000000000000000000000000000000000..d8544273d948a1f7cb6db9fb18ab5f783c82290e --- /dev/null +++ b/bats/dshlib.c @@ -0,0 +1,163 @@ +#include "dshlib.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/wait.h> + +//--------------------------------------------------------------------- +// Helper: trim_whitespace +// Removes leading and trailing whitespace from a string. +static char *trim_whitespace(char *str) { + while (isspace((unsigned char)*str)) + str++; + if (*str == '\0') + return str; + char *end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) + end--; + *(end + 1) = '\0'; + return str; +} + +//--------------------------------------------------------------------- +// Helper: parse_cmd_buff +// +// Parses the input command line stored in buf into tokens placed into +// the cmd_buff_t structure. It follows these rules: +// - All leading and trailing spaces are removed. +// - Duplicate spaces are eliminated outside quoted strings. +// - Quoted strings (using double quotes) are treated as a single token, +// preserving any spaces within them. +// +// This function modifies the input buffer by inserting '\0' terminators. +// Returns 0 on success and -1 on error. +static int parse_cmd_buff(char *buf, cmd_buff_t *cb) { + cb->argc = 0; + char *p = buf; + + while (*p != '\0') { + // Skip any spaces. + while (*p && isspace((unsigned char)*p)) + p++; + if (*p == '\0') + break; + + char *token; + if (*p == '\"') { // Quoted token. + p++; // Skip the opening quote. + token = p; + // Advance until closing quote or end-of-string. + while (*p && *p != '\"') + p++; + if (*p == '\"') { + *p = '\0'; // Terminate token. + p++; // Skip the closing quote. + } + } else { // Non-quoted token. + token = p; + while (*p && !isspace((unsigned char)*p)) + p++; + if (*p != '\0') { + *p = '\0'; + p++; + } + } + // Store the token if we haven’t exceeded the maximum. + if (cb->argc < CMD_ARGV_MAX) + cb->argv[cb->argc++] = token; + else { + fprintf(stderr, "Error: Too many arguments\n"); + return -1; + } + } + return 0; +} + +//--------------------------------------------------------------------- +// Function: exec_local_cmd_loop +// +// Implements the shell’s main loop. It: +// - Prompts the user with SH_PROMPT. +// - Reads a line of input using fgets(). +// - Trims all leading/trailing spaces. +// - Parses the line into arguments (handling duplicate spaces and quoted strings). +// - If the command is EXIT_CMD, the loop exits. +// - If the command is "cd", it calls the built-in cd command logic. +// - Otherwise, it forks a child process that executes the command using execvp(). +// - The parent waits for the child process to finish. +int exec_local_cmd_loop(void) { + char input[SH_CMD_MAX]; + cmd_buff_t cb; + + // Allocate memory for the raw command buffer. + cb._cmd_buffer = malloc(SH_CMD_MAX); + if (!cb._cmd_buffer) { + perror("malloc"); + return -1; + } + + while (1) { + printf("%s", SH_PROMPT); + + if (fgets(input, sizeof(input), stdin) == NULL) { + printf("\n"); + break; + } + // Remove trailing newline. + input[strcspn(input, "\n")] = '\0'; + // Trim the input. + char *trimmed = trim_whitespace(input); + if (strcmp(trimmed, EXIT_CMD) == 0) + break; + + // Copy the trimmed input into our command buffer. + strncpy(cb._cmd_buffer, trimmed, SH_CMD_MAX); + cb._cmd_buffer[SH_CMD_MAX - 1] = '\0'; + + // Parse the command buffer into tokens. + if (parse_cmd_buff(cb._cmd_buffer, &cb) != 0) { + fprintf(stderr, "Error parsing command\n"); + continue; + } + + // If no command was entered, print a warning and prompt again. + if (cb.argc == 0) { + printf("%s", CMD_WARN_NO_CMD); + continue; + } + + // Built-in cd command: + if (strcmp(cb.argv[0], "cd") == 0) { + // If no argument, do nothing. + if (cb.argc < 2) { + /* do nothing */ + } else if (cb.argc == 2) { + if (chdir(cb.argv[1]) != 0) + perror("cd"); + } else { + fprintf(stderr, "cd: too many arguments\n"); + } + continue; + } + + // Fork and execute an external command. + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + continue; + } + if (pid == 0) { // Child process. + cb.argv[cb.argc] = NULL; // NULL-terminate the argument list. + execvp(cb.argv[0], cb.argv); + perror("execvp"); + exit(1); + } else { // Parent process. + int status; + waitpid(pid, &status, 0); + } + } + free(cb._cmd_buffer); + return 0; +} \ No newline at end of file