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.
Related
I'm trying to run a series of commands through execv() and forking a new process in C to run each one, and yet for some reason they aren't running in parallel. The following code is run for each process, with "full" being the filepath and "args" being the arguments. I know that the execv() part isn't the issue, it has to do with the way I'm forking and waiting.
int status;
pid_t pid = fork();
if (pid == 0) {
execv(full, args);
//perror("execv");
} else if (pid < 0) {
printf("%s\n", "Failed to fork");
status = -1;
} else {
if (waitpid(pid, &status, 0) != pid) {
status = -1;
return status;
}
}
When running this code, the forked commands simply run one after the other. I don't know how this could be happening.
If you don't want to wait for each child process, don't call waitpid immediately; as written, you fork a child, then immediately stop all processing in the parent until the child process exits, preventing you from forking any further children. If you want to launch multiple children without leaving zombie processes lying around (and possibly monitoring them all at some point to figure out their exit status), you can do one of:
Store off the pids from each fork in an array, and call waitpid on them one by one after you've launched all the processes you need to launch
Store a count of successfully launched child processes and call wait that many times to wait on them in whatever order they complete.
Ignore the SIGCHLD from the child processes entirely, for when you don't care when they exit, don't need to know their status, etc.
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.
I am trying to build a shell, and I've managed to code most of the functionality in, however I have a small problem.
Say I type firefox &. Firefox will open up as a background process. The & activates a BG flag that makes the parent not wait for the child process.
Then I type gedit. Gedit will open as a foreground process. Meaning that currently the parent is waiting for the process to close.
At this point, the parent has two processes - firefox and gedit. Firefox hasn't been waited on, and is currently in the background, whereas we are currently waiting for Gedit to finish. So far so good.
However, if I decide to send a SIGINT signal by pressing ctrl-c, both firefox and gedit will close. Not good, only gedit should be closing.
Here is my signal handler function:
pid_t suspended_process[10];
int last_suspended = -1;
void signal_handler(int signo){
pid_t process = currentpid();
// Catches interrupt signal
if(signo == SIGINT){
int success = kill(process, SIGINT);
}
// Catches suspend signal
else if(signo == SIGTSTP){
int success = kill(process, SIGTSTP);
resuspended = 1;
suspended_process[last_suspended+1] = process;
last_suspended++;
}
}
And here's the part in fork-exec code that either waits on a process, or keeps on going.
else if(pid > 0){ //Parent
current_pid = pid;
// Waits if background flag not activated.
if(BG == 0){
// WUNTRACED used to stop waiting when suspended
waitpid(current_pid, &status, WUNTRACED);
if(WIFEXITED(status)){
setExitcode(WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("Process received SIGNAL %d\n", WTERMSIG(status));
}
}
}
This also happens if I suspend a process beforehand. For example, I run firefox and then press ctrl-z to suspend it. Then I run gedit and press ctrl-c to close it. Right after, if I press fg to restore the suspended firefox, it closes immediately.
I cannot find a way to only send the SIGINT signal to the foreground process, it always sends the signal to ALL children other than the parent, no matter if they are in the background, or suspended.
Just in case, this is the function that initialises the signal handler:
void init_handler(){
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
// If conditions for signal handling.
// Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
if(sigaction(SIGINT, &sa, NULL) == -1)
printf("Couldn't catch SIGINT - Interrupt Signal\n");
if(sigaction(SIGTSTP, &sa, NULL) == -1)
printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}
This is rather simple, but it isn't done with signals. Instead you must use a feature called process groups. Each single job (executable or pipeline or so) will be a separate process group. You can create process groups with setpgid (or on some systems with setpgrp). You can simply set the process group of the child process after fork but before exec, and then store the process group id of this job into the job table.
Now, the process group that is in the foreground is set as the active process group for the terminal (the /dev/tty of the shell) with tcsetpgrp - this is the process group that will receive CTRL+C. Those process groups that belong to the same session, but not to the group set to foreground with tcsetpgrp will be completely oblivious to CTRL+C.
With the help of Antti, I managed to find the problem. I added a single line to the fork-exec code:
else if(pid > 0){ //Parent
current_pid = pid;
if(setpgid(pid, pid) == 0) perror("setpid");
// Waits if background flag not activated.
if(BG == 0){
// WUNTRACED used to stop waiting when suspended
waitpid(current_pid, &status, WUNTRACED);
if(WIFEXITED(status)){
setExitcode(WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("Process received SIGNAL %d\n", WTERMSIG(status));
}
}
}
if(setpgid(pid, pid) == 0) perror("setpid");
From what I could gather, setpgid sets the process group ID of a process. Meaning that in the line above, I am setting the pgid of the process with the pid pid to pid.
I might be wrong, I still don't fully understand the process, but the reason why this works, is that the SIGINT signal is only sent to the process with pgid pid. Meaning before, since I wasn't setting the pgid of each process, they all had the same pgid, hence they'd all receive the signal. However, once I set the pgid for each process, if I press CTRL-C in the middle of a foreground process, it only exits that running process.
At least that's from what I could gather. I still don't fully understand tcsetpgrp, especially what I could set as the first parameter, which is the file descriptor. Adding this line right after setpgid:
tcsetpgrp(STDIN_FILENO, pid)
Simply launches the entire program in the background whenever I exec a command. Instead of running firefox and it shows up, I run firefox and the whole program gets stopped (according to what the terminal says at least). I have no idea why that happens.
Still, thanks to Antti!
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'm coding a basic shell in C, and I'm working on suspending a child process right now.
I think my signal handler is correct, and my child process is suspending, but after that, the terminal should return to the parent process and that's not happening.
The child is suspended, but my shell isn't registering any input or output anymore. tcsetpgrp() doesn't seem to be helping.
Here's my signal handler in my shell code for SIGTSTP:
void suspend(int sig) {
pid_t pid;
sigset_t mask;
//mpid is the pgid of this shell.
tcsetpgrp(STDIN_FILENO, mpid);
tcsetpgrp(STDOUT_FILENO, mpid);
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL);
//active.pid is the pid of the child currently in the fg.
if (active.pid != 0) {
kill(active.pid, SIGTSTP);
}
else{
//if this code is being run in the child, child calls SIGTSTP on itself.
pid = getpid();
if (pid != 0 && pid != mpid){
kill(pid, SIGTSTP);
}
}
signal(SIGTSTP, suspend);
}
Can anyone tell me what I'm doing wrong?
Am I suspending my shell along with the child, and do I need to return stdin and stdout to the shell somehow? How would I do this?
Thanks!
It's an old question but still I think I found an answer.
You didn't write your parent's code but I'm assuming its looks something like:
int main(){
pid_t pid = fork();
if(pid == 0) //child process
//call some program
else //parent process
wait(&status); //or waitpid(pid, &status, 0)
//continue with the program
}
the problem is with the wait() or waitpid(), it's look like if you run your program on OS like Ubuntu after using Ctrl+Z your child process is getting the SIGTSTP but the wait() function in the parent process is still waiting!
The right way of doing that is to replace the wait() in the parent with pause(), and make another handler that catch SIGCHLD. For example:
void sigHandler(int signum){
switch(signum){
case SIGCHLD:
// note that the last argument is important for the wait to work
waitpid(-1, &status, WNOHANG);
break;
}
}
In this case after the child process receive Ctrl+Z the parent process also receive SIGCHLD and the pause() return.
tcsetpgrp is to specify what is the foreground job. When your shell spawns a job in foreground (without &), it should create a new process group and make that the foreground job (of the controlling terminal, not whatever's on STDIN). Then, upon pressing CTRL-Z, that job will get the TSTP. It's the terminal that suspends the job, not your shell. Your shell shouldn't trap TSTP or send TSTP to anyone.
It should just wait() for the job it has spawned and detect when it has been stopped (and claim back the foreground group and mark the job as suspended internally). Your fg command would make the job's pgid the foreground process group again and send a SIGCONT to it and wait for it again, while bg would just send the SIGCONT
i used folk with signals for make process pause and resume with ctrl+c
video while is running : link
Code:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void reverse_handler(int sig);
_Bool isPause=0;
_Bool isRunning=1;
int main()
{
int ppid;
int counter=0;
//make parent respond for ctrl+c (pause,resume).
signal(SIGINT,reverse_handler);
while(isRunning){
while(isPause==0)
{
/*code exec while process is resuming */
printf("\nc:%d",counter++);
fflush(stdout);
sleep(1);
}
//close parent after child is alive.
if((ppid=fork())==0){ exit(0); }
//make child respond for ctrl+c (pause,resume).
signal(SIGINT,reverse_handler);
//keep child alive and listening.
while(isPause==1){ /*code exec while process is pausing */ sleep(1); }
}
return 0;
}
//if process is pause made it resume and vice versa.
void reverse_handler(int sig){
if(isPause==0){
printf("\nPaused");
fflush(stdout);
isPause=1;
}
else if(isPause==1){
printf("\nresuming");
fflush(stdout);
isPause=0;
}
}
i hope that's be useful.
please comment me if there's any questions
I might be late to answer the question here but this is what worked when I was stuck with the same problem. According to the man pages for tcsetpgrp()
The function tcsetpgrp() makes the process group with process group ID
pgrp the foreground process group on the terminal associated to fd,
which must be the controlling terminal of the calling process, and
still be associated with its session. Moreover, pgrp must be a
(nonempty) process group belonging to the same session as the calling
process.
If tcsetpgrp() is called by a member of a background process group in
its session, and the calling process is not blocking or ignoring
SIGTTOU, a SIGTTOU signal is sent to all members of this background
process group.
So, what worked for me was ignoring the signal SIGTTOU in the shell program, before I created the processes that would come to the foreground. If I do not ignore this signal, then the kernel will send this signal to my shell program and suspend it.