I am implementing a simple shell in c for a class. There are a number of requirements but the one thing that I am concerned about is this sequence of commands:
ls > test
wc < test
which will output the results of the ls command to the file test and the wc
command will then count the number of words, bytes, characters (or something
like that) in that file.
Anyway, the first command works and the test file is successfully created with
the expected content. The wc command doesn't work however. It triggers the error associated with the execv statement "Command can't be executed. My input redirection works as a command like:
grep test < test works perfectly. My question is, Why doesn't my shell recognize the wc command?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
void parse(char buffer[2048], char *arguments[512]){
//these characters(space, tab, new line, return)represent white space
//that separate words
char * delim = " \t\r\n\f";
char * token;
int count = 0;
//Finds the first word in buffer
token = strtok (buffer, delim);
//While a token still exists
while (token != NULL){
//if token is not empty it is added to arguments
if (strlen (token) > 0){
arguments[count]=token;
count++;
}
//Find the next token.
token = strtok (NULL, delim);
arguments[count+1]=NULL;
}
int checkInput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],"<")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectInput(int input,char *arguments[512]){
int in;
int in2;
char*inFile = arguments[input+1];
in = open(inFile, O_RDONLY);
if (in < 0){
perror("Error Opening File");
exit(1);
}
in2 = dup2(in, 0);
if (in2 < 0){
perror("Error redirecting stdin");
exit(1);
}
close(in);
}
int checkOutput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],">")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectOutput(int output,char *arguments[512]){
int out;
int out2;
char*outFile=arguments[output+1];
out = open(outFile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
if (out < 0){
perror("Error Opening File");
exit(1);
}
out2 = dup2(out, 1);
if (out2 < 0){
perror("Error redirecting stdout");
exit(1);
}
close(out);
}
int main(int argc, char **argv){
//buffer is to hold the commands that the user will type in
char buffer[512];
char buffer2[512];
//bin/program_name is the arguments to pass to execv
//if we want to run ls, "/bin/ls" is required to be passed to execv()
char* path = "/bin/";
char * arguments[512];
char * args_copy[512];
//This will be the final path to the program is passed to execv
char prog[512];
char directory[512];
pid_t pid,w;
int status;
int isValid;
int input;
int output;
getcwd(directory,sizeof(directory));
while(1){
isValid = 0;
while(!isValid){
//print the prompt
printf(":");
fflush(stdout);
//get input
fgets(buffer, 512, stdin);
if(buffer[0]!='\n' && buffer[0] != '#'){
isValid=1;
}
}
strcpy(buffer2, buffer);
parse(buffer2, args_copy);
//Handle the builtin functions without before forking
if (strcmp(args_copy[0],"exit")==0){
exit(0);
}
else if (strcmp(args_copy[0],"status")==0){
printf("exit value %d\n",WEXITSTATUS(status));
}else if( strcmp(args_copy[0],"cd") == 0 ){
if(args_copy[1]==NULL){
chdir(directory);
}
else{
chdir(args_copy[1]);
}
}
else{
//fork!
pid = fork();
//Error checking to see if fork works
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
//If pid !=0 then it's the parent
if(pid!=0){
do {
w = waitpid(pid, &status, WUNTRACED | WCONTINUED);
if (w == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
}
else if (WIFSIGNALED(status)) {
printf("killed by signal %d\n", WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
printf("stopped by signal %d\n", WSTOPSIG(status));
}
else if (WIFCONTINUED(status)) {
printf("continued\n");
}
}while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
else{
//parse the user input into an array of strings(arguments)
parse(buffer, arguments);
input = checkInput(arguments);
if (input){
redirectInput(input,arguments);
arguments[input]=NULL;
}
output = checkOutput(arguments);
if (output){
redirectOutput(output,arguments);
arguments[output]=NULL;
}
//First we copy a /bin/ to prog
strcpy(prog, path);
//Then we concancate the program name to /bin/
//If the program name is ls, then it'll be /bin/ls
strcat(prog, arguments[0]);
//pass the prepared arguments to execv and we're done!
int rv=execv(prog, arguments);
if (rv == -1) {
perror("Command can't execute");
exit(EXIT_FAILURE);
}
}
}
}
return 0;
}
This happens because wc is /usr/bin/wc, not /bin/wc.
You can use execvp instead of execv to automatically search through $PATH for your executable. In that case, you would not add /bin/ to the path.
Related
I am doing a simple shell program in Linux. I am trying to implement the pipe operator for shell. I cant seem to find the problem and solution. The execvp didnt read from the previous pipe pipi. So if i do ls -l | more, the more prints bad usage instead of the list of files
Here is my code below.
execCommands handles the commands.
command.h parse the command.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <wait.h>
#include "modules/token.h"
#include "modules/command.h"
#define BUFSIZE 10000
int tokenSize = 0;
int cmdSize = 0;
char promptSymbol[BUFSIZE] = "%";
char cwd[BUFSIZE];
// execute commands accordingly to the apropriate function
void execCommands(Command command[]){
int fd = 0;
char dir[BUFSIZE];
pid_t pid, p2;
int pipi[2];
for(int i = 0;i < cmdSize;i++){
//start executing commands
if(strcmp(command[i].argv[0], "prompt") == 0){
strcpy(promptSymbol, command[i].argv[1]); //change prompt symbol
}else if(strcmp(command[i].argv[0], "pwd") == 0){
if(getcwd(cwd, sizeof(cwd)) != NULL){
printf("%s\n", cwd );
}else{
perror("getcwd() error\n");
}
}else if(strcmp(command[i].argv[0], "cd") == 0){
if(command[i].argv[1] == NULL){
strcpy(dir, "/home");
}else if(strcmp(command[i].argv[1], "~") == 0 || strcmp(command[i].argv[1], "~/") == 0){
strcpy(dir, "/home");
}else{
strcpy(dir, command[i].argv[1]);
}
if(chdir(dir) < 0){
printf("No such directory/file %s found\n", dir);
}
}else{
pid = fork();
if(pid < 0){
perror("pid fork failed\n");
}
if(pid == 0){
if(command[i].stdin_file || command[i].stdout_file){
//check for stdin redirection
if(command[i].stdin_file){
fd = open(command[i].stdin_file, O_RDONLY);
dup2(fd, STDIN_FILENO);
}
//check for stdout redirection
if(command[i].stdout_file){
fd = open(command[i].stdout_file, O_WRONLY | O_CREAT, 0777);
dup2(fd, STDOUT_FILENO);
}
}
if(strcmp(command[i].sep,"|") == 0){
if(pipe(pipi) == -1){
perror("pipi pipe failed\n");
}
close(pipi[0]);
dup2(pipi[1], STDOUT_FILENO);
close(pipi[1]);
execvp(command[i].argv[0], command[i].argv);
p2 = fork();
if(p2 > 0){
close(pipi[1]);
dup2(pipi[0], STDIN_FILENO);
close(pipi[0]);
execvp(command[i].argv[0], command[i].argv);
}
}else{
execvp(command[i].argv[0], command[i].argv);
}
close(fd);
exit(0);
}else{
if(strcmp(command[i].sep, "&") != 0){
wait(0);
}
} // end of first fork()
} //end of command check
} //end of for loop
} //end of execCommands
//handles SIGINT, SIGTSTP and SIGQUIT
void handler(int num){
printf("\nEnter 'exit' to end shell program\n");
}
main code
int main(){
char cmdLine[BUFSIZE];
char *token[BUFSIZE];
Command command[BUFSIZE];
//Runs untill user wants to exit
while(strcmp(cmdLine, "exit") != 0){
signal(SIGTSTP, handler); //Ignores SIGTSTP signal(ctrl+z)
signal(SIGINT, handler); //Ignores SIGINT signal(ctrl+c)
signal(SIGQUIT, handler); //Ignores SIGQUIT signal(ctrl+\)
//get current directory
if(getcwd(cwd, sizeof(cwd)) != NULL){
strcat(cwd, promptSymbol);
}else{
perror("getcwd error");
return 1;
}
//prompts user for icommand
printf("%s ", cwd);
scanf(" %[^\n]", cmdLine);
// split command line into tokens
tokenSize = tokenise(cmdLine, token);
//split the tokens into commands
initCommand(command);
cmdSize = seperateCommands(token, command);
//execute commands accordingly
execCommands(command);
}
return 0;
}
I am trying to implement shell redirection, using this I redirect stdout to fd1
int redirectOut(int fd1)
{
fflush(stdout);
int fd2 = dup(STDOUT_FILENO);
dup2(fd1, STDOUT_FILENO);
close(fd1);
return fd2;
}
I then fork and call an executable, it works except in the case where the executable uses putchar.
On the putchar man page it is written that it uses stdout.
putchar(c); is equivalent to putc(c,stdout).
Why doesn't putchar write anywhere neither in the standard output nor the file I redirected the stream to ?
I tried changing putchar to putc but it didn't help, it might have something to do with the fact that stdout if a *FILE and STDOUT_FILENO an int
How can I make my code work and why does it work with printf which uses (code for printf)
done = vfprintf (stdout, format, arg);
EDIT MORE CODE
int executeBlocs(execBloc *bloc,int fileIn,int fileOut){
if(bloc->first != NULL){
if (strcmp(bloc->ope, ">") == 0){
int out = open(bloc->command[0], O_WRONLY | O_CREAT | O_TRUNC , 0644);
int returnCode = executeBlocs(bloc->first, STDIN_FILENO, out);
redirectOut(fileOut);
redirectIn(fileIn);
return returnCode;
}
}
else{
redirectIn(fileIn);
redirectOut(fileOut);
return call(bloc->nbWords, bloc->command);
}
}
execBloc is a struct that contains a command to execute (or a file name) an operator (>> , | , > ...) and a reference to the another bloc that contains the rest of the command.
If the user enter cat /tmp/testCat > /tmp/testCatRedirection
then a first structure will be created containing the operator > and the command /tmp/testCatRedirection and first which is a reference to the second structure containing the command cat /tmp/testCat
int call(int argc, char const *argv[]) {
if (argc > 0){
if (executeProgram(argv) == 1) return 1;
if (executeStandardLibrary(argc, argv) == 1) return 1;
if (executeDynamicLibrary(argc, argv) == 1) return 1;
}
return -1;
}
int executeProgram(char const *argv[]){
//Creation de la chaine de caractère /home/kerdam/cbin/nonExecutable
char *path = strdup(binFolder);
strcat(path, argv[0]);
//Test si le fichier existe et est executable
if (access(path, F_OK|X_OK) != -1){
//Le fichier existe et on peut l'éxecuter
int pid = fork();
// Error
if (pid == -1){
return -1;
}
//Fils
else if (pid == 0) {
// Executer la commande
execv(path, argv);
return 1;
}
// Parent process
else {
// Wait for child process to finish
int childStatus;
waitpid(pid, &childStatus, 0);
return 1;
}
}
else return -1;
}
Finally the code of the program I am trying to execute
#include<stdio.h>
#include<string.h>
#define MAX_FILE_NAME_CHARS 255
int main(int argc, char *argv[])
{
FILE *fp;
char file_name[MAX_FILE_NAME_CHARS], ch;
int i;
/*
* after creating a.out, rename it as mycat for our own cat command
* and it usage is same as standard cat command
*/
if(argc<=1){
printf("Utiliser cat avec aumoin un argument (un fichier) <nomfichier> \n");
return 0;
}
/*
* This is for multiple file in argument
*/
for(i=1; i<=argc;i++){
strncpy(file_name, argv[i], MAX_FILE_NAME_CHARS);
fp=fopen(file_name, "r");
if(fp == NULL) {
printf("%s: No such file or directory\n", file_name);
return 0;
}
/*
* read file and feed contents to STDIO
*/
while((ch=fgetc(fp)) != EOF || ch == '}'){
putchar(ch);
}
fclose(fp);
}
return 0;
}
Remark
I should not change the code of the executable I am trying to execute as the users of my shell should be able to execute their programs without restriction on what function they can use.
I've been face with the same problem and found the solution.
Look at the code which reads a file argv[1] and writes its content to file argv[2] without spaces and line feeds.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char* argv[]) {
if (argc != 3) {
printf ("Expected exactly two arguments\n");
return 1;
}
int rd = open(argv[1], O_RDONLY);
if (rd == -1) {
printf ("Failed to open file %s\n", argv[1]);
return 1;
}
int wd = open(argv[2], O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (wd == -1) {
printf ("Failed to open file %s\n", argv[2]);
return 1;
}
int temp_in = dup(0);
int temp_out = dup(1);
dup2(rd, 0);
dup2(wd, 1);
close(rd);
close(wd);
int written_bytes = 0;
int c;
while ((c = getchar()) != EOF) {
if (c == ' ' || c == '\n') {
continue;
}
putchar(c);
written_bytes++;
}
// The instruction below is critically important, because
// putchar by default writes char to an internal buffer,
// so we have to send it to the file descriptor manually
fflush(stdout);
dup2(temp_in, 0);
dup2(temp_out, 1);
printf ("%d bytes have been written\n", written_bytes);
return 0;
}
I'm trying to write a simple C shell. My problem is that I have written the program so that when the user enters a NULL value I've got the shell to exit and stop running. However after using different shells i've realised that the shell continues to loop. Is there anyway to fix this without having to rewrite my code? I'm still quite a novice to C.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_CMD_SIZE 512
int getPath(){
printf("PATH = %s\n", getenv("PATH"));
return 0;
}
int setPath(char* arg){
setenv("PATH", arg, 1);
return 0;
}
int setwd() {
char *arg;
arg = getenv("HOME");
chdir(arg);
return 0;
}
int main()
{
char buff[MAX_CMD_SIZE]; //buff used to hold commands the user will type in
char *defaultPath = getenv("PATH");
char *args[50] = {NULL};
char *arg;
int i;
pid_t pid;
setwd();
while(1){
printf(">");
if (fgets(buff, MAX_CMD_SIZE, stdin) == NULL) { //Will exit if no value is typed on pressing enter
setPath(defaultPath);
getPath();
exit(0);
}
arg = strtok(buff, " <|>\n\t");
i = 0;
if (arg == NULL) return -1;
while (arg != NULL){
printf("%s\n", arg);
args[i] = arg;
arg = strtok(NULL, " <|>\n\t");
i++;
}
if (strcmp(args[0], "exit") == 0 && !feof(stdin)){ //Will exit if input is equal to "exit" or ctrl + d
setPath(defaultPath);
getPath();
exit(0);
}
else {
pid = fork();
if (pid < 0){ //Error checking
fprintf(stderr, "Fork Failed\n");
} else if (pid == 0){ //This is the child procsess
execvp(args[0], args);
exit(-1);
} else { //Parent Process
wait(NULL); // Parent will wait for child to complete
}
}
}
return 0;
}
Hi i'm trying to build a shell on linux and i'm stuck with the pipelining part.First i take the inputs from the user like "ls | sort" then when i try to run the program it lookls like the commands ls and sort doesnt work
It looks like i've done everything right but it still cant seem to work. can you help please. thanks in advance
include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int setup();
int main(int argc, const char * argv[])
{
while(1)
{
printf("333sh: ");
if(setup())
break;
}
return 0;
}
int setup(){
char input [128];
char *arg[32];
int i = 1;
while(fgets(input,128,stdin)!=NULL)
{
arg[0] = strtok(input," \n");
while((arg[i]=strtok(NULL," \n")) != NULL){
i++;
}
if (arg[1]!=NULL && strcmp(arg[1],"|")==0 && arg[2]!=NULL ){
pid_t pid;
int fd[3];
pipe(fd);
pid=fork();
if(pid<0){
printf("fork");
}
else if(pid==0){
pid_t cpid;
cpid=fork();
if(cpid==0){
dup2(fd[2], 1); // Replace stdin with the read end of the pipe
close(fd[0]); // Don't need another copy of the pipe read end hanging about
close(fd[2]);
execvp(arg[0],arg);
}
else if(pid>0){
dup2(fd[0], 0); // Replace stdout with the write end of the pipe
close(fd[0]); //close read from pipe, in parent
close(fd[2]); // Don't need another copy of the pipe write end hanging about
execvp(arg[2], arg);
}
}
else if(pid>0){
waitpid(pid, NULL,0);
}
}
}
}
Your biggest problem is that your argument lists for your commands are malformed (after you've resolved the index 2 vs index 1 issue with the pipe file descriptors diagnosed by Ben Jackson in his answer).
I added a function:
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
and called it just before the calls to execvp(), and the output I got was:
$ ./ns
333sh: ls | sort
args for 29780:
0: [ls]
1: [|]
2: [sort]
ls: sort: No such file or directory
ls: |: No such file or directory
^C
$
The control-C was me interrupting the program. The arguments for each command must be 'the command name' (conventionally, the name of the executable), followed by the remaining arguments and a null pointer.
Your tokenization code is not providing two correct commands.
You also have a problem with which PID you're looking at:
cpid = fork();
if (cpid == 0)
{
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else if (pid > 0) // should be cpid!
{
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(pid, arg);
execvp(arg[1], arg);
fprintf(stderr, "Failed to exec %s\n", arg[1]);
exit(1);
}
You also need to close the pipe file descriptors in the parent process before waiting.
This code compiles and 'works' for simple x | y command sequences such as ls | sort or ls | sort -r. However, it is far from being a general solution; you'll need to fix your argument parsing code quite a lot before you reach a general solution.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int setup(void);
int main(void)
{
while (1)
{
printf("333sh: ");
if (setup())
break;
}
return 0;
}
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
int setup(void)
{
char input[128];
char *arg[32];
int i = 1;
while (fgets(input, sizeof(input), stdin) != NULL)
{
arg[0] = strtok(input, " \n");
while ((arg[i] = strtok(NULL, " \n")) != NULL)
{
i++;
}
if (arg[1] != NULL && strcmp(arg[1], "|") == 0 && arg[2] != NULL)
{
pid_t pid;
int fd[2];
arg[1] = NULL;
pipe(fd);
pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (pid == 0)
{
pid_t cpid = fork();
if (cpid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (cpid == 0)
{
printf("Writer: [%s]\n", arg[0]);
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else
{
printf("Reader: [%s]\n", arg[2]);
assert(cpid > 0);
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), &arg[2]);
execvp(arg[2], &arg[2]);
fprintf(stderr, "Failed to exec %s\n", arg[2]);
exit(1);
}
}
else
{
close(fd[0]);
close(fd[1]);
assert(pid > 0);
while (waitpid(pid, NULL, 0) != -1)
;
}
}
}
return 1;
}
You're using fd[0] and fd[2] but pipe(fd) only sets fd[0] and fd[1].
Couple of immediate problems:
setup() has no return value, but you expect an int
The definition of fgets is:
char * fgets ( char * str, int num, FILE * stream );
Get string from stream
Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
fgets() returns NULL on an error; otherwise it returns a pointer to str. So this seems like a very unsound test condition in your while loop.
I'm trying to implement a very small shell of my own. I have to be able to handle pipes, like
ls -l | wc -l
but only for two programs at a time. Right now, I have this:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFFER_SIZE 256
#define NO_PARAMS 32
void split_string(char **params, char *string){
char *arg;
int i;
arg = strtok(string, " ");
params[0] = arg;
i = 1;
while(arg != NULL){
arg = strtok(NULL, " ");
params[i] = arg;
i++;
}
}
int main(int argc, char **argv){
char string[BUFFER_SIZE];
char *prog1, *prog2;
int i, err;
int fd[2];
pid_t pid1, pid2;
size_t buffer = BUFFER_SIZE;
char *params1[NO_PARAMS], *params2[NO_PARAMS];
int pipe_exists = 0;
memset(string,0,buffer);
while(1){
/*Read command*/
fgets(string, BUFFER_SIZE-1, stdin);
if(string == NULL){
perror("Error reading input:\n");
exit(1);
}
/*replace linefeed character with end of line character*/
for(i=0;i<BUFFER_SIZE;i++){
if(string[i] == 10){
string[i] = 0;
}
}
/*check if command is "exit"*/
if(strcmp(string,"exit") == 0){
return 0;
}
/*split command into different program calls*/
prog1 = strtok(string, "|");
prog2 = strtok(NULL,"\0");
if(prog2 != NULL){
pipe_exists = 1;
printf("PIPE!\n");
err = pipe(fd);
if(err<0){
perror("Error creating pipe:\n");
exit(1);
}
}
/*split string into arguments*/
split_string(params1, prog1);
if(pipe_exists){
split_string(params2, prog2);
}
/*fork child process*/
pid1 = fork();
if(pid1==0){ /*child 1*/
if(pipe_exists){
close(fd[0]); /*close read-end*/
err = dup2(fd[1], 1);
if(err<0){
perror("Error with dup in child 1!\n");
exit(1);
}
}
execvp(params1[0],params1);
perror("Error calling exec()!\n");
exit(1);
}else{ /*parent*/
if(pipe_exists){
pid2 = fork();
if(pid2==0){ /*child 2*/
close(fd[1]); /*close pipe write-end*/
err = dup2(fd[0], 0);
if(err<0){
perror("Error with dup in child 2!\n");
exit(1);
}
execvp(params2[0],params2);
perror("Error calling exec()!\n");
exit(1);
}else{ /*parent with 2 children*/
waitpid(pid1,0,0);
waitpid(pid2,0,0);
}
}else{ /*parent with 1 child*/
waitpid(pid1,0,0);
}
}
}
}
Right now, it'll handle single commands fine, but when I input something like the command above, nothing happens!
Thanks!
Oh! I've already figured it out. I had to close the pipe in the parent program as well :)
To start with, you should loop as long as you find the pipe character. Then you need to create a pipe for each "piping".
Real shells usually forks and exec itself for each command in the pipeline. This is so it should be able to handle internal commands.
There are 3 main parts in a command with pipes.
The begining, that takes stdin and pipes its output something |
The middle, optionnal or repeated at will with two pipes | something |
The end, that outputs to stdout | something
Then use three functions, one for each of those:
#define PIPE_INPUT 0
#define PIPE_OUTPUT 1
execute_pipe_start(t_cmdlist *commands)
{
int pid;
int fd[2];
if (!commands)
return;
if (commands->next)
{
if (pipe(fd) < 0)
{
perror("pipe failed");
exit(1);
}
pid = fork();
if (!pid)
{
close(fd[PIPE_INPUT]);
if (dup2(fd[PIPE_OUTPUT, 1) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
{
waitpid(...); //what you put here is a bit tricky because
//some shells like tcsh will execute all
//commands at the same time (try cat | cat | cat | cat)
}
if (commands->next->next != null) //If you have 2 commands in line there is a middle
execute_pipe_middle(commands->next, fd);
else // no middle
execute_pipe_end(commands->next, fd);
}
else
parse_and_exec_cmd(commands->cmd);
}
execute_pipe_middle(t_cmdlist *commands, int fd_before[2])
{
int pid;
int fd_after[2];
if (pipe(fd_after) < 0)
{
perror("pipe failed");
exit(1);
}
pid = fork();
if (!pid)
{
close(fd_before[PIPE_OUTPUT]);
close(fd_after[PIPE_INPUT]);
if (dup2(fd_after[PIPE_OUTPUT, 1) < 0)
{
perror("dup2 failed");
exit(1);
}
if (dup2(fd_before[PIPE_INPUT, 0) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
waitpid(...);
if (commands->next->next != null) //More than two following commands : a middle again
execute_pipe_middle(commands->next, fd_after);
else // No more repetition
execute_pipe_end(commands->next, fd_after);
}
execute_pipe_end(t_cmdlist *commands, int fd_before[2])
{
int pid;
if (!commands)
return;
if (commands->next)
{
pid = fork();
if (!pid)
{
close(fd_before[PIPE_OUTPUT]);
if (dup2(fd_before[PIPE_INPUT, 0) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
waitpid(...);
}
}