foreground signal tcsetpgrp c - c

I'm making my own shell cause why the hell not.
When you run a command and end it with &, the process with be run in the background, so I'd like to make an fg command that you can use to put the background process to the foreground.
I have some troubles making the fg function.
If i understand it correctly, putting signal() in the child process will let the child process receive a signal.
Signal receives two arguments, signum and the handler function.
We're gonna use tcsetpgrp() to set a given background process to foreground. So in lsh_fg I call tcsetpgrp(STDIN_FILENO, pid).
So signum should be sigttou so it can receive the signal from tcsetpgrp().
I don't know what should be put inside the handler, since tcsetpgrp() is supposed to do as the man page describes it:
"
The function tcsetpgrp() makes the process group with process group ID
pgrp the foreground process group on the terminal associated to fd
"
As I understand it, tcsetpgrp() is sending a signal to the process that has signal(sigttou,handler), which is put to the foreground when it receives it. But I clearly misunderstood this since it's not working.
My questions: How should I understand the way tcsetpgrp() and signal(sigttou,handler) work together? And what should my handler include?
I really appreciate your answers cause i really got stuck here :-)
See my code below:
Ps: I'm new to C and system programming and this is my first post ever so any constructive criticism regarding my code is warmly welcome
THANKS A LOT :D
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
pid_t pid;
int toke_c;
//function declaration for the function pointers
int lsh_cd(char **args);
int lsh_pwd(char **args);
int lsh_exit(char **args);
int lsh_fg(char **args);
//An array of functions:
int (*builtin_func[]) (char **) = {
&lsh_cd,
&lsh_pwd,
&lsh_exit,
&lsh_fg
};
//An array of the given strings:
char *builtin_str[] = {
"cd",
"pwd",
"exit",
"fg"
};
///built in functions cd and pwd
int lsh_fg(char **args){
tcsetpgrp(STDIN_FILENO, pid);
return 1;
}
void fg_handler()
{
//What to put here???
}
///built in functions cd and pwd
int lsh_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "lsh: cd: no arguments given\n");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
int lsh_pwd(char **args)
{
char * cwd;
cwd=getcwd (NULL,0);
printf ("%s\n ", cwd);
return 1;
}
int lsh_exit(char **args)
{
return 0;
}
/* Handlers Here*/
void killer()
{
if (pid == 0)
exit(0);
}
void handler()
{
//I DON'T KNOW WHAT TO PUT HERE
}
int lsh_launch(char **args)
{
int status=0;
pid = fork();
if (pid == 0) {
// child process
signal(SIGINT, killer);
if (execvp(args[0], args) == -1) {
fprintf(stderr,"Command not found in $PATH\n");
}
return 1;
} else if (pid < 0) {
//error
perror("lsh");
} else {
// parent
signal(SIGINT, killer);
waitpid(pid, &status, WUNTRACED);
}
return 1;
}
int lsh_background(char **args)
{
pid_t pid;
int status=0;
pid = fork();
if (pid == 0) {
// child process
setpgid(0, 0);
signal(SIGINT, killer);
signal(SIGTTOU, fg_handler);
if (execvp(args[0], args) == -1) {
fprintf(stderr,"Command not found in $PATH\n");
}
return 1;
} else if (pid < 0) {
//error
perror("lsh");
} else {
// parent
signal(SIGTTOU, fg_handler);
signal(SIGINT, killer);
}
return 1;
}
//if a command was entered that we've been using
int lsh_exec(int argc, char **args)
{
int i;
if (args[0] == NULL) {return 1;}
int tresh=4;
char **args1=malloc(toke_c*sizeof(char *));
int j;
for(j=0;j<toke_c-1;j++){
args1[j]=args[j];
}
if(strcmp(args[toke_c-1],"&")==0){
return lsh_background(args1);
}
for (i = 0; i < tresh; i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}
#define MAX_STR 256
//reading the line
char *lsh_lread(void)
{
char *str = malloc (MAX_STR);
fgets (str, MAX_STR, stdin);
}
//tokenizer
char **lsh_tokenizer(char *line)
{
int bufsize = 64;
int pos_t = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token;
token = strtok(line, " \t\r\n\a");
while (token != NULL) {
tokens[pos_t] = token;
pos_t++;
token = strtok(NULL, " \t\r\n\a");
}
tokens[pos_t] = NULL;
toke_c=pos_t;
return tokens;
}
void lsh_loop(void)
{
int argc;
char *line;
char **args;
int status;
do {
printf(">> ");
line = lsh_lread();
args = lsh_tokenizer(line);
status = lsh_exec(argc,args);
free(line);
free(args);
} while (status);
}
int main(int argc, char **argv)
{
lsh_loop();
return 0;
}

How should I understand the way tcsetpgrp() and signal(sigttou,handler) work together?
For your purposes, they don't. You do not need to send a process a signal to make its process group the foreground pgroup (but see below). In fact, I don't see why you would ever intentionally send a SIGTTOU to a process group that you're trying to put in the foreground.
Here's the central part of POSIX's documentation for tcsetpgrp() (emphasis added):
If the process has a controlling terminal, tcsetpgrp() shall set the foreground process group ID associated with the terminal to pgid_id. The application shall ensure that the file associated with fildes is the controlling terminal of the calling process and the controlling terminal is currently associated with the session of the calling process. The application shall ensure that the value of pgid_id matches a process group ID of a process in the same session as the calling process.
Attempts to use tcsetpgrp() from a process which is a member of a background process group on a fildes associated with its controlling terminal shall cause the process group to be sent a SIGTTOU signal. [...]
You're talking about implementing an fg command. The primary usefulness of such a command is interactive execution, and if a process (i.e. your shell) is receiving that command interactively then it must be in the foreground process group, because that's the only process group that receives input from the terminal. Supposing, then, that such a process calls the function, and that the arguments satisfy their individual requirements, the effect is "tcsetpgrp() shall set the foreground process group ID associated with the terminal to pgid_id." Or fail, of course. No signaling is documented to go along with that.
SIGTTOU comes into this picture only if tcsetpgrp() is called by a process that is in a background process group. Were I implementing a shell, I'd be inclined to disable job control for shells running in the background (the command would fail with an error). The default handler for this signal stops the process (not the same thing as terminating it); this is appropriate for a background process that attempts to write to its session's controlling terminal. Similarly, SIGTTIN by default stops the process, and is delivered to background processes that attempt to read from its session's controlling terminal.
For an fg command, you do not expect or want to handle SIGTTOU or SIGTTIN, but that doesn't mean you don't need to signal. Rather, the (initially foreground) process that calls tcsetpgrp() should afterward send a SIGCONT to the new forground pgroup in case some or all of those processes are stopped, as might well be the case. The default handler for this signal resumes the process if it is stopped, which is exactly what you want.
In short, then, you probably do not need to write any custom signal handlers at all for this purpose.

Related

C linux tcsetpgrp stops parent process even after calling to ignore it

Ok so I wrote this tiny program in order to test how tcsetpgrp behaves and in general handling signals, now I've seen a few posts here asking about tcsetpgrp but for some reason the solutions in those posts are not working for me.
int main() {
signal(SIGINT,sig_handler);
static char line[4096];
int line_num = 0;
pid_t id = fork();
if(id == 0 )
{
while (fgets(line, 4096, stdin)) {
printf("\nyou wrote something");
}
}
else
{
int pgid_child = id;
printf("child group : %d", pgid_child);
signal(SIGTTOU, SIG_IGN);
tcsetpgrp(0, pgid_child);
wait(NULL);
}
return 1;
}
what I expect to happen:
the terminal will ask for input as long as the user continues to write into it, but if the user presses ctrl+c then the program will end.
what actually happens:
as soon as the child process is set to foreground group the SIGTTOU is called to all other processes and the parent process also stops, even though I've asked to ignore that signal.
does anyone know why is this happening?

How to set status termination of a process C?

My program is a rudimental little shell.
It allow you to run programs in PATH as ls, cd..also with arguments.
To run the program type from terminal "./myshell2" then it starts and you can insert how many commands you want.
It starts a child process, runs execvp,it returns and restarts so you can type a new command.
When typed "Q" or "q" all the entire program should terminates.
The problem is that I don't know how to stop it,the code is below.
My idea is, when typed "Q" or "q", to kill the child process created and send a signal to comunicate its bad termination(of child process).
So the final status(from parent) 'll be not 1 and the function returns.
I commented some parts of the code hoping that it's easier to understand.
It works the problem is that to stop it I need of ctrl C.
I would like to say to child process that he must ends with a non-zero value.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
int main(int argc, char * argv[]) {
while(1)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(EXIT_FAILURE);
}
if (pid == 0) { // child process
printf("type the command to start (and arguments if required) \n"
"Q to quit\n");
char *dest[10]; // allow you to insert
char line[4096];//commands from terminal
if (fgets(line,sizeof(line),stdin)==0) return 1;
int i;
line[strcspn(line, "\n")] = '\0';
char *st = line;
for (i=0; i< 10 && (dest[i]=strsep(&st," "))!=NULL;i++)
continue;//now you typed the command
if ( ( memcmp(dest[0],"Q",1)==0 ) // if Q or q the program
|| (memcmp(dest[0],"q",1)==0) ) //must end
{
printf("got it!\n");
if (kill(getpid(),SIGSEGV)==-1) printf("kill error\n");
//in theory the process should terminates with bad status
// and the value of the variable "status" 'll be not 0
// I think that the problem is in this part of the code
}
if( strcmp(dest[0]," ")!=0 )
{
int res = execvp(dest[0], dest);
}
else
{ int res= execvp(dest[1],dest+1);}
perror("execvp error");
exit(EXIT_FAILURE);
}
int status;
pid_t child = wait(&status);
if (child == -1) {
perror("wait error");
exit(EXIT_FAILURE);
}
if (status==1)
break; //so it can exit from the loop that creates new process
setenv("WAIT","TRUE",0); //dont' worry about
//perror("setenv error\n");
if (memcmp("TRUE",getenv("WAIT"),4) == 0 ) //these 6 lines
printf("WAIT=TRUE\n");
else if(memcmp("FALSE",getenv("WAIT"),4) == 0 )
printf("WAIT=FALSE\n");
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
return EXIT_SUCCESS;
}
You're printing out WEXITSTATUS() for all cases, but that isn't right. You need to check if the status returned by wait is an exit status or not using WIFEXITED(). If it's non-zero then the child exited normally. Otherwise, you can use WIFSIGNALED() to see if the child was terminated and you'll get the signal from WTERMSIG()
if(WIFEXITED(status))
{
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
else if(WIFSIGNALED(status))
{
printf("end current process (signal=%d, child=%d)\n", WTERMSIG(status), son);
}
You really should have the parent process handle the inputting of the command and leave the child process to run it though.

Keep a parent process waiting and run linux command as many times as wanted

I have the below C code in linux (using gcc):
void doWho(void)
{
char entry[10];
int ret, i;
ret = read(0, entry, 10);
if (*entry == 'u')
{
for(i=0; i<3; i++)
{
execl("/usr/bin/who","who",NULL);
}
}
}
int main(int argc, char *argv[])
{
int childpid;
printf("Before it forks\n");
childpid = fork();
if(childpid == 0)
{
printf("This is a child process\n");
doWho();
exit(0);
} else
{
printf("This is the parent process\n");
wait(NULL);
}
return 0;
}
I want the parent process to keep waiting indefinitely and run "who" every time I press the "u" key. I only get the desired effect one time and then the child and parent processes exit. Any help on this?
Observe below loop :
for(i=0; i<3; i++)
{
execl("/usr/bin/who","who",NULL);
}
It doesn't matter how many times you rotate the loop , it will execute only once because when child process starts running(PCB created), you are calling function doWho then control will come to doWho function and in doWho function what you are doing ? execl(). what execl() does is that " it replaces current process(child) with new process, after when i = 0 whole child process image got replaced with new one using execl(), so there is nothing left in child process that's why it will execute only once.
If you want to execute as many as you should use fork() in the function because then exec will replace every process created by fork(), but it's not advised to run fork() in loop because your program may crash when there are no more resource available.
So replace your doWho function as
void doWho(void)
{
char entry[10] = {0};
int ret, i;
for(; ;) {
/** who command will be executed when ever condition is true **/
ret = read(0, entry, 10);
__fpurge(stdin);//to clear stdin buffer everytime
if (*entry == 'u') {
if(fork()==0)//only this newly created process will be replaces by execl not one was in main()
execl("/usr/bin/who","who",NULL);
else
;// this parents does nothing
}
}
}
I hope it helps.

Ctrl+Z Signal handling in C

I am writing a simple shell in C.
However, I found that my program cannot properly handle the Ctrl+Z signal. My program looks like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
void interpreter() {
char input[256];
int i;
char dir[PATH_MAX+1];
char *argv[256];
int argc = 0;
char *token;
if (getcwd(dir, PATH_MAX+1) == NULL) {
//error occured
exit(0);
}
printf("[shell:%s]$ ", dir);
fgets(input,256,stdin);
if (strlen(input) == 0) {
exit(0);
}
input[strlen(input)-1] = 0;
if (strcmp(input,"") == 0) {
return;
}
token = strtok(input, " ");
while(token && argc < 255) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = 0;
pid_t forknum = fork();
if (forknum != 0) {
int status;
waitpid(forknum, &status, WUNTRACED);
} else {
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
setenv("PATH","/bin:/usr/bin:.",1);
execvp(argv[0], argv);
if (errno == ENOENT) {
printf("%s: command not found\n", argv[0]);
} else {
printf("%s: unknown error\n", argv[0]);
}
exit(0);
}
}
int main() {
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
while(1) {
interpreter();
}
}
I have ignored above signals in the main process.
When I start cat(1) and then hit Ctrl+Z, the next line of input will still be captured by the cat(1) program rather than my main process. It means that my main process will do nothing but if I wake up the cat(1) program, it will output what I typed immediately. All things go back to normal after this.
I can't figure out how to resolve this. I am still not sure if I have stated it clearly.
Interesting. Even though this is tagged Linux, I'll go out on a limb and say that you are running this on OS X.
When compiled on Linux, the problem is not there, but on Mac it happens exactly as you described. It looks like a bug in OS X: because both the shell process and cat(1) are on the same process group (since you don't explicitly change group membership), it seems like OS X makes the mistake of feeding the next input line to the fgets(3) call that is asleep in the cat(1) process, so you end up losing that line of input from the shell process (because it is consumed by the sleeping cat(1)).
The reason this doesn't happen with bash is because bash supports job control, and as such processes are put in separate process groups (in particular, bash chooses the first process of a process pipeline as the process group leader). So when you do the same thing on bash, each invocation of cat(1) ends up putting it in a separate process group (and then the shell controls which process group is in the foreground with tcsetpgrp(3)). So, at any time, it is clear which process group has control over terminal input; the moment you suspend cat(1) in bash, the foreground process group is changed to bash again and input is read successfully.
If you do the same as bash in your shell, it will work in Linux, OS/X, and basically any other UNIX variant (and it is how other shells do it too).
In fact, if you want your shell to have job support, you'll have to do this sooner or later (learn about process groups, sessions, tcsetpgrp(3), setpgid(2), etc.).
So, in short, do the right thing if you want job support and wrap the forked process in a new process group:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void interpreter() {
char input[256];
char dir[PATH_MAX+1];
char *argv[256];
int argc = 0;
char *token;
if (getcwd(dir, PATH_MAX+1) == NULL) {
//error occured
exit(0);
}
printf("[shell:%s]$ ", dir);
fgets(input,256,stdin);
if (strlen(input) == 0) {
exit(0);
}
input[strlen(input)-1] = 0;
if (strcmp(input,"") == 0) {
return;
}
token = strtok(input, " ");
while(token && argc < 255) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = 0;
pid_t forknum = fork();
if (forknum != 0) {
setpgid(forknum, forknum);
signal(SIGTTOU, SIG_IGN);
tcsetpgrp(STDIN_FILENO, forknum);
tcsetpgrp(STDOUT_FILENO, forknum);
int status;
waitpid(forknum, &status, WUNTRACED);
tcsetpgrp(STDOUT_FILENO, getpid());
tcsetpgrp(STDIN_FILENO, getpid());
} else {
setpgid(0, getpid());
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
setenv("PATH","/bin:/usr/bin:.",1);
execvp(argv[0], argv);
if (errno == ENOENT) {
printf("%s: command not found\n", argv[0]);
} else {
printf("%s: unknown error\n", argv[0]);
}
exit(0);
}
}
int main() {
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
while(1) {
interpreter();
}
}
(Although, admittedly, it is unfortunate that OS X does such a poor job in this situation - you really shouldn't have to do this).
The changes are just inside the process-specific code: both the child and the parent call setpgid(2) to make sure that the newborn process is indeed in a single process group before either the parent of the process itself assumes that this is already true (this pattern is recommended in Advanced Programming in the UNIX Environment); the tcsetpgrp(3) call must be invoked by the parent.
Of course, this is far from complete, you then need to code the necessary functions to bring a job back to the foreground, list jobs, etc. But the code above works with your test scenario nonetheless.
Nitpick: you should be using sigaction(2) instead of the deprecated, unreliable and platform-dependent signal(3), but it's a minor issue here.

How to kill a process and all of its children in C when executing the process by execv()?

I'm trying to implement a timeout-like command on a unix-based operating system as follows:
int pid;
timer_t timer_id;
struct sigevent timer_event;
struct itimerspec timer_value;
void timeout_signal_handler(int sig_no)
{
kill(pid, SIGKILL);
}
int create_timer() { /* implementation */ }
int start_timer_oneshot(int interval_ms) { /* implementation */ }
int main(int argc, char* argv[])
{
int status, pid_return;
void *signal_return;
if (argc < 2)
return EXIT_FAILURE;
signal_return = signal(SIGUSR1, timeout_signal_handler);
if (signal_return == SIG_ERR)
return EXIT_FAILURE;
create_timer();
start_timer_oneshot(TIMEOUT);
if ((pid = fork()) == 0)
{
execv(argv[1], &argv[1]);
return EXIT_FAILURE;
}
else
{
status = -1;
while (status == -1)
status = wait(&pid_return);
}
return EXIT_SUCCESS;
}
And I use this utility as follows:
./timeout example
The example program runs for a couple of seconds and forks a couple of processes. When the timer expires in timeout, only the parent process of example gets killed and its children keep printing on the console.
When I run the example without timeout, and press Ctrl+C, the parent and all its children get killed successfully.
Can anyone please let me know how could I fix this in my timeout program?
You want to call kill() on pid 0. This sends the signal to all members of the calling process' process group.
This however only works if the process, which had been fork()/exec*()'ed (or its children) does not change its (their) process group by itself.
From man 2 kill:
If pid is 0, sig shall be sent to all processes (excluding an unspecified set of system processes) whose process group ID is equal to the process group ID of the sender, and for which the process
has permission to send a signal.

Resources