I know about the fork(), dup2 calls but I cannot infer the output of the program.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main (int argc, char *argv[]){
pid_t pid1, pid2;
int fds[2];
char *argv1[] = { "ls", "-l","usr/bin", NULL};
char *argv2[] = {"more", NULL};
pipe(fds);
pid1=fork();
if(!pid1){
close(fds[0]);
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
execvp(argv1[0],argv1);
}
pid2=fork();
if(!pid2){
close(fds[1]);
dup2(fds[0], STDOUT_FILENO);
close(fds[0]);
execvp(argv1[0],argv2);
}
close(fds[0]);
close(fds[1]);
waitpid(pid2,NULL,0);
return EXIT_SUCCESS;
}
Also what would happen if i experiment and mistakenly press pid1 instead of pid2 at the waitpid call in the end.
Nominally, the program is running the shell pipeline
ls -l usr/bin | more
In practice, the connections to more are mishandled (using copy-and-paste is dangerous if you don't make all the necessary changes). It connects the read end of the pipe to the standard output of more, which is simply broken. It also runs ls a second time (passing argv1[0] to execvp() instead of argv2[0]) but tells ls that its name is more. It also doesn't believe execvp() can fail — but it can.
With those minimal fixes in place (the program doesn't include <stdio.h> so there is no error reporting), you get something like:
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
pid_t pid1, pid2;
int fds[2];
char *argv1[] = { "ls", "-l", "usr/bin", NULL };
char *argv2[] = { "more", NULL };
pipe(fds);
pid1 = fork();
if (pid1 == 0)
{
close(fds[0]);
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
execvp(argv1[0], argv1);
exit(EXIT_FAILURE);
}
pid2 = fork();
if (pid2 == 0)
{
close(fds[1]);
dup2(fds[0], STDIN_FILENO);
close(fds[0]);
execvp(argv2[0], argv2);
exit(EXIT_FAILURE);
}
close(fds[0]);
close(fds[1]);
waitpid(pid2, NULL, 0);
return EXIT_SUCCESS;
}
I called the program pipe31 (created from pipe31.c) and got the sample output:
$ pipe31
ls: usr/bin: No such file or directory
$ mkdir -p usr/bin
$ random -n 15 1000 9999 > usr/bin/polyglot
$ pipe31
total 8
-rw-r--r-- 1 jonathanleffler staff 75 Dec 8 22:07 polyglot
$
When invoked as shown, the local random program creates 15 random numbers between 1000 and 9999, each on its own line — corresponding to 75 bytes in the file. The outputs were piped via more, but it is hard to spot that in a web browser.
As to your proposed experimentation — experiment away — you won't cause harm. What you see will depend on the size of the directory you're working in, and the size of your terminal window. However, if the output from ls -l is big enough (but not too big), then you'd get a shell prompt in the middle of your ls listing, and you'd have more showing data and waiting to read a newline. There could potentially be a competition between the shell and more for the following inputs which could get interesting. That assumes you fix the problems identified in the rest of the answer before experimenting.
Related
I know this is a simple exercise but I'm having troubles with it.
ps | sed 1d | wc -l
I am trying to generate an output of the above terminal command using c code for my school exercise. I have written a code that is redirecting all the inputs and outputs to the required destination but I am not getting any output on the console as I haven't redirected the final output in any file so it must be displayed on console.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#define READ 0
#define WRITE 1
int main()
{
//fd fd1
//ps | sed 1d | wc -l
pid_t pid;
pid_t pid1;
pid_t pid2;
int fd[2];
int fd1[2];
pipe(fd);
pipe(fd1);
pid=fork();
if(pid==0)
{ //redirecting into sed 1d
close(fd[READ]);
dup2(fd[WRITE],1);
close(fd[WRITE]);
execlp("ps","ps",NULL);
}
else
{
pid1=fork();
if(pid1==0)
{ //redirecting into wc -l
close(fd[WRITE]);
dup2(fd[READ],0);
close(fd[READ]);
close(fd1[READ]);
dup2(fd1[WRITE],1);
close(fd1[WRITE]);
execlp("sed","sed","1d",NULL);
}
else
{
pid2=fork();
if(pid2==0)
{ //must display on console
close(fd1[WRITE]);
dup2(fd1[READ],0);
close(fd1[READ]);
execlp("wc","wc","-l",NULL);
}
else
{
close(fd[READ]);
close(fd[WRITE]);
close(fd1[READ]);
close(fd1[WRITE]);
wait(NULL);
}
}
}
}
What can be the problem?
The ps process has both ends of the fd1 pipe open, which it shouldn't. Also, the wc -l process has both ends of the fd pipe open, which it shouldn't. Fix both of those things and your program will stop hanging. Also, wait(NULL) only waits for one child process to terminate, not all of them, so you're risking the terminal prompt reappearing before your program is actually done.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char *getcwd(char *buf, size_t size); //define getcwd
char PATH_MAX[1024]; //define max size of path
int chdir(const char *path);
int main(int argc, char *argv[]) { // gets arguments when program ran, no arguments means argv=1
pid_t pid; //process ID = pid
pid=fork();
char cwd[1024]; //compare directory to max character size
if(pid==0){ //child has been forked! //child process created
int ret;
printf("Child PID=%d\n", getpid());
getcwd(PATH_MAX, sizeof(PATH_MAX));
printf(" My current working directory is: %s\n", PATH_MAX);
ret= execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
printf("%d\n", ret); //why isn't this printed out?
}
//}
else {
int status;
//parent process
//wait for child to complete
printf("Parent PID=%d\n", getpid());
if (waitpid(pid, &status, 0) == -1) {
printf("ERROR");
}
else {
printf("Child done.\n");
getcwd(PATH_MAX, sizeof(PATH_MAX));
printf("0");
exit(0);
}
}
}
I left my commented out code so you can see my thought process. If my understanding is correct the shell(terminal) is its own process so when you call fork, it creates a new child process and its parent becomes the shell. So trying to chdir in the child process will not translate over to the shell and you will remain in the same Directory so you would need to execute the chdir function in the parent PID, which is now the shell, yes?
I am having a hard time trying to figure out where exactly I should be putting this chdir() command and what flavor of exec I need to use to execute the terminal commands.
I am testing 3 different commands as command line arguments when running in terminal. This is after making the file with gcc -o script script.c
$ ./script
result - print out current directory
print out "Usage: "<dir>" string. no command executed
$ ./script .
result -"Executing ls . --all -l --human-readable" string
executes above commands
$./script /
result - should execute above commands but change directory before
executing
$./script /blah/blah
result - can't execute chdir
exit status: 1
I believe this code should cause the child process to return a -1 which would terminate it, or if my if statement is correct it would print out the error message.
Any help would be appreciated, I believe I got the logic down, or at least somewhat. Just having a hard time implementing chdir.
I cooked your program a little and got the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int main(int argc, char *argv[]) // gets arguments when program ran, no arguments means argv=1
{
int ret;
pid_t pid; //process ID = pid
pid=fork();
char cwd[1024]; //compare directory to max character size
char newPath[200]=".";
if( argc > 1 )
{
strcpy(newPath,argv[1]);
}
ret=chdir(newPath);
if( ret < 0 )
{
printf("Problem switching to :%s\n", newPath);
perror("chdir");
exit(ret);
}
if(pid==0){ //child has been forked! //child process created
int ret;
printf("Child PID=%d\n", getpid());
getcwd(cwd, sizeof(cwd));
printf(" My current working directory is: %s\n", cwd);
ret= execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
printf("%d\n", ret); //why isn't this printed out?
}
else {
int status;
//parent process
//wait for child to complete
printf("Parent PID=%d\n", getpid());
if (waitpid(pid, &status, 0) == -1) {
printf("ERROR");
} else
{
printf("Child done. stat=%d\n", status);
getcwd(cwd, sizeof(cwd));
printf("Parent cwd:%s\n", cwd);
printf("0");
exit(0);
}
}
}
Your understanding is not quite correct. When you execute a program from the command line on a terminal, the shell forks and does an execute of the process you are running. Normally, the parent shell process waits until the child process is done. You created a child process and did another exec and another wait. The parent shell patiently waits for you to finish, forked processes and all.
Let's see:
I put the chdir in your main program to show the child follows the parent.
A child process is not going to change the working directory of the parent. Unix and Linux don't work that way.
When you run execl, that is it. There is no return unless it can't do the execute. The point of exec* is you are blowing away your currently running program with a new executable. There is nothing to return to.
If you want to see the return code of the child, look at the status returned by the wait. In this case, the ls ran fine so the return code is zero. If you added a last argument of "baddir" (that is not there) you would see the ls non-zero return code, in this case, 512.
I'm trying to execute the Linux command "ls -l | tail -n 2" with a simple pipe in a c code.
I added your tips and now this works but the output isn't exactly as it should be. It prints the output in a single line instead of two and waits for a user input to close.
here is the new code:
#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
#include "sys/wait.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main()
{
char line[100];
pid_t pid;
int fd[2];
int status;
char* ls_arguments[] = {"ls", "-l", NULL};
char* tail_arguments[] = {"tail", "-n", "2", NULL};
pipe(fd);
pid = fork();
if(pid == 0)//ls client
{
close(1);
dup(fd[1]);
close(fd[0]);
execvp("ls", ls_arguments);
}
pid = fork();
if(pid == 0)//tail client
{
close(0);
close(fd[1]);
dup(fd[0]);
execvp("tail", tail_arguments);
}
wait(pid, 0, WNOHANG);
close(fd[0]);
close(fd[1]);
}
this should run the "ls -l" command and output to the pipe and the next "tail" client would get it as input and run the "tail -n 2" command and print out the final output but the terminal prints nothing. Any help?
First of all, there is not such wait function, here is what the man says:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
I think you meant to use waitpid.
Then, you child process doesn't finish because the pipe is still opened somewhere: in the parent. Indeed you should first close the descriptors and then wait for your childs process. I would write:
close(fd[0]);
close(fd[1]);
wait(NULL); // Wait for the first child to finish
wait(NULL); // Wait fot the second one
return 0;
}
Instead of:
wait(pid, 0, WNOHANG);
close(fd[0]);
close(fd[1]);
}
I'm trying to create pipes in the shell to redirect standard streams and I am stuck now.
When I try to run this code:
int fd[2];
pid_t pid;
pipe(fd);
pid = fork();
if (pid == 0)
{
// child process
// redirect standard input and output
dup2(fd[1], STDOUT_FILENO);
dup2(fd[0], STDIN_FILENO);
// close them (they are now redirected)
close(fd[0]);
close(fd[1]);
char *input_argv[] = {"/bin/ls", "/bin/ls", ">", "out.txt", NULL};
execv(input_argv[0], input_argv);
}
else if (pid > 0)
{
// parent process
waitpid(pid, NULL,0);
}
I got this error messages:
/bin/ls: cannot access >: No such file or directory
/bin/ls: cannot access out.txt: No such file or directory
I have no idea what they mean, what cause them and how to fix them.
What am I doing wrong?
All in all, the code doesn't make any sense. I think the best answer one can give here is to explain the most problematic parts:
// redirect standard input and output
dup2(fd[1], STDOUT_FILENO);
dup2(fd[0], STDIN_FILENO);
fd[0] is the reading end, fd[1] the writing end of a pipe. Whatever you write to fd[1] is available for read on fd[0]. So, this is just "short-circuiting" your stdio streams, something not usefull at all.
What you want to do with pipes normally is have one pipe per direction of communication between parent and child process (e.g. child should read from parent: dup2() the reading end to STDIN_FILENO in the child and write to the writing end from the parent).
char *input_argv[] = {"/bin/ls", "/bin/ls", ">", "out.txt", NULL};
Now this doesn't make sense either. A > tells a shell to open a file for writing and exec() the child with a redirected STDOUT_FILENO already in place. It's certainly not an argument understood by ls here. You don't have a shell, you just exec() ls directly.
If your original intention was to mimic what the shell would do when given
ls > out.txt
you should just open the file out.txt for writing and in the child code dup2() the file descriptor of your opened file to STDOUT_FILENO before exec()ing ls. There's no need for a pipe in this scenario.
edit in case you want to understand what a shell does internally for ls > out.txt:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
/* open file for writing */
int outfd = open("/tmp/out.txt", O_CREAT|O_WRONLY, 00666);
if (outfd < 0)
{
perror("open()");
return EXIT_FAILURE;
}
/* fork child */
int pid = fork();
if (pid < 0)
{
perror("fork()");
return EXIT_FAILURE;
}
if (pid == 0)
{
/* in the child, redirect stdout to our file */
if (dup2(outfd, STDOUT_FILENO) < 0)
{
perror("dup2()");
return EXIT_FAILURE;
}
close(outfd);
/* then execute 'ls' */
execlp("ls", "ls", 0);
/* only reached when execlp() fails: */
perror("execlp()");
return EXIT_FAILURE;
}
/* we don't need the output file in the parent process: */
close(outfd);
/* wait for child to complete */
int childrc;
waitpid(pid, &childrc, 0);
/* return exit code of child process */
return childrc;
}
Of course, the code of the actual shell looks different (doesn't have names hardcoded, uses execv* family of functions because it doesn't know the number of arguments in advance, and so on.)
How do I use dup2 to perform the following command?
ls -al | grep alpha | more
A Little example with the first two commands. You need to create a pipe with the pipe() function that will go between ls and grep and other pipe between grep and more. What dup2 does is copy a file descriptor into another. Pipe works by connecting the input in fd[0] to the output of fd[1]. You should read the man pages of pipe and dup2. I may try and simplify the example later if you have some other doubts.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define READ_END 0
#define WRITE_END 1
int
main(int argc, char* argv[])
{
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if(pid==0)
{
printf("i'm the child used for ls \n");
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[WRITE_END]);
execlp("ls", "ls", "-al", NULL);
}
else
{
pid=fork();
if(pid==0)
{
printf("i'm in the second child, which will be used to run grep\n");
dup2(fd[READ_END], STDIN_FILENO);
close(fd[READ_END]);
execlp("grep", "grep", "alpha",NULL);
}
}
return 0;
}
You would use pipe(2,3p) as well. Create the pipe, fork, duplicate the appropriate end of the pipe onto FD 0 or FD 1 of the child, then exec.