Works Locally, Fails on Submission (Formatting Issue?)

Yesterday, I completed 22 of 43 stages. I planned to redo the autocompletion part from scratch, so I paused. Today I updated my code, but the redirection stage is failing. After some research, I found that formatting differs across OSs — on my MacBook Air, I got the desired results.

Could my code be picking up the wrong ls? Logs are attached below.

[tester::#UN3] Running tests for Stage #UN3 (Redirection - Append stderr)
[tester::#UN3] [setup] export PATH=/tmp/blueberry/apple/blueberry:$PATH
[tester::#UN3] Running ./your_program.sh
[your-program] $ ls -1 nonexistent >> /tmp/qux/bar.md
[your-program] ls: cannot access 'nonexistent': No such file or directory
[tester::#UN3] ^ Line does not match expected value.
[tester::#UN3] Expected: "ls: nonexistent: No such file or directory"
[tester::#UN3] Received: "ls: cannot access 'nonexistent': No such file or directory"
[your-program] $ 
[tester::#UN3] Assertion failed.
[tester::#UN3] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details)

Here’s the code which handles non-builtin commands:


void exec_external(char **argv, struct exec_info **execs, int n_exc) {
    struct exec_info *ei = find_exec(argv[0], execs, n_exc);
    if (strcmp(argv[0], "ls") == 0) {
        printf("%s\n", ei->e_path);
    }
    if (ei == NULL) {
        printf("%s: command not found\n", argv[0]);
        return;
    }
    pid_t pid;

    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        execv(ei->e_path, argv);
    } else {    
        if (waitpid(pid, NULL, 0) != pid) {
            perror("waitpid");
            exit(1);
        }
    }
}

The parser remains unchanged. My current idea is to, before entering the REPL loop, traverse all the paths, locate all executables, and store them in a structure like this:

struct exec_info {
    char *e_name;
    int e_len;
    char *e_path;
};

Each entry will be dynamically allocated — e_name for the executable name, e_len for its length, and e_path for the absolute path. I’ll then perform a binary search to quickly retrieve the associated data and work with it as needed.


#include "exec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>

int exec_compare_sort(const void *a, const void *b) {
    const struct exec_info *ea = *((const struct exec_info**)a);
    const struct exec_info *eb = *((const struct exec_info**)b);
    return strcmp(ea->e_name, eb->e_name);
}

int exec_compare_bin_search(const void *key, const void *elem) {
    const struct exec_info *e_key = (const struct exec_info*)key;
    const struct exec_info *e_elem = *((const struct exec_info**)elem);
    return strcmp(e_key->e_name, e_elem->e_name);
}

struct exec_info *find_exec(char *name, struct exec_info **execs, int n_exc) {
    struct exec_info key = {
        .e_name = name,
        .e_len = strlen(name),
        .e_path = NULL
    };
    struct exec_info **res = bsearch(&key, execs, n_exc, sizeof(struct exec_info*), exec_compare_bin_search);
    return res ? *res : NULL;
}

struct exec_info **build_exec_list(char **paths, int n_paths, int *n_exc, int *mx_len) {
    int cap = 16, len = 0, tmp_len = 0;

    struct exec_info **exc_list = malloc(cap * sizeof(struct exec_info*));

    char *built_in[] = {"echo", "pwd", "cd", "exit", "type"};

    for (int i = 0; i < 5; i++) {
        exc_list[len] = malloc(sizeof(struct exec_info));
        exc_list[len]->e_name = strdup(built_in[i]);
        exc_list[len]->e_len = strlen(built_in[i]);
        exc_list[len++]->e_path = strdup("-");
    }

    for (int i = 0; i < n_paths; i++) {
        DIR *d = opendir(paths[i]);
        if (!d) continue;

        struct dirent *di;
        while ((di = readdir(d)) != NULL) {
            if (di->d_name[0] == '.' || di->d_type == DT_DIR) continue;

            int okay = 1;
            for (int i = 0; i < 5; i++) {
                if (strcmp(built_in[i], di->d_name) == 0) {
                    okay = 0;
                    break;
                }
            }
            if (!okay) continue;

            char full[256];
            sprintf(full, "%s/%s", paths[i], di->d_name);
            if (access(full, F_OK | X_OK) == 0) {
                if (len == cap) {
                    cap *= 2;
                    exc_list = realloc(exc_list, cap * sizeof(struct exec_info*));
                }
                int l = strlen(di->d_name);
                if (l > tmp_len) {
                    tmp_len = l;
                }
                exc_list[len] = malloc(sizeof(struct exec_info));
                exc_list[len]->e_name = strdup(di->d_name);
                exc_list[len]->e_len = l;
                exc_list[len++]->e_path = strdup(full);
            }
        }
        closedir(d);
    }
    *mx_len = tmp_len;
    *n_exc = len;

    qsort(exc_list, len, sizeof(struct exec_info*), exec_compare_sort);//sort for auto-completion

    return exc_list;
}

}

Hey @E1gocn, could you upload your code to GitHub and share the link? It will be much easier to debug if I can run it directly.

1 Like

@E1gocn I tried running your code against the previous stages, but it’s actually no longer passing a previous stage #MG5 (Locate executable files).

Suggestions:

  1. Use our CLI to test against previous stages by running:
codecrafters test --previous
  1. Focus on fixing the early stages first, as later stages depend on them.

Let me know if you’d like help debugging this one!

[tester::#MG5] Running tests for Stage #MG5 (Locate executable files)
[tester::#MG5] [setup] export PATH=/tmp/bar:$PATH
[tester::#MG5] [setup] export PATH=/tmp/baz:$PATH
[tester::#MG5] [setup] export PATH=/tmp/qux:$PATH
[tester::#MG5] [setup] PATH is now: /tmp/qux:/tmp/baz:/tmp/bar:/vcpkg:...
[tester::#MG5] [setup] Available executables:
[tester::#MG5] [setup] - my_exe
[tester::#MG5] Running ./your_program.sh
[your-program] $ type cat
[your-program] cat is /bin/cat
[tester::#MG5] ^ Line does not match expected value.
[tester::#MG5] Expected: "cat is /usr/bin/cat"
[tester::#MG5] Received: "cat is /bin/cat"
[your-program] $ 
[tester::#MG5] Assertion failed.
[tester::#MG5] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details)

My program does print output, but it’s wrong — it finds cat, yet the path it shows isn’t correct. I’m not sure why, since I never changed or modified the paths manually. I just went through each directory in the PATH variable, looked for executables, and saved their paths in e_path. I didn’t strip, append, or index anything, so I don’t understand how the paths ended up mismatched.


    char *env_path = strdup(getenv("PATH"));
    char *env_home = strdup(getenv("HOME"));

    char *paths[128];
    size_t n_paths = 0;

    char *tmp = strtok(env_path, ":");
    while (tmp != NULL) {
        paths[n_paths++] = tmp;
        tmp = strtok(NULL, ":");
    }
    paths[n_paths] = NULL;
//line 61-62 exec.c
char full[256];
sprintf(full, "%s/%s", paths[i], di->d_name);
//line 72-75 exec.c
                exc_list[len] = malloc(sizeof(struct exec_info));
                exc_list[len]->e_name = strdup(di->d_name);
                exc_list[len]->e_len = l;
                exc_list[len++]->e_path = strdup(full);

I found the root cause. I wasn’t aware that the POSIX standard specifies that when a command name isn’t a shell builtin, the shell must search each directory in $PATH from left to right until it finds an executable with that name.

My previous binary search ignored the $PATH order and could return any matching entry, regardless of its position in $PATH. After adjusting the lookup logic to respect the path order, it now works correctly.

1 Like

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