2

I'm writing a shell as a hobby and more specifically because I'm bored but passionate. My shell works great, I even implemented pipelines. But there is one thing that keeps my shell crashing or entering in a for loop and it's only happening when I run bash through my shell.

So I'm in trouble when I issue this command bash -ic <some_command>. If my shell is the default one and I launch an instance and I issue this command above, my shell gets in an infinite loop. Whereas if the default shell is bash and I launch an instance then I run my shell through the first bash prompt and THEN run bash -ic <some_command> through my shell, it gets stopped and I'm back to bash.

Here is a minimal example of the main issue:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void loop_pipe(char ***cmd) 
{
    pid_t pid, wpid;
    int status;

    pid = fork();
    if (pid == 0)
    {
        // Child process
        if (execvp((*cmd)[0],*cmd) == -1)
            perror("Command error");
        exit(EXIT_FAILURE);
    }
    else if (pid < 0)
    {
        // Error forking
        perror("Fork error");
    }
    else
    {
        // Parent process
        do {
            wpid = waitpid(pid, &status, WUNTRACED);
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }

}

int main()
{
    char *ls[] = {"bash", "-ic", "uname", NULL};
    char **cmd[] = {ls, NULL};

    while (1)
    {
        loop_pipe(cmd);
    }
}

The problem here is that after running the command, the process gets stopped so the output is this:

./a.out 
Linux

[4]+  Stopped                 ./a.out

I really don't now what's causing this, but it has to do with bash conflicting with my shell. I also tried to ignore SIGINT and SIGSTOP But it still gets stopped (by bash i guess ?) If someone could help the situation that would be great.

Because my project has more than one source file, I link it not sure if it's right way to do it.

Liwinux
  • 87
  • 6
  • 1
    There should not be a problem of producing a [MCVE] for this. Just write a small program that is invoking the `bash` the same way as your shell does. – Eugene Sh. Jun 07 '22 at 13:57
  • @EugeneSh. I just edited my post with a minimal but accurate example of the main issue – Liwinux Jun 07 '22 at 14:13
  • 1
    In the child, close `fd_in` after the dup2. Also close `p[1]` if you dup it. And add an error message before exiting on the `fork` failure. – William Pursell Jun 07 '22 at 14:18
  • @WilliamPursell Thank you, I just edited my question with your suggestion. However, I get the same results. – Liwinux Jun 07 '22 at 14:29
  • 1
    The original code you posted here compiled cleanly. The update you posted has many compile errors. Please edit and post the cleanly compiling version--Thanks. Also, in the update you have `j` and `i` but they're used but not defined or set/changed – Craig Estey Jun 07 '22 at 14:36
  • @CraigEstey I'm so sorry, I've mixed some code with the original one and that created a mess . It's fixed now. – Liwinux Jun 07 '22 at 14:48
  • Something caught my eye on some elses post : According to bash manual: "Only foreground processes are allowed to read from the terminal. Background processes which attempt to read from the terminal are sent a SIGTTIN signal by the kernel's terminal driver, which, unless caught, suspends the process." Not sure why it would be killed here. Also while debugging, it doesn't get killed – Liwinux Jun 07 '22 at 14:50
  • 1
    One of the issues is that the parent is doing `wait` in the same loop where the pipe elements are being established. This prevents a true pipeline. The parent should do the `wait` in a separate loop. – Craig Estey Jun 07 '22 at 15:07
  • @CraigEstey So for example having the `wait` right after the `fd_in = p[0];` outside of the `while (*cmd != NULL)` loop ? Just tried : still no luck.. – Liwinux Jun 07 '22 at 15:11
  • I updated the code again but this time it can't be more minimalistic than it is right now. So if someone could explain with the program behaves like that, that would be really awesome ! – Liwinux Jun 07 '22 at 15:23
  • 1
    I'm looking at it. It's definitely the `-i`. BTW, I have a shell pipe impl: [fd leak custom shell](https://stackoverflow.com/a/52825582) I ran the command through that. It has a different reaction: a bad lockup. So, I'll be fixing my own version and when I figure it out I'll post a solution. If you figure it out first, let me know. – Craig Estey Jun 07 '22 at 17:17
  • 1
    Is your program running in the background? "When a background process reads from the terminal it gets a SIGTTIN signal. Normally, that will stop it, the job control shell notices and tells the user, who can say fg to continue this background process as a foreground process, and then this process can read from the terminal." ([source](https://www.win.tue.nl/~aeb/linux/lk/lk-10.html)) – John Kugelman Jun 07 '22 at 17:29
  • @JohnKugelman It isn't, I just execute the program above and bash just stops it. Then I have to manually say "fg" to continue executing my the program. I think you got something as it is the exact behavior I'm having. But I don't understand how this can apply to the my given example. I'm forking, executing and waiting for the execution to finish and then I repeat the whole process. Clearly ZSH tells me : `zsh: suspended (tty input) ./a.out` – Liwinux Jun 07 '22 at 18:02
  • @CraigEstey It's rather combination of -i and -c that does that. But you can see JohnKugelman 's comment. That's the description of what's going on as ZSH tells me : `zsh: suspended (tty input) ./a.out` – Liwinux Jun 07 '22 at 18:03
  • @JohnKugelman I forgot to say it but yes that's the signal I'm getting on my shell and the example given, and after getting it, I can't read from STDOUT as I always get EOF – Liwinux Jun 07 '22 at 18:06
  • 1
    The TL;DR Don't worry--be happy. Doing `-ic` for a shell is _overriding_ it's "my input is a pipe" detector. So, it's going to do screwy things. That is, _your_ shell will get `SIGTTIN`. Most commands won't do this (not even `vi`). So, if it hurts, _don't_ do it ;-) `bash` itself handles this. But, it does a whole lot of changing signal handlers to do this. And, it has a whole lot of places where it checks for `-i` given when input/output is a pipe. If you really want to handle this, I'd mimic what `bash` does. [I _think_] it also bypasses `stdin` and reads directly from fd 0. – Craig Estey Jun 07 '22 at 20:31
  • @CraigEstey Well, I think I now understand what's going on. If even `vi`doesn't support it then I guess it's no so critical. I mean I can look into it later if I want right ? :) – Liwinux Jun 07 '22 at 20:47
  • Yes, it's 10x the work for something obscure. I had done several things: run `bash` under `strace`, looked at the bash source code (Hint: If you do download it, `-i` will set the `force_interactive` variable). Other things you might have to do: use `setsid` and/or `setpgrp` in addition to changing signal handlers. open `/dev/tty`. I'm as gung ho as the next person, but ... I'm going to try some simple things (e.g. playing with `SIGTTIN` handling) and trying `read(0,...)` instead of `fgets(...,stdin)` – Craig Estey Jun 07 '22 at 20:56
  • One thing I did try that may help: Using `sigaction` I set a handler for `SIGTTIN`. It sets a global to indicate it saw the signal (Note: setting `SIG_IGN` is _not_ the same). In the base loop that does `fgets(...,stdin)`: If it returns `NULL` (e.g. EOF), check the global and exit the program if it's set. Not the ultimate solution but maybe less messy as the shell that _invokes_ my shell does _not_ suspend it. So, it's "clean" ... Note that in the pipe setup, just before `execvp` in the child, I reset the handler to `SIG_DFL` – Craig Estey Jun 07 '22 at 20:59

0 Answers0