Stderr redirection

I’m stuck on Stage #VZ4

I’ve tried commenting out all extra error messages I had initially added, but somehow when the test calls “ls” which is not a built in shell command it spits out an error message that is specific to “cd” which is built in command. It also spits the error to stdout instead of stderr which makes it fail the test.

Here are my logs:

[compile] Compilation successful.

Debug = true

[tester::#VZ4] Running tests for Stage #VZ4 (Redirection - Redirect stderr)
[tester::#VZ4] [setup] export PATH=/tmp/raspberry/apple/banana:$PATH
[tester::#VZ4] Running ./your_program.sh
[tester::#VZ4] [setup] echo -n "apple" > "/tmp/baz/apple"
[your-program] $ ls -1 nonexistent 2> /tmp/qux/foo.md
[your-program] ls: /tmp/qux/foo.md: No such file or directory
[tester::#VZ4] Expected prompt ("$ ") but received "ls: /tmp/qux/foo.md: No such file or directory"
[your-program] ls: nonexistent: No such file or directory
[your-program] $ 
[tester::#VZ4] Assertion failed.
[tester::#VZ4] Test failed

And here’s a snippet of my code:

    std::unordered_set<std::string> builtIn = {"echo", "exit", "type", "pwd", "cd"};
    std::unordered_set<char> escapedChars = {'\\', '$', '\"', '\n'};
    std::unordered_set<std::string> outRedirect = {">", "1>"};
    std::vector<std::string> pathDirs;
    std::string currentDir;
    int stdOut, stdError;
    bool out = false, error = false;

    std::vector<std::string> tokenizeInput(const std::string& input) {
        bool singleQuote = false, doubleQuote = false;
        std::vector<std::string> tokens;
        std::string token;

        for (size_t i = 0; i < input.size(); i++) {
            char c = input[i];
            if (c == ' ' && !singleQuote && !doubleQuote) {
                if (token == "2>") error = true;
                else if (!token.empty() && outRedirect.find(token) == outRedirect.end() && !out) tokens.push_back(token);
                else if (outRedirect.find(token) != outRedirect.end()) out = true;
                else if (out && !token.empty()) handleOutputRedirection(token); 
                else if (error && !token.empty()) handleErrorRedirection(token);
                token.clear();
            } else if (c == '\'' && !doubleQuote) {
                singleQuote = !singleQuote;
            } else if (c == '\"' && !singleQuote) {
                doubleQuote = !doubleQuote;
            } else if (c == '\\') {
                if (singleQuote) {
                    token += c;
                } else if (doubleQuote) {
                    if (i+1 < input.size() && escapedChars.find(input[i+1]) != escapedChars.end()) {
                        token += input[++i];
                    } else {
                        token += c;
                    }
                } else if (i+1 < input.size()) {
                    token += input[++i];
                }
            } else token += c;
        }

        if (!token.empty() && !out) tokens.push_back(token);
        else if (!token.empty()) handleOutputRedirection(token);

        return tokens;
    }

    void handleOutputRedirection(std::string file) {
        int fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd == -1) {
            // perror("open");
            return;
        }

        dup2(fd, STDOUT_FILENO);
        close(fd);
    }

    void handleErrorRedirection(std::string file) {
        int fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd == -1) {
            // perror("open");
            return;
        }

        dup2(fd, STDERR_FILENO);
        close(fd);
    }

    void closeErrorRedirect() {
        dup2(stdError, STDERR_FILENO);
        error = false;
    }

    void closeOutputRedirect() {
        dup2(stdOut, STDOUT_FILENO);
        out = false;
    }

Code for cd and external commands:

    void handleCd(const std::vector<std::string>& tokens) {
        int status = 0;
        if (tokens.size() == 1 || tokens[1][0] == '~') {
            status = chdir(getenv("HOME"));
        } else {
            status = chdir(tokens[1].c_str());
        }

        if (status == -1) {
            std::cerr << tokens[0] << ": " << tokens[1] << ": No such file or directory" << std::endl;
        } else {
            currentDir = std::filesystem::current_path();
        }
    }

    void executeExternalCommand(const std::vector<std::string>& tokens) {
        std::vector<char*> args(tokens.size() + 1);
        for(size_t i = 0; i < tokens.size(); i++) {
            args[i] = const_cast<char*>(strdup(tokens[i].c_str()));
        }
        args[tokens.size()] = nullptr;
        
        pid_t pid = fork();
        
        if (pid < 0) { 
            // perror("fork");
            return;
        } else if (pid > 0) {  // Parent
            int status;
            if (waitpid(pid, &status, 0) == -1) {
                // perror("waitpid");
                return;
            }
            
            if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
                std::cerr << tokens[0] << ": command not found" << std::endl;
            } else if (WIFSIGNALED(status)) {
                // std::cout << tokens[0] << ": terminated by signal " << WTERMSIG(status) << std::endl;
            } else if (WIFEXITED(status) != 0 && WEXITSTATUS(status) != 0) {
                // std::cout << tokens[0] << ": exit status " << WEXITSTATUS(status) << std::endl;
            }
        } else if (pid == 0) {  // Child    
            execvp(tokens[0].c_str(), args.data());
            // std::cerr << "Execution failed for command: " << tokens[0] << std::endl;
            // perror("execvp");
            exit(127);
        }

        for (size_t i = 0; i < tokens.size(); i++) {
            free(args[i]);
        }
    }

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

Link to repo: [redacted by Andy]

@BardhT Sorry for the delayed response!

It also spits the error to stdout instead of stderr which makes it fail the test.

Could you share more details on how you verified that? Would be helpful to pinpoint what’s going wrong.

It seems to me you only “complete” tokens when they are succeeded by a space in your tokenizeInput. So, to me, it looks like, if you see 2> that you set error to true. Then on your next token you would ideally match (error && !token.empty()) and run handleErrorRedirection(token).

But what happens when the input ends without a ' ' after it; do you modify the input command to always have an extra space at the end? Otherwise the, assuming no output redirection, if will just add the file name to the tokens list before returning at the end. It does not seem like handleErrorRedirection would trigger.

This might explain the error you get too: instead of redirecting to /tmp/qux/foo.md, you are just passing it as a argument I guess, and since the file doesn’t exist you are told: "ls: /tmp/qux/foo.md: No such file or directory".

1 Like

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.