Changing parsing made all tests fail, can't replicate when i compile it #NI5

I’m stuck on Stage #NI 5

I’ve tried commenting out all the irrelevant sections of the code. The change I made was with the way argc and argv are parsed. Furthermore, I can’t replicate the eror when I compile it myself. This is what happens when I run myself:

vestatian@pegasus:~/codecrafters/codecrafters-shell-c$ gcc app/main.c && ./a.out
$ echo 'example shell'
example shell
$ 

Here are my logs:

vestatian@pegasus:~/codecrafters/codecrafters-shell-c$ codecrafters test
Initiating test run...

⚡ This is a turbo test run. https://codecrafters.io/turbo

Running tests. Logs should appear shortly...

[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.

Debug = true

[tester::#NI6] Running tests for Stage #NI6 (Quoting - Single quotes)
[tester::#NI6] [setup] export PATH=/tmp/blueberry/orange/mango:$PATH
[tester::#NI6] Running ./your_program.sh
[tester::#NI6] [setup] echo -n "pineapple mango." > "/tmp/bar/f   84"
[tester::#NI6] [setup] echo -n "grape mango." > "/tmp/bar/f   76"
[tester::#NI6] [setup] echo -n "pineapple apple." > "/tmp/bar/f   6"
[your-program] $ echo 'test script'
[your-program] execvp failed
[tester::#NI6] Output does not match expected value.
[tester::#NI6] Expected: "test script"
[tester::#NI6] Received: "execvp failed"
[your-program] : Bad address
[your-program] $ 
[tester::#NI6] Assertion failed.
[tester::#NI6] Test failed

Below is the modified code: Before this, no errors were being thrown at all; originally, this code just used strtok with ’ ’ delimiter to get argc and argv.

void function_parse_input(int argc, char **argv, char *input) {
  char **argv_i = (char**)malloc(sizeof(char*) * (argc + 1));
  bool in_quotes = false;
  bool in_delimiter = true;
  int len = strlen(input) + 1;
  // printf("Initial input: %s\n", input);
  for (int i = 0; i < len; i++) {
    // printf("%d | Processing: %c | ", i, input[i]);
    if (input[i] == '\'') {
      in_quotes = in_quotes ? false : true;
      strcpy(input + i, input + i + 1);
      len--;
      i--;
      // printf("toggled quotes\n");
    }
    else if (!in_quotes && input[i] == ' ') {
      in_delimiter = true;
      input[i] = '\0';
      // printf("delimiter added\n");
      }
    else if (input[i] != '\0') {
      if (in_delimiter) {
        in_delimiter = false;
        argv_i = (char**)realloc(argv_i, sizeof(char*) * (argc + 1));
        argv_i[argc++] = &input[i];
        // printf("Added argv[%d]: index %d (addr %p) | ", argc - 1, i, argv_i[argc - 1]);
      }
    }
  }
  for (int i = 0; i < argc; i++) {
    argv[i] = argv_i[i];
    // printf("argv[%d]: %s (addr %p)\n", i, argv[i], argv_i[i]);
  }
  free(argv_i);
}

This is my main function:

int main() {
  setbuf(stdout, NULL);
  while (1) {
    char input[BUFFERSIZE_INPUT];
    int argc = 0;
    char *argv[BUFFERSIZE_ARGV];

    printf("$ ");
    fgets(input, BUFFERSIZE_INPUT, stdin);
    input[strlen(input) - 1] = '\0';

    function_parse_input(argc, argv, input);
    
    if (strcmp(argv[0], "exit") == 0 && argv[1][0] == '0') {
      return 0;
    }
    else if (strcmp(argv[0], "type") == 0) {
      builtin_type(argv[1]);
    }
    else if (strncmp(argv[0], "echo ", 5) == 0) {
      builtin_echo(argc, argv);
    }
    else if (strcmp(argv[0], "pwd") == 0) {
      builtin_pwd();
    }
    else if (strcmp(argv[0], "cd") == 0) {
      builtin_cd(argv[1]);
    }
    else {
      char valid_path[BUFFERSIZE_PATH];
      if (function_search_path(argv[0], valid_path) == 0) {
        builtin_execute(valid_path, argv);
      } else {
        printf("%s: command not found\n", argv[0]);
      }
    }
  }
}

This is where execvp failed comes from:

void builtin_execute (char *path, char **argv) {
  pid_t pid = fork();
  if (pid == EXIT_SUCCESS) {
    execvp(path, argv);
    perror("execvp failed\n"); 
    exit(1);
  }
  else if (pid < 0) {
    perror("fork failed\n");    
  }
  else {
    int status;
    waitpid(pid, &status, 0);
  }
}

Hi @VestaCord, I tried printing the contents of argv after calling function_parse_input(argc, argv, input):

Since argv is passed to execvp, it must be null-terminated to work correctly. You might want to take a closer look at function_parse_input to ensure it’s handling the terminating null pointer properly.

Let me know if you need further assistance!

Hi Andy,

I’ve made sure the argv all end with a null char, you can see the same if you run the parsing code. Thank you!

@VestaCord, thanks for the clarification!

It looks like the null pointer is not positioned correctly. If it were in the right place, the logs I added would stop after [1], without printing gibberish at [2] and [3]:

Let me know if you’d like me to dig deeper!

Hi, I think the issue is that I forgot to pass argv, argc, input as pointers to my function_parse_input(), so they only got passed by value. However, my point of confusion is that I can’t replicate the error when I run ./your_program.sh. Do you mind running the whole file for me to see if you can replicate it? I have attached it as text below:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// to refactor: dont hardcode strcmp (use enum or const table, or switch case), dont use exit_success for non os return codes
// to fix: core dumps when no args given and args needed
// to refactor: builtins should all use argc argv -> type needs to accept multiple args
const int BUFFERSIZE_INPUT = 512;
const int BUFFERSIZE_PATH = 512;
const int BUFFERSIZE_ARGV = 32;
int function_search_path(char *input, char *valid_path) {
  char *path_env = getenv("PATH");
  if (path_env == NULL) {
    return EXIT_FAILURE;
  }
  char *path_env_cpy = strdup(path_env);
  char *curr_dir = strtok(path_env_cpy, ":");
  while (curr_dir) {
    snprintf(valid_path, BUFFERSIZE_PATH, "%s/%s", curr_dir, input);
    if (access(valid_path, X_OK) == EXIT_SUCCESS) {
      free(path_env_cpy);
      return EXIT_SUCCESS;
    }
    curr_dir = strtok (0, ":");
  }
  free(path_env_cpy);
  return EXIT_FAILURE;
}
void function_parse_input(int argc, char **argv, char *input) {
  char **argv_i = (char**)malloc(sizeof(char*) * (argc + 1));
  bool in_quotes = false;
  bool in_delimiter = true;
  int len = strlen(input) + 1;
  // printf("Initial input: %s\n", input);
  for (int i = 0; i < len; i++) {
    // printf("%d | Processing: %c | ", i, input[i]);
    if (input[i] == '\'') {
      in_quotes = in_quotes ? false : true;
      strcpy(input + i, input + i + 1);
      i--;
      len--;
      // printf("toggled quotes\n");
    }
    else if (!in_quotes && input[i] == ' ') {
      in_delimiter = true;
      input[i] = '\0';
      // printf("delimiter added\n");
      }
    else if (input[i] != '\0') {
      if (in_delimiter) {
        in_delimiter = false;
        argv_i = (char**)realloc(argv_i, sizeof(char*) * (argc + 1));
        argv_i[argc++] = &input[i];
        // printf("Added argv[%d]: index %d (addr %p) | ", argc - 1, i, argv_i[argc - 1]);
      }
    }
  }
  for (int i = 0; i < argc; i++) {
    argv[i] = argv_i[i];
    // printf("argv[%d]: %s (addr %p)\n", i, argv[i], argv_i[i]);
  }
  free(argv_i);
}

int builtin_echo(int argc, char **argv) {
  // bash: Output the args, separated by spaces, terminated with a newline. The return status is 0 unless a write error occurs. 
  for (int i = 1; i < argc; i++) {
    printf("%s", argv[i]);
    if (i != argc - 1)
      printf(" ");
  }
  printf("\n");
  return 0;
}
void builtin_type (char *input) {
  const char *type_builtins[] = {"echo", "exit", "type", "pwd", "cd"};
  for (size_t i = 0; i < (sizeof(type_builtins)/sizeof(*type_builtins)); i++) {
    if (strncmp(input, type_builtins[i], strlen(type_builtins[i])) == EXIT_SUCCESS) {
      printf("%s is a shell builtin\n", type_builtins[i]);
      return;
    }
  }
  char valid_path[BUFFERSIZE_PATH];
  if (function_search_path(input, valid_path) == EXIT_SUCCESS) {
    printf("%s is %s\n", input, valid_path);
  }
  else {
    printf("%s: not found\n", input);
  }
  return;
}
void builtin_pwd () {
      char cwd[BUFFERSIZE_PATH];
      getcwd(cwd, sizeof(cwd));
      printf("%s\n", cwd);
}
void builtin_cd (char *input) {
  // todo: make it fill in env vars regardless of location in argv
  if (strcmp(input, "$HOME") == 0 || strcmp(input, "~") == 0) {
    input = getenv("HOME");
  }
  if(chdir(input) < 0) {
    printf("cd: %s: No such file or directory\n", input);
  }
}
void builtin_execute (char *path, char **argv) {
  pid_t pid = fork();
  if (pid == EXIT_SUCCESS) {
    execvp(path, argv);
    perror("execvp failed\n"); 
    exit(1);
  }
  else if (pid < 0) {
    perror("fork failed\n");    
  }
  else {
    int status;
    waitpid(pid, &status, 0);
  }
}

int main() {
  setbuf(stdout, NULL);
  while (1) {
    char input[BUFFERSIZE_INPUT];
    int argc = 0;
    char *argv[BUFFERSIZE_ARGV];

    printf("$ ");
    fgets(input, BUFFERSIZE_INPUT, stdin);
    input[strlen(input) - 1] = '\0';

    function_parse_input(argc, argv, input);
    
    if (strcmp(argv[0], "exit") == 0 && argv[1][0] == '0') {
      return 0;
    }
    else if (strcmp(argv[0], "type") == 0) {
      builtin_type(argv[1]);
    }
    else if (strncmp(argv[0], "echo ", 5) == 0) {
      builtin_echo(argc, argv);
    }
    else if (strcmp(argv[0], "pwd") == 0) {
      builtin_pwd();
    }
    else if (strcmp(argv[0], "cd") == 0) {
      builtin_cd(argv[1]);
    }
    else {
      char valid_path[BUFFERSIZE_PATH];
      if (function_search_path(argv[0], valid_path) == 0) {
        builtin_execute(valid_path, argv);
      } else {
        printf("%s: command not found\n", argv[0]);
      }
    }
  }
}