2

Just a little background: the user enters a command\series of commands when each one of them separated with ';'. Then a function gets the input, copies the string until next ';' or end-of-string, and then the function starts working on that command it's just extracted. If it's an 'echo' command - special treatment, if it's an environmental variable call it has a special treatment, etc. When the command is ready to be executed, its sent to a function that forks and execute the command.

My final section in this assignment is to add the option to use pipes. For example: "ps|wc", the program will execute the first command (ps), and redirect the output as the second command (wc) input. Of course infinite amount of pipes in a single command has to be supported.

This function gets the whole command that contains the pipes (for example: "ls|wc|more"), and will send each command separated by '|' to the execute function after creating the pipes. the function already knows how many pipes there are in the whole command block.

void pipe_parse(char **args, char *str, int pipes_amount) {
    char curr_cmd[510];
    for (int i = 0; i < 510; ++i) curr_cmd[i] = '\0';
    int pipes_executed = 0;
    char *token;

    for (int i = 0, j = 0; i < strlen(str); ++i) {
        if (str[i] == '|' || str[i] == '\0') { // curr_cmd holds current command to execute
            curr_cmd[j] = '\0';
            j = 0;
            token = strtok(curr_cmd, " ");
            args[0] = token;

            int k = 0;
            for (; token != NULL; k++, token = strtok(NULL, " "))
                args[k] = token;

            for (; k < 10; k++) args[k] = NULL;

            int pipefd[2];
            int prev_pipefd[2] = {-1, -1};

            if (pipe(pipefd) == -1) {
                perror("ERR pipe");
                exit(1);
            }

            exec_command(args, 0, NULL, pipes_executed, pipes_amount, pipefd, prev_pipefd);
            pipes_executed++;
        } else {
            curr_cmd[j++] = str[i];
        }
    }

this function gets the command, forks, sets the pipes directions accordingly, and executes the command.

void exec_command(char **args, int ampersand, char *file_name, int pipe_num, int pipe_amount, int *pipefd,
                  int *prev_pipefd) {
    int status;
    pidd = fork();

    // child
    if (pidd == 0) {
        signal(SIGTSTP, SIG_DFL);

        if (redirect_flag == 1) {
            int fd = open(file_name, O_WRONLY | O_TRUNC | O_CREAT, 0644);
            dup2(fd, STDOUT_FILENO);
            if (fd == -1) perror("ERR file");
        }

        if (pipe_amount != 0) {
            for (int i = 0; i < 10; ++i)
                printf("!%s\t", args[i]);
            printf("\n");

            // first pipe
            if (pipe_num == 0) {
                printf("first pipe\n");
                close(pipefd[0]); // close read
                if (dup2(pipefd[1], STDOUT_FILENO) == -1) { // redirect stdout to write
                    perror("pipe 1 dup");
                    exit(EXIT_FAILURE);
                }
            }

                // last pipe
            else if (pipe_num == pipe_amount) {
                printf("last pipe\n");
                close(prev_pipefd[1]); // close unused write end of previous pipe
                if (dup2(prev_pipefd[0], STDIN_FILENO) == -1) { // redirect stdin to read end of previous pipe
                    perror("pipe 3 dup");
                    exit(EXIT_FAILURE);
                }
                close(pipefd[0]); // close unused read end of pipe
            }

                // mid pipes
            else {
                printf("mid pipe\n");
                close(prev_pipefd[1]); // close unused write end of previous pipe
                dup2(prev_pipefd[0], STDIN_FILENO); // redirect stdin to read end of previous pipe
                close(pipefd[0]); // close unused read end of pipe
                dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to write end of pipe
            }
        }

        execvp(args[0], args);
        printf("after exec\n");
        perror("ERR0");
        exit(1);
    }

        // error
    else if (pidd < 0) {
        perror("ERR1\n");
        exit(0);
    }

        // parent
    else {
        signal(SIGTSTP, father_handler);
        
        if (pipe_amount != 0) {
            if (prev_pipefd[0] != -1) close(prev_pipefd[0]);
            if (prev_pipefd[1] != -1) close(prev_pipefd[1]);
            prev_pipefd[0] = pipefd[0];
            prev_pipefd[1] = pipefd[1];
            printf("parent process\n");
        }
        
        if (ampersand == 0)
            waitpid(pidd, &status, WUNTRACED);

        legalCmds += 1; // inc cmds counter

        for (int i = 0; i < 10; ++i) // inc args counter
            if (args[i] != NULL)
                legalArgs += 1;
    }
}

Right now, when I enter a command like "ps|wc", it'll print the "parent process", "first pipe" and each of the args array elements (args array is the array gets sent to execution). So I believe the problem is within the pipes themselves somewhere.

#cmd:0|#args:0@/home/student/CLionProjects/Ex2/cmake-build-debug ps|wc
parent process
!ps !(null) !(null) !(null) !(null) !(null) !(null) !(null) !(null) !(null) 
first pipe
#cmd:1|#args:1@/home/student/CLionProjects/Ex2/cmake-build-debug

Our lecturer encourages us to use ChatGPT for some reason. I tried messing around with it but didn't get any relevant information. Tired to ask some friends for help but since the code architecture is so different for each person it's kinda hard to get a relevant assistant.

Barmar
  • 741,623
  • 53
  • 500
  • 612
EladO O
  • 21
  • 1
  • 1
    Where does `pipes_amount` come from? Why is it a parameter to `pipe_parse()` if that function is looking for the pipe characters? – Barmar May 05 '23 at 15:50
  • 1
    @Barmar before the command gets to the pipe_parse function, I have a function that checks if the command has any pipes in it, and counts them. The function returns the amount of pipes, so if it returns a value greater than zero, I know there are pipes. Also, I need to know how many pipes there are in order to determine if the command I'm sending to execute is the command before\after the pipe, or in between pipes incase there are more than one pipes in the command. And yes, I agree, lecturer said that this assignment will be harder than usual because we can use ChatGPT, not that it helped.. – EladO O May 05 '23 at 15:55
  • 2
    You shouldn't call `waitpid()` in `exec_command()`. You need to run all the commands in the pipeline concurrently, so you shouldn't wait for the first command to finish before starting the second command. – Barmar May 05 '23 at 16:04
  • 1
    @Barmar How so? If I won’t wait for the first command to execute how does the second command will know its input? Anyway, I tried to add to the if statement before the waitpid() the check for “pipe_amount != 0” but it still doesn’t work.. – EladO O May 05 '23 at 16:20
  • It gets it from the previous pipe. – Barmar May 05 '23 at 16:22
  • 1
    We want an [MRE](https://stackoverflow.com/help/minimal-reproducible-example). For C, we want a _single_ `.c` file [_with_ (e.g.) `#include `]. But, _no_ (e.g.) `#include "myprogram.h"`. For runtime errors, it should compile cleanly. We should be able to download it and build and run it on our local systems [if we so choose]. – Craig Estey May 05 '23 at 16:47

1 Answers1

1
  1. In pipe_parse, the definition of prev_pipefd is misplaced.
  2. Because it is loop scoped, it is being reset to -1 on each loop iteration.
  3. Thus, any changes to it from exec_command are lost.

To fix, move prev_pipefd to function scope [similar to pipes_executed].


For an example of a working shell pipe implementation, see my answer: fd leak, custom Shell


UPDATE:

I moved prev_pipefd (and pipefd after that as well) to function scope, no changes in the output when I enter a pipe command. Although I can see how the pipes creations could mess up when they was inside the loop. Is it possible I have unrelated other problem now? – EladO O

Okay, with the full code, a number of issues ...

  1. In pipe_parse, there is only one pipe call [done before the loop].
  2. This pipe call must be moved into the loop [just before the exec_command].
  3. In exec_command, doing waitpid before all pipe children have been forked/constructed is bad news.
  4. Installing the SIGCHLD [or any other] signal handler using signal prevents multiple signals from occuring [AFAICT]. Use sigaction instead.
  5. In get_input, getting a SIGCHLD during fgets will cause it to return NULL with errno set to EINTR. The buffer will be unchanged and we'll loop infinitely on the same [stale] command. Must check for this and reloop.
  6. In parse_input, ampersand_flag must be globa/file scope, so pipe_parse can use it.
  7. Because sig_child_handler does a waitpid, it can change errno. This would be disruptive to the base level, so the handler should save/restore the original value of errno.
  8. I/O redirection only works if not within a pipe. (e.g.) cat /etc/passwd > /tmp/out works but cat /etc/passwd | cat > /tmp/out doesn't [not fixed below].
  9. Doing for (int i = 0; i < strlen(str); i++) is O(n^2) instead of O(n). Replace with for (int i = 0; str[i] != 0; i++)
  10. IMO, "sidebar" comments (e.g. x = 5; // set the value) are less readable except for struct members.
  11. Various additional cleanups.

UPDATE #2:

Hey, first of all - thank you for your time and effort making changes to my program! Now, I cleaned the file with unifdef and ran it but I still can't use any pipe command. for example: "ps|wc" won't do anything besides printing "parent process" and giving me the prompt to next command input..

Okay, my bad. I didn't test this as thoroughly as I would have liked. The issue was the for loop [in pipe_parse]. Originally, I thought I had broken working code, but it turns out that your original loop had a bug.

Your original:

for (int i = 0, j = 0; i < strlen(str); ++i) {

The change I made:

for (int i = 0, j = 0; str[i] != 0; ++i) {

Your original was looping for one less than it should. In both cases above, exec_command would never get the final pipe command.

Here is the [truly ;-)] fixed version:

int len = strlen(str);
for (int i = 0, j = 0; i <= len; ++i) {

I've added this change to the code below.

as for the rest of the changes you made - some of them aren't really relevant due to how our lecturer wanted us to write the assignment, but still, thank you! Am I missing something regarding the pipe feature? – EladO O

Yes, I did some [style] cleanup. And, added some debug code. Aside from that, most of the changes were necessary. But, as far as that goes, just how did they conflict with how your lecturer wanted you to write the assignment?


Here is the corrected code. It is annotated with the bugs and fixes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if 1
#include <stdarg.h>
#include <errno.h>
#endif
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>

#define MAX_INPUT_LENGTH 510
#if 0
#define MAX_ARGS 10
#else
#define MAX_ARGS    30
#define MAX_INPUT_LENGTH_PLUS5 (MAX_INPUT_LENGTH + 5)
#endif

// Using LinkedList to store vars
typedef struct node {
    char *name;
    char *value;
    struct node *next;
} Node;

typedef struct linked_list {
    Node *head;
} LinkedList;

int get_input(char *input);
void parse_input(char *input, char **args, LinkedList *vars, int *stopped_process_pid);
void exec_command(char **args, int ampersand, char *file_name, int pipe_num, int pipe_amount, int *pipefd, int *prev_pipefd);
char *replace_vars(char *input, LinkedList *vars);
void quots_remover(char *str);
int is_echo(char *str);
int is_CD(char *str);
int is_BG(char *str);
int is_redirection(char *str);
int is_pipe(char *str);
void pipe_parse(char **args, char *str, int pipes_amount);
int have_illegal_spaces(char *str);
void spaces_remover(char *str);
void prompt_printer(int legCmds, int args);
void splitToEqual(const char *str, char *left_string, char *right_string);
int ampersand_finder_remover(char *str);
void initialize(LinkedList *list);
void insert(LinkedList *list, char *name, char *value);
Node *search(LinkedList *list, char *name);
void terminate(LinkedList *list);
void sig_child_handler(int n);
void father_handler(int n);

int pidd = 0;
#if 0
int last_pid = 0;
#else
volatile int last_pid = 0;
#endif
int redirect_flag = 0;
int legalArgs = 0,
    legalCmds = 0;

#if 1
int ampersand_flag;
#endif

#if DEBUG
#define dbgprt(_fmt...) \
    do { \
        _dbgprt(_fmt); \
    } while (0)
#else
#define dbgprt(_fmt...) \
    do { \
    } while (0)
#endif

#define dbgprtattr(_lvl) \
    __attribute__((__format__(__printf__,_lvl,_lvl + 1)))

// signal-safe(?) debug printf
void dbgprtattr(1)
_dbgprt(const char *fmt,...)
{
    va_list ap;
    char buf[1000];

    int sverr = errno;

    va_start(ap,fmt);
    int len = vsprintf(buf,fmt,ap);
    va_end(ap);

    write(2,buf,len);

    errno = sverr;
}

void
xsignal(int signo,void *hdr)
{
#if 0
// NOTE/BUG: this is a one shot -- we need to catch _multiple_ signals
    signal(signo,hdr);
#else
    struct sigaction act;

    sigaction(signo,NULL,&act);

    act.sa_flags |= SA_SIGINFO;
    act.sa_sigaction = hdr;
    sigemptyset(&act.sa_mask);

    sigaction(signo,&act,NULL);
#endif
}

int
main()
{
    xsignal(SIGCHLD, sig_child_handler);

    int noCmdCounter = 0,
        inputStatus,
        stopped_process_pid = 0;
#if 0
    char input[MAX_INPUT_LENGTH + 5];
#else
    char input[MAX_INPUT_LENGTH_PLUS5];
#endif
    char *args[MAX_ARGS];

#if 1
    setlinebuf(stderr);
#endif

    for (int i = 0; i < MAX_ARGS; ++i) {
        args[i] = "\0";
    }

    // int* pidd = malloc(sizeof(int));

    LinkedList vars;

    initialize(&vars);

    while (1) {
#if 1
        // reap detached jobs
        // FIXME/CAE -- may not be necessary with fixed SIGCHLD handler
#if 1
        while (1) {
            pid_t pid = waitpid(-1,NULL,WNOHANG);
            if (pid <= 0)
                break;
            dbgprt("main: WAIT pid=%d\n",pid);
        }
#endif
#endif

        // prompt
        prompt_printer(legalCmds, legalArgs);
        inputStatus = get_input(input);

        // input len is greater than max
        if (inputStatus == 0) {
            printf("ERR\n");
            continue;
        }

        // user pressed enter 3 times in a row
        else if (inputStatus == -1) {
            noCmdCounter++;

            if (noCmdCounter == 3)
                break;

            continue;
        }

        // legal input
        else {
            noCmdCounter = 0;
            parse_input(input, args, &vars, &stopped_process_pid);
        }
    }
    terminate(&vars);
}

// function that gets command input from the user
int
get_input(char *input)
{
//    printf("student@student-virtual-machine ~/ex1 $ ");
#if 1
    fflush(stdout);
#endif

#if 0
    // NOTE/BUG: with SIGCHLD handler, this can get EINTR and buffer is _not_
    // changed, so we loop infinitely on last command
    fgets(input, MAX_INPUT_LENGTH_PLUS5, stdin);
#else
    while (1) {
        if (fgets(input, MAX_INPUT_LENGTH + 5, stdin) != NULL)
            break;
        if (errno != EINTR)
            return -1;
        dbgprt("get_input: EINTR\n");
    }
#endif

    // replaces last char (new-line char) to NULL terminator
    input[strcspn(input, "\n")] = '\0';

    // if illegal input length
    if (strlen(input) > MAX_INPUT_LENGTH)
        return 0;

    // if no cmd entered
    else if (strlen(input) == 0)
        return -1;

    // if legal input length
    return 1;
}

// function that divides the commands and their arguments
void
parse_input(char *input, char **args, LinkedList *vars,
    int *stopped_process_pid)
{
#if 0
    char temp_str[520], temp_arg[520];
#else
    char temp_str[MAX_INPUT_LENGTH + 1], temp_arg[MAX_INPUT_LENGTH + 1];
#endif
    char *token;
    int input_len = strlen(input),
        equal_val = '=',
        idx,
        quot_counter = 0,
#if 0
// NOTE/BUG: this needs to be global
        ampersand_flag,
#endif
        pipe_counter = 0;

    dbgprt("parse_input: ENTER\n");

    // ----------------------copying each command seperated by
    // ';'-----------------------------------

    // copying each command seperated by ';'
    for (int i = 0, j = 0; i <= input_len; ++i) {
        dbgprt("parse_input: CHAR i=%d j=%d chr='%c'\n",i,j,input[i]);

        if (input[i] == '\"') {
            if (quot_counter == 1)
                quot_counter = 0;
            else
                quot_counter = 1;
        }

        // end of command
        if ((input[i] == ';' || input[i] == '\0') && quot_counter == 0) {
            char *file_name;

            temp_str[j] = '\0';
            redirect_flag = 0;
            ampersand_flag = 0;
            ampersand_flag = ampersand_finder_remover(temp_str);
            quot_counter = 0;
            dbgprt("command is : %s\n", temp_str);
            j = 0;

            // ******************working on each command***********************
            pipe_counter = is_pipe(temp_str);
            if (pipe_counter > 0) {
                pipe_parse(args, temp_str, pipe_counter);
                continue;
            }

            // redirection
            if (is_redirection(temp_str) == 1) {
                redirect_flag = 1;

                // holds the cmd
                char *command_to_redirect = strtok(temp_str, ">");

                // holds the file name
                file_name = strtok(NULL, ">");
                spaces_remover(file_name);
                dbgprt("@%s@\t@%s@\n", command_to_redirect, file_name);
                dbgprt("--%s--\n", temp_str);
            }

            if (is_CD(temp_str) == 1) {
                printf("CD not supported!\n");
                continue;
            }

            // 'echo' cmd
            if (is_echo(temp_str) == 1 && pipe_counter == 0) {
                token = strtok(temp_str, " ");
                // inserting "echo" to first exec array element
                args[0] = token;
                token = strtok(NULL, "\0");
                // inserting echo's argument into second exec array element
                args[1] = token;
                args[2] = "\0";
                for (int k = 0; k < MAX_ARGS; ++k) {
                    if (k != 0 && k != 1)
                        args[k] = '\0';
                    if (k == 1) {
                        strcpy(temp_arg, replace_vars(args[1], vars));
                        quots_remover(temp_arg);
                        args[1] = temp_arg;
                    }
                }
            }

            // if cmd has '=' then it's a variable cmd
            else if (strchr(temp_str, equal_val) != NULL && pipe_counter == 0) {
                char temp_str2[520];

                strcpy(temp_str2, temp_str);
                if (have_illegal_spaces(temp_str2) == 0) {
                    char left[520],
                     right[520];

                    // left/right holds the two parts of the variable
                    splitToEqual(temp_str, left, right);

                    char var_name[520];

                    strcpy(var_name, left);
                    spaces_remover(var_name);

                    char var_value[520];

                    strcpy(var_value, right);
                    quots_remover(var_value);

                    insert(vars, var_name, var_value);
                    continue;
                }
            }

            else if (is_BG(temp_str) == 1 && pipe_counter == 0) {
                kill(last_pid, SIGCONT);
                continue;
            }

            // regular command
            else {
                idx = 0;
                char *p = (char *) malloc(sizeof(char *));

                p = replace_vars(temp_str, vars);
                strcpy(temp_arg, p);

                token = strtok(temp_arg, " ");
                while (token != NULL) {
                    spaces_remover(token);
                    args[idx++] = token;
                    token = strtok(NULL, " ");
                }
                while (idx < MAX_ARGS) {
                    args[idx++] = NULL;
                }
            }

            if (redirect_flag == 1) {
                exec_command(args, ampersand_flag, file_name, 0, 0, NULL, NULL);
                continue;
            }
            exec_command(args, ampersand_flag, NULL, 0, 0, NULL, NULL);
        }

        // ***************************************************************

        // copying next letter
        else {
            temp_str[j] = input[i];
            j++;
        }
    }

    dbgprt("parse_input: EXIT\n");
    // ------------------------------------------------------------------------
}

void
printArr(char **arr)
{
    dbgprt("printArr:\n");

    for (int i = 0; arr[i] != NULL; i++)
        dbgprt("%s\n", arr[i]);
}

// function that creates a son process and sends the command to execvp
void
exec_command(char **args, int ampersand, char *file_name,
    int pipe_num, int pipe_amount, int *pipefd, int *prev_pipefd)
{
    int status;

    dbgprt("exec_command: ENTER ampersand=%d file_name='%s' pipe_num=%d pipe_amount=%d pipefd=%p prev_pipefd=%p\n",
        ampersand,file_name,pipe_num,pipe_amount,pipefd,prev_pipefd);

    for (int i = 0; i < MAX_ARGS; ++i) {
        if (args[i] == NULL)
            continue;
        dbgprt("exec_command: ARGV/%d args='%s'\n",i,args[i]);
    }

    pidd = fork();

    // child
    if (pidd == 0) {
        xsignal(SIGTSTP, SIG_DFL);

        if (redirect_flag == 1) {
            int fd = open(file_name, O_WRONLY | O_TRUNC | O_CREAT, 0644);
            dup2(fd, STDOUT_FILENO);
            if (fd == -1)
                perror("ERR file");
        }

        if (pipe_amount != 0) {
            // first pipe
            if (pipe_num == 0) {
                dbgprt("first pipe\n");

                // close read
                close(pipefd[0]);

                // redirect stdout to write
                if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
                    perror("pipe 1 dup");
                    exit(EXIT_FAILURE);
                }
            }

            // last pipe
            else if (pipe_num == pipe_amount) {
                dbgprt("last pipe\n");

                // close unused write end of previous pipe
                close(prev_pipefd[1]);

                // redirect stdin to read end of previous pipe
                if (dup2(prev_pipefd[0], STDIN_FILENO) == -1) {
                    perror("pipe 3 dup");
                    exit(EXIT_FAILURE);
                }

                // close unused read end of pipe
                close(pipefd[0]);
            }

            // mid pipes
            else {
                dbgprt("mid pipe\n");

                // close unused write end of previous pipe
                close(prev_pipefd[1]);

                // redirect stdin to read end of previous pipe
                dup2(prev_pipefd[0], STDIN_FILENO);

                // close unused read end of pipe
                close(pipefd[0]);

                // redirect stdout to write end of pipe
                dup2(pipefd[1], STDOUT_FILENO);
            }
        }

        execvp(args[0], args);
        printf("after exec\n");
        perror("ERR0");
        exit(1);
    }

    // error
    else if (pidd < 0) {
        perror("ERR1\n");
        exit(2);
    }

    // parent
    else {
        if (pipe_amount != 0) {
            if (prev_pipefd[0] != -1)
                close(prev_pipefd[0]);
            if (prev_pipefd[1] != -1)
                close(prev_pipefd[1]);
            prev_pipefd[0] = pipefd[0];
            prev_pipefd[1] = pipefd[1];
            printf("parent process\n");
        }

        xsignal(SIGTSTP, father_handler);

#if 0
// NOTE/BUG: parent must _not_ wait until all pipe sections are created
        if (ampersand == 0)
            waitpid(pidd, &status, WUNTRACED);
#endif

        // inc cmds counter
        legalCmds += 1;

        // inc args counter
        for (int i = 0; i < MAX_ARGS; ++i)
            if (args[i] != NULL)
                legalArgs += 1;
    }

    dbgprt("exec_command: EXIT\n");

//    if (prev_pipefd[0] != -1) close(prev_pipefd[0]);
//    if (prev_pipefd[1] != -1) close(prev_pipefd[1]);
}

// function that removes quots from a string
void
quots_remover(char *str)
{
    int len = strlen(str);
    int i, j;

    for (i = 0, j = 0; i < len; i++)
        if (str[i] != '\"')
            str[j++] = str[i];
    str[j] = '\0';
}

// function that checks if it's an echo command
int
is_echo(char *str)
{
    int counter = 0;

    for (int i = 0; str[i] != 0; ++i) {
        if (str[i] == 'e') {
            counter++;
            continue;
        }
        if (str[i] == 'c' && counter == 1) {
            counter++;
            continue;
        }
        if (str[i] == 'h' && counter == 2) {
            counter++;
            continue;
        }
        if (str[i] == 'o' && counter == 3) {
            counter++;
            break;
        }

        counter = 0;
    }
    if (counter == 4)
        return 1;
    return 0;
}

int
is_CD(char *str)
{
    int counter = 0;

    for (int i = 0; str[i] != 0; ++i) {
        if (str[i] == 'c') {
            counter++;
            continue;
        }
        if (str[i] == 'd' && counter == 1) {
            counter++;
            break;
        }

        counter = 0;
    }
    if (counter == 2)
        return 1;
    return 0;
}

int
is_BG(char *str)
{
    int counter = 0;

    for (int i = 0; str[i] != 0; ++i) {
        if (str[i] == 'b') {
            counter++;
            continue;
        }
        if (str[i] == 'g' && counter == 1) {
            counter++;
            break;
        }

        counter = 0;
    }
    if (counter == 2)
        return 1;
    return 0;
}

// function that determines how many pipes the command have, if any
int
is_pipe(char *str)
{
    int counter = 0;

    for (int i = 0; str[i] != 0; ++i)
        if (str[i] == '|')
            counter++;
    return counter;
}

// function that parses the command by pipes
void
pipe_parse(char **args, char *str, int pipes_amount)
{
    char curr_cmd[MAX_INPUT_LENGTH];

    for (int i = 0; i < MAX_INPUT_LENGTH; ++i)
        curr_cmd[i] = '\0';
    int pipes_executed = 0;
    char *token;

    int pipefd[2];
    int prev_pipefd[2] = { -1, -1 };

    dbgprt("pipe_parse: ENTER\n");

#if 0
// NOTE/BUG: we need a new pipe for each pipe stage
    if (pipe(pipefd) == -1) {
        perror("ERR pipe");
        exit(1);
    }
#endif

#if 0
    // Elad's original code
    // NOTE/BUG: this is looping one too few
    for (int i = 0, j = 0; i < strlen(str); ++i) {
#endif
#if 0
    // Craig's original change
    for (int i = 0, j = 0; str[i] != 0; ++i) {
#endif
#if 1
    // corrected loop
    int len = strlen(str);
    for (int i = 0, j = 0; i <= len; ++i) {
#endif
        if (str[i] == '|' || str[i] == '\0') {
            curr_cmd[j] = '\0';
            j = 0;
            dbgprt("pipe_parse: CURR curr_cmd='%s'\n",curr_cmd);
            // printf("@%s@\n", curr_cmd);

            token = strtok(curr_cmd, " ");
            args[0] = token;

            int k = 0;

            for (; token != NULL; k++, token = strtok(NULL, " "))
                args[k] = token;

            for (; k < MAX_ARGS; k++)
                args[k] = NULL;

#if 1
// NOTE/FIX: we need a new pipe for each pipe stage
            if (pipe(pipefd) == -1) {
                perror("ERR pipe");
                exit(1);
            }
#endif

            exec_command(args, 0, NULL, pipes_executed, pipes_amount, pipefd, prev_pipefd);
            pipes_executed++;
        }

        else {
            curr_cmd[j++] = str[i];
        }
    }

#if 1
    // reap foreground pipe jobs
    if (ampersand_flag == 0) {
        while (1) {
            pid_t pid = waitpid(-1, NULL, WUNTRACED);
            if (pid <= 0)
                break;
            dbgprt("pipe_parse: WAIT pid=%d\n",pid);
        }
    }
#endif

    dbgprt("pipe_parse: EXIT\n");
}

// function that checks if an environmental variable command has illegal spaces
int
have_illegal_spaces(char *str)
{

    char *temp = str;
    char *toke = strtok(temp, "=");
    int flag = 0;

    for (int i = 0; toke[i] != 0; ++i) {
        if (temp[i] != ' ') {
            flag = 1;
        }
        if (temp[i] == ' ' && flag != 0)
            return 1;
    }
    return 0;
}

// function that removes spaces
void
spaces_remover(char *str)
{
    int len = strlen(str);
    int i,
     j;

    for (i = 0, j = 0; i < len; i++)
        if (str[i] != ' ')
            str[j++] = str[i];
    str[j] = '\0';
}

// function that prints the prompt line
void
prompt_printer(int legCmds, int args)
{
    char cwd[256];

    if (getcwd(cwd, sizeof(cwd)) == NULL)
        // TODO: CHANGE TO WHAT?
        printf("ERR getcwd");
    else
        printf("#cmd:%d|#args:%d@%s ", legCmds, args, cwd);
#if 1
    fflush(stdout);
#endif
}

// function that replaces all variables calls with their values
char *
replace_vars(char *input, LinkedList *vars)
{
    char *temp = (char *) malloc(520);

    // TODO: add * before temp inside sizeof ??
    memset(temp, '\0', sizeof(*temp));
    char name_temp[520];

    memset(name_temp, '\0', sizeof(name_temp));
    int flag = 0,
        tempIdx = 0,
        nameIdx = 0;
    char c[2];

    c[1] = 0;

#if 0
    for (int i = 0; i < strlen(input) + 1; ++i) {
#else
    int len = strlen(input);
    for (int i = 0; i < len + 1; ++i) {
#endif
        if (input[i] == '$')
            flag = 1;
        // check for var in list
        else if (input[i] == ' ' || input[i] == '\0' || input[i] == '\"') {
            flag = 0;
            Node *temp_node = search(vars, name_temp);

            // var exist
            if (temp_node != NULL) {
                strcat(temp, temp_node->value);
            }
            // var doesn't exist
            else {
                strcat(temp, " ");
            }
            memset(name_temp, '\0', sizeof(name_temp));
            if (input[i] == ' ') {
                strcat(temp, " ");
            }
        }
        // copy char
        else if (input[i] != ' ') {
            sprintf(c, "%c", input[i]);
            // printf("%c\n", c[0]);

            // copy to name
            if (flag == 1) {
                strcat(name_temp, c);
            }
            // copy to temp
            else {
                strcat(temp, c);
            }
        }
    }
    return temp;
}

// function that splits the environmental variable
void
splitToEqual(const char *str, char *left, char *right)
{
    memset(left, '\0', MAX_INPUT_LENGTH);
    memset(right, '\0', MAX_INPUT_LENGTH);
    int flag = 0,
        j = 0;

    for (int i = 0; str[i] != 0; ++i) {
        if (str[i] == '=') {
            flag = 1, j = 0;
        }
        else {
            if (flag == 0)
                left[j++] = str[i];
            else
                right[j++] = str[i];
        }
    }
}

void
sig_child_handler(int n)
{
#if 0
// NOTE/BUG: try to reap more
    waitpid(-1, NULL, WNOHANG);
#else
    int sverr = errno;

    while (1) {
        pid_t pid = waitpid(-1, NULL, WNOHANG);
        if (pid <= 0)
            break;
        dbgprt("sig_child_handler: pid=%d\n",pid);
    }

    errno = sverr;
#endif
}

void
father_handler(int n)
{
    last_pid = pidd;
}

// function that looks if command has ampersand, if it does it'll delete it
int
ampersand_finder_remover(char *str)
{
    int i,
     j;
    int insideQuotes = 0;
    int deleted = 0;

    for (i = 0, j = 0; str[i]; i++) {
        if (str[i] == '\"') {
            // toggle flag
            insideQuotes = !insideQuotes;
        }
        else if (str[i] == '&' && !insideQuotes) {
            deleted = 1;
            // skip ampersand if not inside quotes
            continue;
        }
        else {
            str[j++] = str[i];
        }
    }
    str[j] = '\0';
    return deleted;
}

// function that checks for '>' char and determines if the cmd is a concat cmd
int
is_redirection(char *str)
{
    for (int i = 0; str[i] != 0; ++i)
        if (str[i] == '>')
            return 1;
    return 0;
}

// Function to initialize the linked list
void
initialize(LinkedList *list)
{
    list->head = NULL;
}

// function to insert a new node into the linked list
void
insert(LinkedList *list, char *name, char *value)
{
    Node *new_node = malloc(sizeof(Node));

    if (new_node == NULL) {
        printf("ERR");
        exit(1);
    }
    new_node->name = strdup(name);
    new_node->value = strdup(value);
    new_node->next = NULL;

    // check if list is empty
    if (list->head == NULL) {
        list->head = new_node;
    }

    // search the node with the same name
    else {
        Node *current_node = list->head;

        while (current_node != NULL) {
            if (strcmp(current_node->name, name) == 0) {
                // replace the existing node with the new node

                free(current_node->value);
                current_node->value = strdup(value);
                free(new_node->name);
                free(new_node->value);
                free(new_node);
                return;
            }
            current_node = current_node->next;
        }

        // Insert the new node at the end of the list
        current_node = list->head;
        while (current_node->next != NULL) {
            current_node = current_node->next;
        }
        current_node->next = new_node;
    }
}

// Function to search for a node with a given name
Node *
search(LinkedList *list, char *name)
{
    Node *current_node = list->head;

    while (current_node != NULL) {
        if (strcmp(current_node->name, name) == 0) {
            return current_node;
        }
        current_node = current_node->next;
    }
    return NULL;
}

// function to terminate the linked list and free all memory
void
terminate(LinkedList *list)
{
    Node *current_node = list->head;

    while (current_node != NULL) {
        Node *next_node = current_node->next;

        free(current_node->name);
        free(current_node->value);
        free(current_node);
        current_node = next_node;
    }
    list->head = NULL;
}

In the code above, I've used cpp conditionals to denote old vs. new code:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

Note: this can be cleaned up by running the file through unifdef -k

Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • I moved prev_pipefd (and pipefd after that as well) to function scope, no changes in the output when I enter a pipe command. Although I can see how the pipes creations could mess up when they was inside the loop. Is it possible I have unrelated other problem now? – EladO O May 06 '23 at 00:08
  • @EladOO That's why I suggested the MRE in my top comment. Although the issue is probably in the code you've already posted, because there wasn't a full program, so downloading/building/running (under `gdb`) wasn't an option, so I couldn't verify that the `prev_pipefd` change was the only one needed. Or, that there was/is an issue in the code _not_ posted. – Craig Estey May 06 '23 at 00:15
  • Since the code is in one file as the assignment requirement, i'll just share the whole file. I don't know what parts are needed or not anyway so I hope that will be good. https://pastebin.com/UyaXkWjz It's probably very messy, since we got more and more features to implement in the code as follow-up assignments, due to lack of time I just had to find the quickest possible solutions every time.. – EladO O May 06 '23 at 11:17
  • @EladOO A number of bugs. I listed the bugs and remedies with working code. I'm short on time here, but I wanted to get you the fix sooner. Otherwise, I would have added a section on the steps I took to debug the program. – Craig Estey May 07 '23 at 00:47
  • Hey, first of all - thank you for your time and effort making changes to my program! Now, I cleaned the file with unifdef and ran it but I still can't use any pipe command. for example: "ps|wc" won't do anything besides printing "parent process" and giving me the prompt to next command input.. as for the rest of the changes you made - some of them aren't really relevant due to how our lecturer wanted us to write the assignment, but still, thank you! Am I missing something regarding the pipe feature? – EladO O May 07 '23 at 11:47