I'm trying to write a mini-shell in C using this template. But whenever I try to use interactive commands such as less and vi, the shell gets stuck on waitpid (with WUNTRACED enabled these commands return immediately because they are stopped by a job control signal as indicated by ps) . Other commands that don't require input such as ls are fine. The root cause is setpgid, which seems to puts the forked child process (such as less and vi) into a different process group which no longer shares a terminal. The child process is therefore stopped by a job control signal. Deleting setpgid will make the mini-shell work again, but it can't be removed since the mini-shell needs to control its foreground processes as a group (e.g. if the foreground process P forks additional processes P1 and P2, the shell, upon receiving a SIGTSTP from the user, should stop P, P1, and P2. This can be conveniently done if P, P1, P2 are in the same process group whose pgid is the same as P's pid. We can just send SIGTSTP to the entire process group).
I have tried to use tcsetpgrp to fix my shell. Although it'll make commands such as vi functional again, the mini-shell will automatically exit upon completion of the forked child, presumably because the parent shell mistakenly views the completion of the forked child as also the completion of the mini-shell.
Is there a fix which will still allow me to keep setpgid?
// full code is in the provided link
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
// call wrapper function for error handling
// set process group id of child to the pid of child
Setpgid(pid, 0);
if (!bg) {
// foreground process, should wait for completion
// tcsetpgrp does make vi and less work,
// but their completion also terminates the mini-shell
// tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
} else {
// background process
printf("%d %s", pid, cmdline);
}
}
The solution is to relinquish control of the tty to the other process group using tcsetpgrp, and when the child is done, take back the control of the tty using tcsetpgrp again. Note that tcsetpgrp sends SIGTTOU to its caller if the calling process belongs to a background process group, so SIGTTOU and SIGTTIN must be blocked.
// error handling is omitted for brevity
// these two signal functions can be anywhere before tcsetpgrp
// alternatively, they can be masked during tcsetpgrp
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) {
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
if (!bg) {
setpgid(pid, 0);
tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
tcsetpgrp(STDERR_FILENO, getpgrp());
} else {
printf("%d %s", pid, cmdline);
}
}
This is a rather crude implementation. Consult Craig's comments for this question for where to find bash's implementation.
Related
I'm making a shell in C for a school project that is capable of running processes in parallel if it is commanded to do so.
This is the loop of the shell application that waits for commands:
while (1) {
action = parseShellArgs();
if (action == 1) {
printf("Exiting...\n");
break;
} else if (action == 0) {
int pid = fork();
if (pid < 0) {
printf("Failed to fork\n");
} else if (pid == 0) {
(*NUM_PROCESSES_RUNNING)++;
printf("There are %d processes running\n", *NUM_PROCESSES_RUNNING);
char * solverArgs[] = {"a", shellArgs[1], NULL}; // first element is placeholder for argv[0]
execv("CircuitRouter-SeqSolver", solverArgs);
exit(0);
} else if (pid > 0) {
if (*NUM_PROCESSES_RUNNING >= MAXCHILDREN) {
printf("All processes are busy\n");
continue;
}
int status, childpid;
wait(&status);
childpid = WEXITSTATUS(status);
(*NUM_PROCESSES_RUNNING)--;
printf("There are %d processes running\n", *NUM_PROCESSES_RUNNING);
(void)childpid; // suppress "unused variable" warning
} else {
printf("Wait what\n");
}
} else {
printf("Oops, bad input\n");
}
}
Please do disregard the constants being incremented and decremented.
Now, this only works partially. Whenever I give it a command to create another process and run another program (condition action == 0, this has been tested and works), the fork happens and the program is correctly executed.
However, I cannot fork multiple times. What I mean by this is: the program forks and the child executes as instructed in the execv call. The problem is that instead of the parent process then goes back to expecting input to possibly fork again, it waits for the child process to finish.
What I am trying to make this cycle do is for the parent to always be expecting input and forking as commanded, having multiple children if necessary. But as I explained above, the parent gets "stuck" waiting for the single child to finish and only then resumes activity.
Thank you in advance.
Edit: I have experimented multiple combinations of not waiting for the child process, using extra forks to expect input etc.
From man wait.2
The wait() system call suspends execution of the calling process until
one of its children terminates.
Your program gets stuck because that's what wait does. Use waitpid instead with WNOHANG.
waitpid(pid_child, &status, WNOHANG);
doesn't suspend execution of the calling process. You can read the waitpid man page to find out the return values and how to know if a child terminated.
So I am trying to create a basic terminal, and my problem is this: if I use the following code to execute most "normal" terminal commands (e.g. ls, cat, etc), there are no issues. It sets the process group and works perfectly. However, when I try to execute a command such as vim ., it appears that the process immediately stops. If I remove the call to setpgrp(), the command works as expected (and executes with the pgid of the controlling terminal).
Here is my code:
pid_t normal_cmd(char **argv, int bg) {
int pid = fork(), status;
if(pid < 0)
unix_error("Could not fork child process");
else if(!pid) { //child (this is the problematic area)
setpgrp();
status = execvp(argv[0], argv);
if(status < 0)
unix_error("Could not exec child process");
}
else { //parent
if(!bg) {
addjob(jobs, pid, FG);
pause(); //waitpid() is in SIGCHLD handler
struct job_t *cj = getjobpid(jobs, pid);
if(cj && cj->state != ST)
deletejob(jobs, pid);
}
else
addjob(jobs, pid, BG);
}
return pid;
}
Any idea why changing the process group would cause vim to fail here?
vim will try to read from the controlling terminal, and because its process group is not the foreground process group, it will receive a SIGTTIN signal which suspends it. ls and other "normal" commands aren't suspended because they don't read from stdin (which is the terminal in this case).
The setpgrp() call has the effect of creating a new process group with the calling process as its leader -- and the new process group is not the foreground process group on the terminal until you make it so with tcsetpgrp() or ioctl(TIOCSPGRP).
You can read more about job control here. Especially this, which explains why a program in the background receives a SIGTTIN if it tries to read from the the tty, but not a SIGTTOU if it tries to write to it.
I am trying to terminate my program which takes a line that is full of commands from a file and then process each command using execvp
However,Whenever I encounter quit, I want to immediately exit processing the commands and ignore all other commands that are coming after it.
I tried to do this the following way using exit()
for(int i =0;i < numOfCommands;i++)
{
childPid = fork();
if(childPid == 0)
{
if(execvp(commands[i].cmd[0],commands[i].cmd) == -1)
{
/*if(strcmp(commands[i].cmd[0],"quit"))
{
done = true;
return;
}*/
if(strcmp(commands[i].cmd[0],"quit")==0)
{
printf("Quit command found ! \n Quitting .");
done = true;
//return;
exit(0);
}
printf("Command %s is unknown \n", commands[i].cmd[0]);
}
}
else
{
//parent process
wait(&child_status);
}
}
}
And this happens inside of the child process, after forking of course. But the problem is that my program keeps processing the remaining commands that comes after quit before exiting the program !
You can use kill(2) to send a signal to the process group. You can do this in the parent or any of the children.
int kill(pid_t pid, int sig);
If pid equals 0, then sig is sent to every process in the process group of the calling process.
For example:
kill(0, SIGTERM);
I think a better way to deal with this is to check for the quit command in the parent process before forking the child.
But if you want to do it in the child, you can send a signal to the parent.
kill(getppid(), SIGUSR1);
The parent process will need to establish a signal handler for SIGUSR1 that cleans everything up and exits. Or you could send a signal like SIGINT, whose default action is to kill the process, but it's better to implement a clean exit.
Also, in your code, you should check for the quit command before calling execvp. Otherwise, if there's a quit program in the user's path, it will never match your built-in quit, since execvp will succeed and not return.
I am trying to write a function that spawns a child process, lets it run for a certain amount of time and then kills it if it hasn't finished:
int sysExecTimeout(const char * exePath, int timeoutSec);
In the function, I use fork and execl to spawn the child, and, when it times out, I use kill(pid, SIGTERM) and kill(pid, SIGKILL) after 2 seconds, to ensure the child dies:
pid_t pid = fork();
if(pid == 0) {
execl("/bin/sh", "sh", "-c", exePath);
exit(127);
} else if(pid != -1) {
// timeout code
if(timeout) {
kill(pid, SIGTERM);
sleep(2);
kill(pid, SIGKILL);
}
}
I am using Linux, and it seems that when a parent process dies, the child is not killed automatically. So the two kill calls will just kill the /bin/sh process and leave the exePath command running, since it is a child process of /bin/sh.
I am trying to write the sysExecTimeout function such that it kills the entire process tree rooted at pid, where pid is the PID from pid = fork()
I need this because the exePath command will spawn other commands, which can also spawn other commands, which could get stuck and consume resources.
I do not have control over the exePath binaries/scripts that get executed, so I cannot write my own parent-dies-so-kill-the-children logic in them.
I tried using kill(0, SIGTERM), which almost did the job, except it also killed my own process :)
I am wondering if there is a flag I can turn on programatically in C that says "hey man, when I die, take all my children and kill them, and repeat recursively for their children" such that the entire process tree started from that program dies (assuming the PID/PPID chain can be followed).
I could use that flag here:
if(pid == 0) {
turnOnRecursiveDeathFlag();
system(exePath);
//execl("/bin/sh", "sh", "-c", exePath);
exit(127);
}
Is there a way to do that? I've been searching for a while, but all I can find are hacks using ps -ef, or modifying the children processes that you are running etc.
Use setpgid in the child to set its GPID equal to its own PID. The parent then can kill(-pid,...) to signal the entire group.
pid_t pid = fork();
if(pid == 0) {
setpgid(0, 0);
execl("/bin/sh", "sh", "-c", exePath);
exit(127);
} else if(pid == -1) {
// timeout code
if(timeout) {
kill(-pid, SIGTERM);
sleep(2);
kill(-pid, SIGKILL);
}
}
That should do it.
One more thing, when you spawn a shell, make sure it doesn't enable job control. Otherwise, it will create its own process groups. Your "/bin/sh -c" is fine.
I'm creating background processes in C using fork().
When I created one of these processes, I add its pid to an array so I can keep track of background processes.
pid = fork();
if(pid == -1)
{
printf("error: fork()\n");
}
else if(pid == 0)
{
execvp(*args, args);
exit(0);
}
else
{
// add process to tracking array
addBGroundProcess(pid, args[0]);
}
I have a handler for reaping zombies
void childHandler(int signum)
{
pid_t pid;
int status;
/* loop as long as there are children to process */
while (1) {
/* get zombie pids */
pid = waitpid(-1, &status, WNOHANG);
if (pid == -1)
{
if (errno == EINTR)
{
continue;
}
break;
}
else if (pid == 0)
{
break;
}
/* Remove this child from tracking array */
if (pid != mainPid)
cleanUpChild(pid);
}
}
When I create a background process, the handler is executing and attempting to clean up the child before I can even make the call to addBGroundProcess.
I'm using commands like emacs& which should not be exiting immediately.
What am I missing?
Thanks.
You're right, there is a race condition there. I suggest that you block the delivery of SIGCHLD using the sigprocmask function. When you have added the new PID to your data structure, unblock the signal again. When a signal is blocked, if that signal is received, the kernel remembers that it needs to deliver that signal, and when the signal is unblocked, it's delivered.
Here's what I mean, specifically:
sigset_t mask, prevmask;
//Initialize mask with just the SIGCHLD signal
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prevmask); /*block SIGCHLD, get previous mask*/
pid = fork();
if(pid == -1)
{
printf("error: fork()\n");
}
else if(pid == 0)
{
execvp(*args, args);
exit(0);
}
else
{
// add process to tracking array
addBGroundProcess(pid, args[0]);
// Unblock SIGCHLD again
sigprocmask(SIG_SETMASK, &prevmask, NULL);
}
Also, I think there's a possibility that execvp could be failing. (It's good to handle this in general, even if it's not happening in this case.) It depends exactly how it's implemented, but I don't think that you're allowed to put a & on the end of a command to get it to run in the background. Running emacs by itself is probably what you want in this case anyway, and putting & on the end of a command line is a feature provided by the shell.
Edit: I saw your comments about how you don't want emacs to run in the current terminal session. How do you want it to run, exactly - in a separate X11 window, perhaps? If so, there are other ways of achieving that.
A fairly easy way of handling execvp's failure is to do this:
execvp(*args, args);
perror("execvp failed");
_exit(127);
Your code just catches the exit of the child process it fork'ed, which is not to say that another process wasn't fork'ed by that child first. I'm guessing that emacs in your case is doing another fork() on itself for some reason, and then allowing the initial process to exit (that's a trick daemons will do).
The setsid() function might also be worth looking at, although without writing up some code myself to check it I'm not sure if that's relevant here.
You should not be using the shell with & to run background processes. If you do that, they come out as grandchildren which you cannot track and wait on. Instead you need to either mimic what the shell does to run background processes in your own code, or it would probably work just as well to close the terminal (or rather stdin/out/err) and open /dev/null in its place in the child processes so they don't try to write to the terminal or take control of it.