fork() and execvp() unexpected outcome when used with sudo - c

So when i invoke this program without sudo. It works fine.
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char** argv)
{
if(fork() == 0) execvp(argv[1], &argv[1]);
// else wait(NULL);
}
But with sudo (when i need to input my password) it gives an odd output:
pasha#skynet:~$ sudo ./a.out bash
[sudo] password for pasha:
pasha#skynet:~$ root#skynet:~#
Then on any input the terminal terminates. Further it only happens on a newly spawned terminal. And when the parent waits for the child the problem with sudo disappears.
Can someone explain why ?

why this happens
You are forking your process, so there are two processes now.
One process is the parent, the process run by your shell, like shell -> fork() -> exec(sudo) -> exec(./a.out). The parent terminates, because fork returns with nonzero and then main() reaches closing }. main by default returns 0. So shell sees that your program terminated with exit status 0. And your shell greets you with a new pasha#skynet:~$ prompt line after your program is done.
The other process is the child, run from your program where fork returned zero, like shell -> fork() -> exec(sudo) -> exec(./a.out) -> fork() -> exec(bash). The child process is bash, it prints root#skynet:~# (it was run after sudo) and waits for input.
Those two processes are running at the same time - ie. your shell (from which you executed sudo ./a.out) and the newly bash run from your program. Both those programs try to read and write to the same input and output at the same time.
The child process, ie. bash, needs to have exclusive control over the input in the terminal. So the child process bash executes tcsetpgrp. But your shell is that one that is controlling your terminal, not the child process. So the child process either receives signal SIGTTOU or maybe SIGTTIN upon trying to read from the input. Then the child bash executed the default handler for the signals - it terminates.
Running sudo bash & from your shell would cause a similar problem to the one that your program causes.

Your program is correct; try it out with "ls" instead of "bash",
$ ./a.out ls -al /tmp
The reason why it does not seem to work with bash is that bash
expect the process to be the group leader of the terminal foreground
process
group, which
it isn't.
That said, while the program is correct, it's severe lack of error
handling is offending :-). For example, when calling a program that
does not exist, execvp() returns with an error (as opposed to not
returning at all) which is ignored. With the effect that ... well
... you can only guess if it worked.
$ ./a.out frobozzzzz
$ # (hm)
Here's my incarnation of it. Longer. Handling errors. Seeing how it
went after child terminated.
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char** argv)
{
int status;
pid_t pid, terminated;
pid = fork();
if (pid == -1 /*unlikely*/) {
perror("fork()");
exit(EXIT_FAILURE);
}
if (pid == 0 /*child*/) {
if (execvp(argv[1], &argv[1]) != 0) { // when argv[1] is no
// progrm in path
perror("execvp()");
exit(EXIT_FAILURE);
}
else
assert(!"not getting here because successful exec() never returns");
}
// optional: wait for child to terminate, and print diagnostics
terminated = waitpid(pid, &status, 0);
if (terminated == -1) {
perror("waitpid()");
exit(EXIT_FAILURE);
}
if (terminated == pid) { // how come those not be equal?
if (WIFEXITED(status))
fprintf(stderr, "child terminated with exit status %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
fprintf(stderr, "child terminated by %d\n", WTERMSIG(status));
else
fprintf(stderr, "see \"man waidpid\" for what that could be\n");
}
return 0;
}

Related

Running bash in interactive mode stops my own shell

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.

Using Signal Handlers to Pause/Resume a Child Process

I'm currently trying to experiment with signals in C by using them to control a child process created with the fork() method. Essentially, I have a child process running the "yes" command from the linux terminal (this command just prints "y" and a newline until it is terminated). I want to be able to pause/resume this process with CTRL-Z. This is what i've got right now:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
pid_t CHILD_PROCESS;
pid_t PARENT_PROCESS;
int isPaused;
void pause_handler(int signo){
if(!isPaused){
printf("Ctrl-Z pressed. Pausing child.\n");
isPaused = 1;
kill(CHILD_PROCESS,SIGSTOP);
}
else if(isPaused){
printf("\nCtrl-Z pressed. Resuming child.\n");
kill(CHILD_PROCESS,SIGCONT);
isPaused = 0;
}
}
int main(int argc, char** argv){
pid_t pid;
PARENT_PROCESS = getpid();
pid = fork();
if(pid == 0){
system("yes");
}
isPaused = 0;
if(pid > 0){
signal(SIGTSTP, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
CHILD_PROCESS = pid;
while(1){
if(signal(SIGTSTP,pause_handler) == SIG_ERR){
printf("Signal Failure");
}
}
}
}
When I run this, I can get "Ctrl-Z pressed. Pausing child." to print to console by pressing CTRL-Z, and I can get "Ctrl-Z pressed. Resuming child." to print to the console by pressing CTRL-Z again. However, it doesn't actually resume printing "y" over and over again. Any ideas as to why the child process isn't resuming?
As it turns out, system has an implicit fork call within it, so the PID that gets stored in CHILD_PROCESS ends up not actually being the child process, and instead an intermediate one.
From man 3 system:
The system() library function uses fork(2) to create a child process
that executes the shell command specified in command using execl(3) as
follows:
execl("/bin/sh", "sh", "-c", command, (char *) 0);
system() returns after the command has been completed.
So, if we replace the system("yes") call with execl("/bin/sh", "sh", "-c", "yes", NULL), then we avoid this extra fork and the program functions as desired.
The only other issue is that, by I comment I found on this post, using printf within a signal handler is undefined behavior. Not an issue to worry about here, but something to keep in mind for future code!

Two children of same parent are not communicating using pipe if parent do not call wait()

Please see the code below:
#include<stdio.h>
main(){
int pid, fds[2], pid1;
char buf[200];
pipe(fds);
pid = fork();
if(pid==0)
{
close(fds[0]);
scanf("%s", &buf);
write(fds[1], buf, sizeof(buf)+1);
}
else
{
pid1 = fork();
if(pid1==0)
{
close(fds[1]);
read(fds[0], buf, sizeof(buf)+1);
printf("%s\n", buf);
}
else
{
Line1: wait();
}
}
}
If I do not comment out Line1, it is working fine. Please see below:
hduser#pc4:~/codes/c/os$ ./a.out
hello //*Entry from keyboard*
hello //Output
hduser#pc4:~/codes/c/os$
But if I comment out Line1, two child processes are not communicating:
hduser#pc4:~/codes/c/os$ ./a.out
hduser#pc4:~/codes/c/os$
hi //*Entry from keyboard*
hi: command not found
hduser#pc4:~/codes/c/os$
I cannot understand significance of wait() here.
What's happening here is that the parent process completes execution before the child processes finish. Causing the children to lose access to the terminal.
Let us have a closer look at all this.
What does wait() do ?
The wait() system call suspends execution of the calling process until
one of its children terminates.
Your program is like this
Your main Process forks 2 child processes. The first one writes to a pipe while the other one reads from a pipe. All this happens while the main process continues to execute.
What happens when the main process has executed it's code ? It terminates. When it terminates, it gives up its control on the terminal. Which causes the children to lose access to the terminal.
This explains why you get command not found -- what you have typed is not on the stdin of your program but on the shell prompt itself.
There were a couple of other issues with your code too,
1) In this part of your code,
scanf("%s", &buf);
This is wrong. You were unlucky and didn't get a segmentation fault. Since buf is already an address, this should have been
scanf("%s", buf);
2) Notice this,
read(fds[0], buf, sizeof(buf)+1);
This is undefined behavior as was pointed out in the comments section. You are trying to read more data and store it in a lesser memory space. This
should have been,
read(fds[0], buf, sizeof(buf));
3) Calling wait(). You have created two child processes, you should wait for both of them to finish, so you should call wait() twice.
After fixing some infelicities in the code, I came up with a semi-instrumented version of your program like this:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int pid, fds[2], pid1;
char buf[200];
pipe(fds);
pid = fork();
if (pid == 0)
{
close(fds[0]);
printf("Prompt: "); fflush(0);
if (scanf("%199s", buf) != 1)
fprintf(stderr, "scanf() failed\n");
else
write(fds[1], buf, strlen(buf) + 1);
}
else
{
pid1 = fork();
if (pid1 == 0)
{
close(fds[1]);
if (read(fds[0], buf, sizeof(buf)) > 0)
printf("%s\n", buf);
else
fprintf(stderr, "read() failed\n");
}
else
{
/*Line1: wait();*/
}
}
return 0;
}
That compiles cleanly under stringent options (GCC 5.1.0 on Mac OS X 10.10.5):
gcc -O3 -g -std=c11 -Wall -Wextra -Werror p11.c -o p11
When I run it, the output is:
$ ./p11
Prompt: scanf() failed
read() failed
$
The problem is clear; the scanf() fails. At issue: why?
The wait() version needs an extra header #include <sys/wait.h> and the correct calling sequence. I used the paragraph:
else
{
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
}
When compiled and run, the output is now:
$ ./p11
Kids are 20461 and 20462
Prompt: Albatross
Albatross
Parent gets PID 20461 status 0x0000
$
So, the question becomes: how or why is the standard input of the child process closed when the parent doesn't wait? It is Bash doing some job control that wreaks havoc.
I upgraded the program once more, using int main(int argc, char **argv) and testing whether the command was passed any arguments:
else if (argc > 1 && argv != 0) // Avoid compilation warning for unused argv
{
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
}
I've got an Heirloom Shell, which is close to an original Bourne shell. I ran the program under that, and it behaved as I would expect:
$ ./p11
Prompt: $ Albatross
Albatross
$ ./p11 1
Kids are 20483 and 20484
Prompt: Albatross
Albatross
Parent gets PID 20483 status 0x0000
$
Note the $ after the Prompt: in the first run; that's the shell prompt, but when I type Albatross, it is (fortunately) read by the child of the p11 process. That's not guaranteed; it could have been the shell that read the input. In the second run, we get to see the parent's output, then the children at work, then the parents exiting message.
So, under classic shells, your code would work as expected. Bash is somehow interfering with the normal operation of child processes. Korn shell behaves like Bash. So does C shell (tcsh). Attempting dash, I got interesting behaviour (3 runs):
$ ./p11
Prompt: $ Albatross
scanf() failed
read() failed
dash: 2: Albatross: not found
$ ./p11
Prompt: $ Albatross
scanf() failed
dash: 4: Albatross: not found
$ read() failed
$ ./p11
Prompt: scanf() failed
$ read() failed
$
Note that the first two runs shows dash reading the input, but the children did not detect problems until after I hit return after typing Albatross. The last time, the children detected problems before I typed anything.
And, back with Bash, redirecting standard input works 'sanely':
$ ./p11 <<< Albatross
Prompt: Albatross
$ ./p11 1 <<< Albatross
Kids are 20555 and 20556
Prompt: Albatross
Parent gets PID 20555 status 0x0000
$
The output Albatross comes from the second child, of course.
The answer is going to be lurking somewhere in behaviour of job control shells, but it's enough to make me want to go back to life before that.

ps command linux vs unix different behavior in c program

I have a simple c program that executes 'ps' and pipes it to 'grep', basically 'ps | grep x'.
the code goes more or less something like this:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int pipefd[2];
int pid;
pipe(pipefd);
pid=fork();
if (pid == 0){
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
execlp("grep", "grep", "b", (char *) 0);
}
else{
close(pipefd[0]);
dup2(pipefd[1], 1);
close(pipefd[1]);
execlp("ps", "ps", (char *) 0);
}
exit(0);
}
The problem that i have is that when i run this on unix (Solaris) is works perfect, but when i run this on (Debian) it executes properly but gives me an error message.
error message:
Signal 17 (CHLD) caught by ps (procps-ng version 3.3.3).
ps:display.c:59: please report this bug
I have try the same program running different commands like 'ls' and 'grep' with no problem on either os. What makes 'ps' different?
EDIT:
added the included libraries to the code.
When your program calls fork, it creates a parent process and a child process. In the child process fork returns 0 and in the parent it returns 1. Whenever a child process terminates, a SIGCHLD signal is sent to the parent process.
Now, in your case you call execlp in both the parent and child process, which replaces the running process image but does not change the relationship. This means that ps is your parent process and grep is your child process. Normally this would not matter, as programs ignore SIGCHLD by default, but ps catches all unknown signals and quits with the message you see there. You can see the relevant function in the source code for ps (or rather procps).

Using C to send an exec process to the background?

My question sounds the same as this but it isn't:
Start a process in the background in Linux with C
I know how to do fork() but not how to send a process to the background. My program should work like a simple command unix shell that supports pipes and background processes. I could do pipe and fork but I don't know how to send a process to the background with & like the last line of the program:
~>./a.out uname
SunOS
^C
my:~>./a.out uname &
How to achieve the background process?
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define TIMEOUT (20)
int main(int argc, char *argv[])
{
pid_t pid;
if(argc > 1 && strncmp(argv[1], "-help", strlen(argv[1])) == 0)
{
fprintf(stderr, "Usage: Prog [CommandLineArgs]\n\nRunSafe takes as arguments:\nthe program to be run (Prog) and its command line arguments (CommandLineArgs) (if any)\n\nRunSafe will execute Prog with its command line arguments and\nterminate it and any remaining childprocesses after %d seconds\n", TIMEOUT);
exit(0);
}
if((pid = fork()) == 0) /* Fork off child */
{
execvp(argv[1], argv+1);
fprintf(stderr,"Failed to execute: %s\n",argv[1]);
perror("Reason");
kill(getppid(),SIGKILL); /* kill waiting parent */
exit(errno); /* execvp failed, no child - exit immediately */
}
else if(pid != -1)
{
sleep(TIMEOUT);
if(kill(0,0) == 0) /* are there processes left? */
{
fprintf(stderr,"\Attempting to kill remaining (child) processes\n");
kill(0, SIGKILL); /* send SIGKILL to all child processes */
}
}
else
{
fprintf(stderr,"Failed to fork off child process\n");
perror("Reason");
}
}
The solution in plain English appears to be here:
How do I exec() a process in the background in C?
Catch SIGCHLD and in the the handler, call wait().
Am I on the right track?
Q: How do I send a process to the background?
A: In general, exactly what you're already doing: fork()/exec().
Q: What's not working as you expect?
I suspect maybe you also want a "nohup" (to completely disassociate the child from the parent).
The key to doing this is to run "setsid()" in the child process:
How to use fork() to daemonize a child process independant of it's parent?
http://www.enderunix.org/docs/eng/daemon.php

Resources