Related
I am writing my own shell. I am facing a problem with the commands like C1 | C2 > file or C1 | C2 >> file. When I execute a command like ls | grep .c > a.txt , I get the result of ls | grep only when I terminate the program. But I want to get it during the execution.
Code from main.c:
if (countPipes == 1 && strstr(userInput, ">>") != NULL){
token = NULL;
resetC(cmd);
resetC(cmdPipe);
token = strtok(userInput, ">>");
char *piped = strdup(token);
token = strtok(NULL, ">>");
char *file = strdup(token);
file = skipwhite(file);
token = strtok(piped, "|");
c1 = strdup(token);
token = strtok(NULL, "|");
c2 = strdup(token);
c2 = skipwhite(c2);
splitCommands(c1, cmd);
splitCommands(c2, cmdPipe);
execPipedCommandsRed(cmd, cmdPipe, file);
memset(userInput, '\0', 1000);
}
Code from functions.c:
void execPipedCommandsRed(char **cmd, char **cmdPiped, char *file){
int pipeOne[2], status, ret_val, s;
status = pipe(pipeOne);
if (status < 0) {
exit(-1);
}
int k;
int e = dup(1);
pid_t p1, p2, w;
int s2 = dup(1);
p1 = fork();
if (p1 < 0) {
printf("Fork failed!\n");
}
if (p1 == 0) {
close(pipeOne[READ]);
dup2(pipeOne[WRITE], STDOUT_FILENO);
close(pipeOne[WRITE]);
if (execvp(cmd[0], cmd) < 0) {
perror("Lathos");
}
} else {
p2 = fork();
if (p2 < 0) {
printf("Fork failed\n");
}
if (p2 == 0) {
close(pipeOne[WRITE]);
dup2(pipeOne[READ], STDIN_FILENO);
close(pipeOne[READ]);
k = open(file, O_WRONLY| O_APPEND | O_CREAT, 0644);
if (k < 0) {
puts("error k");
}
dup2(k, 1);
close(k);
if (execvp(cmdPiped[0], cmdPiped) < 0) {
perror("Lathos!");
}
} else {
// parent is waiting
waitpid(-1, &s, WUNTRACED | WCONTINUED);
printBash();
}
}
}
When i execute a command like ls | grep .c > a.txt, i get the result of ls | grep only when i terminate the program.
The traditional POSIX shell behaviour is that stdout is line-buffered only when it goes into a terminal (isatty returns 1), otherwise it is block-buffered. See setvbuf for buffering descriptions.
If you'd like matching files to be output into stdout as they are found, instead of ls | grep .c > a.txt use the following command:
stdbuf --output=L find -maxdepth 1 -name "*.c" > a.txt
stdbuf allows to explicitly specify the desired buffering mode. And find outputs one filename per-line, unlike plain ls.
You can do ls and grep, but that is sub-optimal in the number of processes involved and that each process must have its output buffering specified explicitly:
stdbuf --output=L ls -1 | stdbuf --output=L egrep '\.c$' > a.txt
Notes:
stdbuf only affects C standard streams and some heavily optimised applications may not use C standard streams at all, so that stdbuf may have no effect.
grep .c matches anything that has character c at non-0 position. Whereas find -name "*.c" matches only files with extension .c. So does egrep '\.c$'.
ls outputs multiple files per line, but grep filtering expects one file per line. ls -1 outputs one file per line.
1) I can't use some commands in this code, such as : cat somefile.txt > somefile2.txt
also I can't use : cat somefile.txt | less
2) When I use commands like : ( cd ../) (cd ./Desktop) and then I want to exit the program, I need to execute exit commands more than one time: ie "if I use 3 cd command, I will need 3 exit commands to end the program"
#define MAX_ARGS 5
// Global Declarations
// Mini Functions
void remove_new_line_char(char line[])
{
int i=0;
while(line[i]!= '\n')
i++;
line[i] = '\0';
}
// Grand Functions
int read_line(char line[])
{
fgets(line, 10000, stdin); // File Get String
remove_new_line_char(line); // Remove New Line Charactere
if (strlen(line) > 512)
{
fprintf(stderr,"The Command exceeded available line length\n");
return 0;
}
if (strcmp(line, "exit") == 0)
exit(0);
return 1;
}
int parse_line(char* args[], char line[])
{
int i=0;
args[i] = strtok(line, " ");
if(args[i] == NULL)
{
printf("Command Line is Empty!\n");
return -1;
}
while (args[i] != NULL)
{
int flag = 0;
if(strcmp(args[i],"&") == 0)
flag = 1;
i++;
args[i] = strtok(NULL, " "); // NULL maintains a static pointer to the previously passed string.
if (args[i] == NULL && flag == 1)
{
args[i-1] = NULL; // Remove & From Argument List and Set Background Flag.
return 1;
}
}
return 0;
}
// Main
int main()
{
char* args[MAX_ARGS]; // Array of Strings
char line[10000]; // String
while(1)
{
printf("Shell> ");
if(read_line(line) == 1) // No Errors
{
int background = parse_line(args, line);
if(background != -1) // Command Line isn't Empty
{
// Fork and Execute
pid_t child_pid = fork();
if(child_pid == 0) // Child
{
if (strcmp(args[0], "cd") == 0 && args[1]!= NULL && args[2] == NULL) // Special Handling For CD
{
//printf("%s\n",args[2]);
int check = chdir(args[1]);
if(check == -1)
fprintf(stderr, "Invalid Directory\n");
}
// Handle if args[1]== NULL, Don't even execvp()
else // Other Functions
{
execvp(args[0], args); // args[0] is actually the command.
fprintf(stderr,"an error occured in execution\n%s\n",strerror(errno));
//fprintf(stderr,"Invalid Instruction\n");
}
}
else // Parent
{
if(background == 0)
waitpid(child_pid, 0);
wait(1000);
}
}
}
}
return 0;
}
I suspect that I can't use any command that has characters like: > < |
Thanks in advance
1) i can't use some commands in this code as : cat somefile.txt > somefile2.txt also i can't use : cat somefile.txt | less
In the standard shell, the > and | are operators interpreted by the shell, not arguments to the command. Since in this case the shell is your program itself, if you must support those operators then you'll need to implement the appropriate redirections yourself. Refer to open(), pipe(), and dup2(), and for the pipe case you'll also need judicious application of close().
2)when i use commands like : ( cd ../) (cd ./Desktop) and then i want to exit the program, i need to execute exit command more than one time "if i use 3 cd command i will need 3 exit command to end the program"
In the special case of the cd command, you fork and then change directory in the child process, but the child does not terminate or exec another process. That leaves you with two copies of your shell running. You need to exit both before control returns to whatever process launched your program. Possibly in that case you want to instead execute chdir without forking (or waiting for a child).
I am trying to create a shell similar to bash, with redirection and pipes.
In my main(), I call a parser and then call the function below. The problem I am running into is the fact that when I run my shell, it outputs to the terminal correctly, but it does not output to the file correctly when using the >, 1>, or 2>.
For instance, if I call:
pwd > foo5.txt
I end up receiving:
>
foo5.txt
In the text file I write too as opposed to the stdout (for ">"/"1>") or stderr (for "2>") which I am trying to achieve.
This is my code to fork and create the child process:
pid_t create_process(char *part, int const pipes[][2], int pipenum)
{
pid_t pid; // Initialize variables/pointers/arrays.
char *args[64];
int argc=0, n;
char *arg=strtok(part, " \t");
//char const **filename = args;
while(arg != NULL)
{
args[argc++]=arg;
arg=strtok(NULL, " \t");
}
args[argc++]=NULL;
pid = fork(); // Create Fork.
if(pid == 0)
{
int m;
if(pipes[pipenum][STDIN_FILENO] >= 0)
dup2(pipes[pipenum][STDIN_FILENO], STDIN_FILENO); // FD 0.
if(pipes[pipenum][STDOUT_FILENO] >= 0)
dup2(pipes[pipenum][STDOUT_FILENO], STDOUT_FILENO); // FD 1.
// Close all pipes.
for(m=0; m<64; m++)
{
if(pipes[m][STDIN_FILENO] >= 0)
close(pipes[m][STDIN_FILENO]);
if(pipes[m][STDOUT_FILENO] >= 0)
close(pipes[m][STDOUT_FILENO]);
}
char *filename;
char *newargs[64];
newargs[63] = NULL;
int i = 0;
int j = 0;
for(i = 0; i<64; i++)
{
if (args[i] == ">")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 1);
close(redir);
}
}
else if (args[i] == "2>")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 2);
close(redir);
}
}
else if (args[i] == "2>")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 2);
close(redir);
}
}
else if (args[i] == 0)
{
break;
}
else
{
newargs[j] = args[i];
j++;
cout<<"The arg is: " << newargs[j] <<endl;
}
}
execvp(newargs[0], newargs);
fprintf(stderr, "Command not found.\n");
exit(255);
}
else if(pid < 0)
{ // Error checking.
fprintf(stderr, "Fork Failed\n");
}
return(pid);
}
UPDATE: Now my code will not recognize the commands, and the arguments being printed (for error checking) appear as such:
ls > foo5.txt
The arg is: fprintf
The arg is:
The arg is: ▒
Command not found.
I see a few problems here:
First, you have the for loop where you scan the command arguments for redirection syntax (if (strcmp(args[i],">")==0) and so on) - but if the condition is true (meaning you found a redirection character) you're always opening args[2], not args[i+1].
Second (and this is why the redirection syntax gets passed on to the command you're running as command arguments) - once you detect redirection syntax, you don't remove the redirection operator or the target filename from the list of arguments that you pass to execvp().
For instance, if args[] = {"echo", "a", ">", "logfile", 0}, your code detects a request to send the output to a new file called "logfile" and redirects the FDs correctly, but it still passes those three arguments ["a", ">", "logfile"] to the command.
Third - in your loop, you're calling execvp() at the end of each conditional statement - meaning that you don't get to the end of argument processing before you launch the new process. You need to process all the command arguments for shell syntax and then exec the command.
To fix the various problems with arg handling, probably the most effective solution is to build a new argument list as you're processing the raw ones provided by the user. For instance (using a==b as a string equality test for brevity)
if (args[i] == ">")
{
i++; //skip the arg
if (args[i]) { // check we haven't hit the end of the arg list
filename = args[i];
// then open the file, dup it to stdout or whatever, etc...
}
else if (args[i] == "<") // repeat for other redirection syntax...
else { // Finally, handle the case where we didn't identify any shell syntax:
newargs[j] = args[i]; // Copy the arg to the new list, since it isn't "special"
j++; // Size of newargs[] has been increased
}
Note that this still doesn't handle things like no whitespace around the ">" character: "echo foo>file" will just print "foo>file"... Syntax processing gets a little more complicated in that case, as you've got to account for quoting rules and escape characters to process the arguments correctly.
I am using forks and pipes to find the amount of 1s and 0s in a string inside of a file. However, I am never reaching the proper end to my program that tallys up the ones and zeroes. It's a pretty small amount of code so here is the whole program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
int main (int argc, char** argv)
{
int leftR, rightR;
char *string;
long size;
int recursion = 0;
if (argc == 3)
{
string = argv[1];
size = strlen(string);
printf("the string is %s\n", string);
if (size <= 2)
{
int bitCounter[2];
bitCounter[0] = 0;
bitCounter[1] = 0;
int i;
for (i=0; i < size; i++)
{
if (string[i]=='0')
{
bitCounter[0]++;
}
else
{
bitCounter[1]++;
}
}
write(STDOUT_FILENO, &bitCounter, sizeof(int)*2);
printf("read bits, sending back %d ones and %d zeroes\n", bitCounter[1], bitCounter[0]);
return 0;
}
else
{
recursion = 1;
}
}
if (argc == 2 || recursion)
{
char *data;
if (!recursion)
{
FILE* filePointer;
if ((filePointer = fopen(argv[1], "r")) == NULL)
{
perror("file didn't work");
}
fseek(filePointer, 0, SEEK_END);
size = ftell(filePointer);
fseek(filePointer, 0, SEEK_SET);
data = malloc(size+1);
fread(data, size, 1, filePointer);
fclose(filePointer);
}
else
{
data = malloc(size+1);
data = string;
}
char *right;
char *left = malloc((size/2)+1);
if (size%2 == 0)
{
right = malloc(size/2 + 1);
}
else
{
right = malloc(size/2 + 2);
}
memcpy(left, data, size/2);
if (size%2 == 0)
{
memcpy(right, (size/2) + data, size/2);
}
else
{
memcpy(right, (size/2) + data, (size/2) + 1);
}
int pidLeft, pidRight;
int leftPipe[2];
int rightPipe[2];
pipe(leftPipe);
pipe(rightPipe);
fd_set readF;
FD_ZERO(&readF);
FD_SET(leftPipe[0], &readF);
FD_SET(rightPipe[0], &readF);
pidLeft = fork();
if (pidLeft > 0)
{
pidRight = fork();
if (pidRight > 0)
{
struct timeval timer;
timer.tv_sec = 3;
timer.tv_usec = 0;
close(rightPipe[1]);
close(leftPipe[1]);
dup2(leftPipe[0], STDOUT_FILENO);
dup2(rightPipe[0], STDOUT_FILENO);
select(2, &readF, NULL, NULL, &timer);
read(leftPipe[0], &leftR, sizeof(int)*2);
read(rightPipe[0], &rightR, sizeof(int)*2);
printf("going back to parent.\n");
}
else if (pidRight == 0)
{
close(rightPipe[0]);
execl("my_program", "my_program", right, "y", NULL);
printf("recursion start\n");
exit(1);
}
}
else if (pidLeft == 0)
{
close(leftPipe[0]);
execl("my_program", "my_program", left, "y", NULL);
printf("start recursion LEFT\n");
exit(1);
}
else
{
fprintf(stderr, "something went wrong! No fork!\n");
}
}
else
{
fprintf(stderr, "Please input file name properly\n");
exit(1);
}
int zeroes = leftR + rightR;
int* numOnes[2];
numOnes[0] = &leftR + sizeof(int);
numOnes[1] = &rightR + sizeof(int);
int ones = (int) *numOnes[0] + (int) *numOnes[1];
printf("0's: %d\n1's: %d\n", zeroes, ones);
return 0;
}
However, the output never reaches my desired end that adds everything up:
the string is 01010▒z
the string is 010
the string is 0
read bits, sending back 0 ones and 1 zeroes
the string is 10▒z
the string is 10
read bits, sending back 1 ones and 1 zeroes
the string is ▒z
read bits, sending back 2 ones and 0 zeroes
the string is 10
read bits, sending back 1 ones and 1 zeroes
the string is 10100
z
the string is 101
the string is 1
read bits, sending back 1 ones and 0 zeroes
the string is 01
read bits, sending back 1 ones and 1 zeroes
the string is 00
z
the string is 00
read bits, sending back 0 ones and 2 zeroes
the string is
z
read bits, sending back 2 ones and 0 zeroes
(140) Admin $
A couple quick points to understand the code a little easier:
argc is only 3 when it is being executed within a child
children will only read the string if it is less than or equal to 2, otherwise recursively cut the string in half again (and make more children)
parents make both a left and right child, both taking half of the string to work on
I guess a couple easy questions to start off would be:
Am I using select() and execl() properly?
Am I using read() and write() properly?
Is there an exit in the main processes I am missing?
Are both children working and using the pipe correctly?
(less important) is the strange character in the strings messing up my count? Is that a null terminator somehow inside my string?
This is one of the tougher nuts to crack. The code is doing an obscure job and is written rather obscurely. As far as I can tell, it is supposed to be called my_program, and is due to be invoked with a file name as a single argument. The process will then open the file, read its content into two arrays (left and right), without bothering to ensure that they are strings (no null termination). The process then forks twice. One then execs itself with the left (non-)string as an argument and the information that it is left, and one execs itself with the right (non-)string as an argument and the information that it is right. The parent process messes around with select() for no very good reason, and without checking the return value. It then calls read to get information on the two pipes. These reads would block until there's data ready, so the select() really doesn't help at all. (I'm still trying to work out where the recursion bit fits in.) Also, most of the system calls are not error checked.
However, given that outline operation (read file, split, children exec and do half each and report back), I would be writing something like this:
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void err_exit(char const *fmt, ...);
int main(int argc, char * *argv)
{
int l_data[2] = { -1, -1 };
int r_data[2] = { -1, -1 };
char *string;
size_t size;
char *arg0 = argv[0];
if (argc != 2 && argc != 3)
err_exit("Usage: %s file\n", argv[0]);
if (argc == 3)
{
/* Child process */
string = argv[1];
size = strlen(string);
fprintf(stderr, "%d: the string is %s\n", (int)getpid(), string);
if (size <= 2)
{
int bitCounter[2];
bitCounter[0] = 0;
bitCounter[1] = 0;
for (size_t i = 0; i < size; i++)
{
if (string[i] == '0')
bitCounter[0]++;
else if (string[i] == '1')
bitCounter[1]++;
}
if (write(STDOUT_FILENO, bitCounter, sizeof(int)*2) != sizeof(int)*2)
err_exit("%d: failed to write on standard output\n",
(int)getpid());
fprintf(stderr, "%d: read bits, sending back %d ones and %d zeroes\n",
(int)getpid(), bitCounter[1], bitCounter[0]);
exit(0);
}
fprintf(stderr, "%d: doing recursion - string too big (%zu)\n",
(int)getpid(), size);
}
char *data = string;
if (argc == 2)
{
FILE *filePointer;
if ((filePointer = fopen(argv[1], "r")) == NULL)
{
perror("file didn't work");
exit(1);
}
fseek(filePointer, 0, SEEK_END);
size = ftell(filePointer);
fseek(filePointer, 0, SEEK_SET);
data = malloc(size+1);
fread(data, size, 1, filePointer);
data[size] = '\0';
if (data[size-1] == '\n')
data[--size] = '\0';
fclose(filePointer);
fprintf(stderr, "%d: data <<%s>>\n", (int)getpid(), data);
}
size_t l_size = size/2;
size_t r_size = size - l_size;
char *left = malloc(l_size+1);
char *right = malloc(r_size+1);
memcpy(left, data, l_size);
left[l_size] = '\0';
memcpy(right, data + l_size, r_size);
right[r_size] = '\0';
int l_pid, r_pid;
int l_pipe[2] = { -1, -1 };
int r_pipe[2] = { -1, -1 };
if (pipe(l_pipe) != 0 || pipe(r_pipe) != 0)
err_exit("%d: Failed to create pipes\n", (int)getpid());
fprintf(stderr, "%d: forking (l_size = %zu, r_size = %zu)\n",
(int)getpid(), l_size, r_size);
l_pid = fork();
if (l_pid < 0)
err_exit("%d: Failed to fork() left child\n", (int)getpid());
else if (l_pid == 0)
{
dup2(l_pipe[1], STDOUT_FILENO);
close(l_pipe[0]);
close(l_pipe[1]);
close(r_pipe[0]);
close(r_pipe[1]);
fprintf(stderr, "%d: left execing with string <<%s>>\n", (int)getpid(), left);
execl(arg0, arg0, left, "y", NULL);
err_exit("%d: failed to start recursion LEFT\n", (int)getpid());
}
else if ((r_pid = fork()) < 0)
err_exit("%d: Failed to fork() right child\n", (int)getpid());
else if (r_pid == 0)
{
dup2(r_pipe[1], STDOUT_FILENO);
close(l_pipe[0]);
close(l_pipe[1]);
close(r_pipe[0]);
close(r_pipe[1]);
fprintf(stderr, "%d: right execing with string <<%s>>\n", (int)getpid(), right);
execl(arg0, arg0, right, "y", NULL);
err_exit("%d: failed to start recursion RIGHT\n", (int)getpid());
}
else
{
/* Parent process */
int nbytes;
close(r_pipe[1]);
close(l_pipe[1]);
if ((nbytes = read(l_pipe[0], l_data, sizeof(int)*2)) != sizeof(int)*2)
err_exit("%d: Read left pipe failed (%d)\n", (int)getpid(), nbytes);
if ((nbytes = read(r_pipe[0], r_data, sizeof(int)*2)) != sizeof(int)*2)
err_exit("%d: Read right pipe failed (%d)\n", (int)getpid(), nbytes);
close(l_pipe[0]);
close(r_pipe[0]);
}
int zeroes = l_data[0] + r_data[0];
int ones = l_data[1] + r_data[1];
if (argc == 3)
{
int data[2] = { zeroes, ones };
if (write(STDOUT_FILENO, data, sizeof(data)) != sizeof(data))
err_exit("%d: failed to read binary data from stdin\n", (int)getpid());
fprintf(stderr, "%d: binary write to stdout OK\n", (int)getpid());
}
fprintf(stderr, "%d: 0's = %d, 1's = %d\n", (int)getpid(), zeroes, ones);
return 0;
}
static void err_exit(char const *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "%d: %s\n", errnum, strerror(errnum));
exit(1);
}
Given an input file:
0101010111111
the output is:
11070: data <<0101010111111>>
11070: forking (l_size = 6, r_size = 7)
11073: right execing with string <<0111111>>
11072: left execing with string <<010101>>
11072: the string is 010101
11072: doing recursion - string too big (6)
11072: forking (l_size = 3, r_size = 3)
11073: the string is 0111111
11073: doing recursion - string too big (7)
11073: forking (l_size = 3, r_size = 4)
11074: left execing with string <<010>>
11075: right execing with string <<101>>
11076: left execing with string <<011>>
11077: right execing with string <<1111>>
11074: the string is 010
11074: doing recursion - string too big (3)
11074: forking (l_size = 1, r_size = 2)
11078: left execing with string <<0>>
11076: the string is 011
11076: doing recursion - string too big (3)
11076: forking (l_size = 1, r_size = 2)
11079: right execing with string <<10>>
11075: the string is 101
11075: doing recursion - string too big (3)
11075: forking (l_size = 1, r_size = 2)
11080: left execing with string <<0>>
11077: the string is 1111
11077: doing recursion - string too big (4)
11077: forking (l_size = 2, r_size = 2)
11082: right execing with string <<11>>
11081: left execing with string <<1>>
11083: right execing with string <<01>>
11084: left execing with string <<11>>
11085: right execing with string <<11>>
11079: the string is 10
11078: the string is 0
11079: read bits, sending back 1 ones and 1 zeroes
11078: read bits, sending back 0 ones and 1 zeroes
11074: binary write to stdout OK
11074: 0's = 2, 1's = 1
11082: the string is 11
11082: read bits, sending back 2 ones and 0 zeroes
11080: the string is 0
11080: read bits, sending back 0 ones and 1 zeroes
11076: binary write to stdout OK
11076: 0's = 1, 1's = 2
11081: the string is 1
11081: read bits, sending back 1 ones and 0 zeroes
11084: the string is 11
11084: read bits, sending back 2 ones and 0 zeroes
11083: the string is 01
11083: read bits, sending back 1 ones and 1 zeroes
11075: binary write to stdout OK
11075: 0's = 1, 1's = 2
11072: binary write to stdout OK
11072: 0's = 3, 1's = 3
11085: the string is 11
11085: read bits, sending back 2 ones and 0 zeroes
11077: binary write to stdout OK
11077: 0's = 0, 1's = 4
11073: binary write to stdout OK
11073: 0's = 1, 1's = 6
11070: 0's = 4, 1's = 9
Here's my code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <readline/readline.h>
#define NUMPIPES 2
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
pid_t pid;
pipe(fdPipe);
while(1) {
bBuffer = readline("Shell> ");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
sPtr = bBuffer;
pCount = -1;
do {
aPtr = strsep(&sPtr, "|");
pipeComms[++pCount] = aPtr;
} while(aPtr);
for(i = 0; i < pCount; i++) {
aCount = -1;
do {
aPtr = strsep(&pipeComms[i], " ");
cmdArgs[++aCount] = aPtr;
} while(aPtr);
cmdArgs[aCount] = 0;
if(strlen(cmdArgs[0]) > 0) {
pid = fork();
if(pid == 0) {
if(i == 0) {
close(fdPipe[0]);
dup2(fdPipe[1], STDOUT_FILENO);
close(fdPipe[1]);
} else if(i == 1) {
close(fdPipe[1]);
dup2(fdPipe[0], STDIN_FILENO);
close(fdPipe[0]);
}
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
lPids[i] = pid;
/*waitpid(pid, &status, 0);
if(WIFEXITED(status)) {
printf("[%d] TERMINATED (Status: %d)\n",
pid, WEXITSTATUS(status));
}*/
}
}
}
for(i = 0; i < pCount; i++) {
waitpid(lPids[i], &status, 0);
if(WIFEXITED(status)) {
printf("[%d] TERMINATED (Status: %d)\n",
lPids[i], WEXITSTATUS(status));
}
}
}
return 0;
}
(The code was updated to reflect he changes proposed by two answers below, it still doesn't work as it should...)
Here's the test case where this fails:
nazgulled ~/Projects/SO/G08 $ ls -l
total 8
-rwxr-xr-x 1 nazgulled nazgulled 7181 2009-05-27 17:44 a.out
-rwxr-xr-x 1 nazgulled nazgulled 754 2009-05-27 01:42 data.h
-rwxr-xr-x 1 nazgulled nazgulled 1305 2009-05-27 17:50 main.c
-rwxr-xr-x 1 nazgulled nazgulled 320 2009-05-27 01:42 makefile
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled 9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
-rwxr-xr-x 1 nazgulled nazgulled 16 2009-05-27 17:19 test
nazgulled ~/Projects/SO/G08 $ ./a.out
Shell> ls -l|grep prog
[4804] TERMINATED (Status: 0)
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled 9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
The problem is that I should return to my shell after that, I should see "Shell> " waiting for more input. You can also notice that you don't see a message similar to "[4804] TERMINATED (Status: 0)" (but with a different pid), which means the second process didn't terminate.
I think it has something to do with grep, because this works:
nazgulled ~/Projects/SO/G08 $ ./a.out
Shell> echo q|sudo fdisk /dev/sda
[4838] TERMINATED (Status: 0)
The number of cylinders for this disk is set to 1305.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
(e.g., DOS FDISK, OS/2 FDISK)
Command (m for help):
[4839] TERMINATED (Status: 0)
You can easily see two "terminate" messages...
So, what's wrong with my code?
Even after the first command of your pipeline exits (and thust closes stdout=~fdPipe[1]), the parent still has fdPipe[1] open.
Thus, the second command of the pipeline has a stdin=~fdPipe[0] that never gets an EOF, because the other endpoint of the pipe is still open.
You need to create a new pipe(fdPipe) for each |, and make sure to close both endpoints in the parent; i.e.
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
if there are multiple cmds
close(old_fds[0])
close(old_fds[1])
Also, to be safer, you should handle the case of fdPipe and {STDIN_FILENO,STDOUT_FILENO} overlapping before performing any of the close and dup2 operations. This may happen if somebody has managed to start your shell with stdin or stdout closed, and will result in great confusion with the code here.
Edit
fdPipe1 fdPipe3
v v
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
^ ^
fdPipe2 fdPipe4
In addition to making sure you close the pipe's endpoints in the parent, I was trying to make the point that fdPipe1, fdPipe2, etc. cannot be the same pipe().
/* suppose stdin and stdout have been closed...
* for example, if your program was started with "./a.out <&- >&-" */
close(0), close(1);
/* then the result you get back from pipe() is {0, 1} or {1, 0}, since
* fd numbers are always allocated from the lowest available */
pipe(fdPipe);
close(0);
dup2(fdPipe[0], 0);
I know you don't use close(0) in your present code, but the last paragraph is warning you to watch out for this case.
Edit
The following minimal change to your code makes it work in the specific failing case you mentioned:
## -12,6 +12,4 ##
pid_t pid;
- pipe(fdPipe);
-
while(1) {
bBuffer = readline("Shell> ");
## -29,4 +27,6 ##
} while(aPtr);
+ pipe(fdPipe);
+
for(i = 0; i < pCount; i++) {
aCount = -1;
## -72,4 +72,7 ##
}
+ close(fdPipe[0]);
+ close(fdPipe[1]);
+
for(i = 0; i < pCount; i++) {
waitpid(lPids[i], &status, 0);
This won't work for more than one command in the pipeline; for that, you'd need something like this: (untested, as you have to fix other things as well)
## -9,9 +9,7 ##
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
- int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
+ int fdPipe[2], fdPipe2[2], pCount, aCount, i, status, lPids[NUMPIPES];
pid_t pid;
- pipe(fdPipe);
-
while(1) {
bBuffer = readline("Shell> ");
## -32,4 +30,7 ##
aCount = -1;
+ if (i + 1 < pCount)
+ pipe(fdPipe2);
+
do {
aPtr = strsep(&pipeComms[i], " ");
## -43,11 +44,12 ##
if(pid == 0) {
- if(i == 0) {
- close(fdPipe[0]);
+ if(i + 1 < pCount) {
+ close(fdPipe2[0]);
- dup2(fdPipe[1], STDOUT_FILENO);
+ dup2(fdPipe2[1], STDOUT_FILENO);
- close(fdPipe[1]);
- } else if(i == 1) {
+ close(fdPipe2[1]);
+ }
+ if(i != 0) {
close(fdPipe[1]);
## -70,4 +72,17 ##
}
}
+
+ if (i != 0) {
+ close(fdPipe[0]);
+ close(fdPipe[1]);
+ }
+
+ fdPipe[0] = fdPipe2[0];
+ fdPipe[1] = fdPipe2[1];
+ }
+
+ if (pCount) {
+ close(fdPipe[0]);
+ close(fdPipe[1]);
}
You should have an error exit after execvp() - it will fail sometime.
exit(EXIT_FAILURE);
As #uncleo points out, the argument list must have a null pointer to indicate the end:
cmdArgs[aCount] = 0;
It is not clear to me that you let both programs run free - it appears that you require the first program in the pipeline to finish before starting the second, which is not a recipe for success if the first program blocks because the pipe is full.
Jonathan has the right idea. You rely on the first process to fork all the others. Each one has to run to completion before the next one is forked.
Instead, fork the processes in a loop like you are doing, but wait for them outside the inner loop, (at the bottom of the big loop for the shell prompt).
loop //for prompt
next prompt
loop //to fork tasks, store the pids
if pid == 0 run command
else store the pid
end loop
loop // on pids
wait
end loop
end loop
I think your forked processes will continue executing.
Try either:
Changing it to 'return execvp'
Add 'exit(1);' after execvp
One potential problem is that cmdargs may have garbage at the end of it. You're supposed to terminate that array with a null pointer before passing it to execvp().
It looks like grep is accepting STDIN, though, so that might not be causing any problems (yet).
the file descriptors from the pipe are reference counted, and incremented with each fork. for every fork, you have to issue a close on both descriptors in order to reduce the reference count to zero and allow the pipe to close. I'm guessing.