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]);
      }
    }
  }
}

@VestaCord Sorry for the delayed response!

I tried running your code locally and ran into this error:

The reason it worked on your machine might be because the memory following argv was initialized to 0/NULL, but that behavior isn’t guaranteed across all systems.

Let me know if you still need asistance with this!

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.