Related
I am creating a Linux type shell program for a school project. So far I have implemented the basic Linux external commands like "ls","ps", etc., using execvp and basic pipes. As part of the project, the user can either run the program in interactive mode or batch mode. In interactive mode the user just enters a command when prompted. For batch mode, the user specifies a file in the command line where there is a list of commands to execute.
The problem I am having is in batch mode. In batch mode, if an invalid command is listed (e.g. "kdfg", for which "kdfg: command not found" while be the output), everything afterward continues, but everything afterward is executed twice. so if I have a "kghd" on one line and the next line is "ls", then the "ls" command will be executed twice. I've literally been looking at my code for hours and have tried a bunch of stuff, but to no avail.
My code is displayed below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include<sys/wait.h>
#include<unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
char* InputString();
char** GetArgs(char* com);
void Free(char** args);
char** GetCommands(char** line);
void PipedCommands(char* line);
int batch = 0; //Acts as bool for if there is a batch file given at command line
FILE* bFile; //This is just to make a quick tweek to the file if necssary to prevent any undefined behavior and keep track of where we are in the fil.
int b_fd;
int stdin_cpy;
int main(int argc, char** argv)
{
pid_t pid;
int status;
int fd;
int exitCom = 0; //acts as bool to check if an exit command was given.
char* line;
if(argc > 1) //check if batch file was given.
{
/*
if(freopen(argv[1], "r", stdin) == NULL) //I added this in case the file isn't found
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
*/
//The following is to append a newline at the end of the file (if there isn't one).
//For some reaosn, there seems to be some undefined behavior if the input file isn't
//stricitly ended with a newline.
bFile = fopen(argv[1], "r+"); //open for reading and updating
if(bFile == NULL)
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
fseek(bFile, -1, SEEK_END); //go to last character of file
if(fgetc(bFile) != '\n') //If last character is not a newline, append a newline to the file.
{
fprintf(bFile, "\n");
}
fclose(bFile); //close the file.
bFile = fopen(argv[1], "r"); //open file to keep track of when it ends
b_fd = open(argv[1], O_RDONLY); //open file again (with file descriptor this time) to duplicate it to stdin
stdin_cpy = dup(fileno(stdin)); //keep track of stdin file.
dup2(b_fd, 0); //duplicate to stdin so program takes input from bFile
close(b_fd);
batch = 1;
}
//int i=0; //this was used for debugging purposes
while(1)
{
printf("\n");
char** coms = GetCommands(&line);
for(int i=0; coms[i] != NULL; ++i) //loop goes through each command returned from GetCommands(...)
{
//fork and wait.
pid = fork();
wait(&status);
if(pid == 0)
{
int pipedCommand = 0;
//printf("\ncoms[%d]: %s\n", i, coms[i]);
for(int j=0; j<strlen(coms[i]); ++j)
{
if(coms[i][j] == '|')
{
pipedCommand = 1;
break;
}
}
if(pipedCommand == 1)
{
PipedCommands(coms[i]);
exit(1);
}
char** args = GetArgs(coms[i]);
//printf("\nargs[0]: %s\n", args[0]);
if(strcmp(args[0],"exit") == 0)
{
exit(5); //if exit command was given, exit status will be 5 (I just used 5 becuse I felt like it).
}
//printf("\nNo exit\n");
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(1); //Normal exit exits with 1 or 0.
}
//Parent continues after child exits
else if(pid > 0)
{
//check exit status of child
if(WEXITSTATUS(status) == 5)
exitCom = 1; //set bool exitCom to 1 (true), indicating that the exit command was given
}
}
if(pid > 0)
{
free(line);
free(coms);
//Now that all commands in the line were executed, check exitCom and if it is 1 (exit command was given), the shell can now exit.
if(exitCom == 1)
{
printf("\n");
exit(0);
}
}
/*
if(i >= 5)
{
printf("\nFORCED EXIT\n"); //this was used for debugging purposes
exit(1);
}
++i;
*/
}
return 0;
}
char* InputString()
{
int len = 20;
char* str = (char*)malloc(sizeof(char)*len);
char* buff;
unsigned int i=0;
if(str != NULL)
{
int c = EOF;
//printf("%c", fgetc(bFile));
while( ((c = getchar()) != '\n') && (c != EOF) )
{
/*
//printf("%c", fgetc(bFile));
//fgetc(bFile);
if(feof(bFile))
{
printf("\n\nEnd of the line\n\n");
}
*/
str[i++] = (char)c;
if(i == len)
{
len = len*2;
str = (char*)realloc(str,sizeof(char)*len);
}
}
str[i] = '\0';
buff = (char*)malloc(i);
}
if(batch == 1)
{
if(fgets(buff, i, bFile) == NULL) //Once the end of file has been reached
{
dup2(stdin_cpy, 0); //revert input back to original stdin file so user can now enter commands interactively (this happens if exit command was not given)
close(stdin_cpy); //close stdin_copy
fclose(bFile); //close bFile as we have reached the end of it
batch = 0;
}
}
printf("\n");
return str;
}
//User enters a line of commands (1 or more). Commands are separated with a ';' being the delimeter.
char** GetCommands(char** line)
{
char** coms = (char**)malloc(sizeof(char*));
char delim[] = ";";
if(batch == 0)
printf("prompt> ");
fflush(stdout);
*line = InputString();
if(batch == 1)
printf("%s\n", *line);
strcat(*line, ";");
int i=0;
coms[i] = strtok(*line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
//printf("\ni: %d\n", i);
}
return coms;
}
//A command obtained from GetCommands(...) is separated into various arguments with a space, ' ', being the delimiter.
char** GetArgs(char* com)
{
char** args = (char**)malloc(sizeof(char*));
char delim[] = " ";
//printf("\nline: %s\n", line);
int i=0;
args[i] = strtok(com, delim);
while(args[i] != NULL)
{
++i;
args = (char**)realloc(args, sizeof(char*) * (i+1));
args[i] = strtok(NULL, delim);
}
return args;
}
void PipedCommands(char* line)
{
char** coms = (char**)malloc(sizeof(char*));
int numComs;
char delim[] = "|";
int i=0;
coms[i] = strtok(line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
}
numComs = i;
int fd[2];
pid_t pid;
int status;
int prev_p = 0;
// printf("\nnumComs: %d\n", numComs);
for(int i=0; i<numComs; ++i)
{
//printf("\ni: %d\n", i);
pipe(fd);
pid = fork();
wait(&status);
if(pid == 0)
{
//printf("\nChild\n");
if(i < numComs-1)
{
//printf("\ni < numComs-1\n");
//printf("%s", coms[i]);
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2\n");
char** args = GetArgs(coms[i]);
//printf("\nexecvp in if\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
dup2(fd[1], 1);
close(fd[1]);
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
else
{
//printf("\nelse\n");
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2 in else\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
//close(fd[0]);
close(fd[1]);
char** args = GetArgs(coms[i]);
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
}
close(prev_p);
close(fd[1]);
prev_p = fd[0];
if(WEXITSTATUS(status) == 3)
{
close(fd[0]);
close(prev_p);
close(fd[1]);
return;
}
}
close(fd[0]);
close(prev_p);
close(fd[1]);
}
You can probably ignore the PipedCommands(...) function as I do not think the problem lies there.
Below is a simple batch file:
kldfg
whoami
Below is the output using the above batch file
kldfg
kldfg: command not found
whoami
jco0100
whoami
jco0100
The whoami command should only execute once, but it appears to execute twice. After that, the program reverts to interactive mode as it should and everything runs fine from there. Does anyone have any idea why this is happening. This only happens when an unknown command is entered. If all commands in the batch file are valid, nothing is outputted twice. It's only for batch files that have an unknown command that all commands after the unknown one are outputted twice.
Here's another example:
Batch file:
date
kldfg
whoami; ls | wc -l
date | wc -c
Output:
date
Tue Apr 13 19:43:19 CDT 2021
kldfg
kldfg: command not found
whoami; ls | wc -l
jco0100
34
date | wc -c
29
whoami; ls | wc -l
jco0100
34
date | wc -c
29
I got it working by disconnecting stdin on the child process before running the command:
...
freopen("/dev/null", "r", stdin); // disconnect
execcvp(args[0], args);
...
From this link: If I fork() and then do an execv(), who owns the console?
I had a crack at debugging it. To get you started on that road:
Compile your C program with debugging symbols:
$ gcc --debug your-program.c
Now debug your C program:
$ gdb a.out
This start a gdb interactive shell.
In gdb itself:
(gdb) list
(gdb) set follow-fork-mode parent
(gdb) breakpoint 69
list your code
tell the debugger to follow the parent when fork()
set a breakpoint at line 69
Run the program:
(gdb) run batch.txt
It will pause at line 69. Execute next line:
(gdb) next
Print a variable:
(gdb) print *coms
Continue running:
(gdb) continue
I leave the rest for you to explore.
I'm still not sure what's wrong with it. Something strange happens in InputString() after your fork fails with an unknown command. InputString() begins returning duplicates from getchar().
I didn't even know you could do that with stdin. Maybe just read from the file in a normal fashion, rather than clobbering stdin, and see if the problem goes away.
Don't write linux code much, I am trying to do more of that.
I took the fork and wait commands out so I could build it in mingw64 (because they arent supported in windows builds) and can't seem to reproduce the issue.
So I think the issue is in the multi-threading setup you have going there.
The "pid" variable is shared between every fork. which means when the fork command is called "pid" is set to whatever the last fork in the loop returned.
It looks like you are using an if statement checking the "pid" variable to see if this thread can execute the command. But wouldn't the main thread keep running right through that?
I don't know what fork() returns but "pid" is uninitialized, don't know if that matters.
Maybe this helps?
I am trying to write a simple shell in C which takes in a command and uses a child process to execute that command. For example, if I input:
ps -ael
my child process should execute that command along with its arguments. I print out my commands as they are stored in an array. This what I see:
Array[0] = ps
Array[1] = -ael
Array[2] = NULL
When I execute I get this:
error: unsupported SysV option
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
My code below.
int main(void)
{
char *args[MAX_LINE/2 +1]; // command line arguments
char *cmdLine;
int should_run = 1; // flag to determine when to exit the program
int i, x;
printf("osh> ");
fflush(stdout);
fgets(cmdLine, MAX_LINE, stdin);
char *token = strtok(cmdLine, " ");
int position = 0;
while (token != NULL)
{
args[position++] = token;
token = strtok(NULL, " ");
}
i = 0;
while (args[i] != NULL)
{
printf("Array[%d] = %s\n", i, args[i]);
i++;
}
if (args[i] == NULL) printf("Array[%d] = NULL", i);
x = 0;
pid_t pid;
/* fork a child process*/
pid = fork();
if (pid < 0)
{
/*Error occured*/
fprintf(stderr, "Fork failed.");
return 1;
}
else if (pid == 0)
{
/*child process*/
execvp(args[0], args); //error here
}
else
{
/*Parent process*/
wait(NULL);
printf("\nChild complete\n");
}
}
The string returned by fgets() includes the newline, but you're not removing it from the string. So you're setting args[1] to "-ael\n", and \n is not a valid option.
Include newline in your strtok() delimiters:
char *token = strtok(cmdLine, " \n");
int position = 0;
while (token != NULL)
{
args[position++] = token;
token = strtok(NULL, " \n");
}
and then it will not be included in the tokens.
You should have been able to see this in your output, I'll bet it printed:
Array[0] = ps
Array[1] = -ael
Array[2] = NULL
with a blank line there.
BTW, I don't see where you're ever setting the last argument to NULL. The while loop stops when strtok() returns NULL, so it never assigns that result to args[position++]. You need to add:
args[position] = NULL;
after the loop.
And there's no need for if (args[i] == NULL) -- the loop before that stops when that condition is met, so it's guaranteed to be true.
I am writing a C program to emulate a simple shell. This shell will basically evaluate commands like any other shell (ls, cat, etc.), as well as handle pipelining and redirection.
Currently, I am trying to start out by getting user input, tokenizing it, and executing the command provided (e.g. executing only "ls" and not "ls -l"). However, I am having a lot of difficulty with the forking. It seems that every time I fork, something goes wrong and hundreds of identical processes are created, leading to my computer freezing and me having to restart. The code appears to be correct, but I have no idea what is causing this behaviour. Below is the relevant portion of my code (main method and input tokenizer method).
int main() {
char inputLine[512]; //user input
char *args[10]; //arguments
char* pathVar = "/bin/";//path for argument
char programPath[512]; //pathVar + args[0]
int n; //count variable
//loop
while (1) {
//print prompt, get input
printf("input> ");
fgets(inputLine, 512, stdin);
n = tokenizer(inputLine, args);
//fork process
pid_t pid = fork();
if (pid != 0) { //if parent
wait(NULL);
} else { //if child
//format input for execution
strcpy(programPath, pathVar);
strcat(programPath, args[0]);
//execute user command
int returnVal = execv(programPath, args);
}
}
return 0;
}
int tokenizer(char *input, char *args[]) {
char *line; //current line
int i = 0; //count variable
line = input;
args[i] = strtok(line, " ");
do {
i++;
line = NULL;
args[i] = strtok(line, " ");
} while (args[i] != NULL);
return i;
}
Putting it all together:
You need to check fork and execv for failure.
You should exit after an execv failure (and perhaps after a fork failure).
And you need to add \n to the strtok delimiters (or remove the newline from the input line in some other way).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 10
#define PATH "/bin/"
int main() {
char inputLine[BUFSIZ];
char *args[MAXARGS];
char programPath[BUFSIZ + sizeof(PATH) + 10];
while (1) {
printf(":-> ");
if (fgets(inputLine, BUFSIZ, stdin) == NULL) /* ctrl-D entered */
break;
tokenize(inputLine, args);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid != 0) { /* parent */
wait(NULL);
} else { /* child */
strcpy(programPath, PATH);
strcat(programPath, args[0]);
execv(programPath, args); /* will not return unless it fails */
perror("execv");
exit(EXIT_FAILURE);
}
}
return 0;
}
int tokenize(char *input, char *args[]) {
int i = 0;
args[0] = strtok(input, " \n");
for (i = 0; args[i] && i < MAXARGS-1; ++i)
args[++i] = strtok(NULL, " \n");
return i;
}
You should check that execv doesn't fail and also be sure to exit() at the end of the child block.
//execute user command
int returnVal = execv(programPath, args);
// check return from execv
if (returnVal < 0) {
perror("execv");
exit(1);
}
Also, beware using functions like strcpy in this context since they may lead to buffer overflows. If an untrusted attacker type is talking to your shell this type of security issue could let them break out of the "sandbox".
I'm working on a mini shell for a college assignment. We have to read in the command, find the binary to execute from the path var, and execute command, both with and without pipes. I have everything working (I think) except for the pipe.
Through web searches I've been able to build a test program that use two hard coded commands and pipes one to the other, with the expected results. Now when I copy and paste that code into my actual program, the first command outputs fine (actually outputs the command as if there were no pipe), while the second I don't think actually does anything (the output from the first is not piped through to the second).
Here is the entire code:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFSIZE 1024
#define MAXWORDS 17
#define MAXCHAR 64
static char *path;
extern char **environ;
//split cmd "string" on pipe (|) symbol
void split(char **pipe, char **left, char **right, int n)
{
int i, x;
for(i = 0; i < n; i++)
{
if (strchr(&pipe[i][0], '|') != 0)
{
for(x = 0; x < i; x++)
strcpy(left[x], pipe[x]);
left[x++] = 0;
break;
}
}
i++;
for(x = 0; i < n; x++)
strcpy(right[x], pipe[i++]);
right[x++] = 0;
}
//Find directory where cmd can be executed from (PATH or direct access)
char *finddir(char *s)
{
char *pp;
char *pf;
int ok;
strcpy(path, getenv("PATH"));
pp = strtok(path, ":");
while (pp != NULL)
{
pf = (char *)malloc(strlen(pp) + strlen(s) + 2);
if (pf == NULL)
{
fprintf(stderr, "Out of memory in finddir\n");
return NULL;
}
strcpy(pf,pp);
strcat(pf,"/");
strcat(pf,s);
ok = !access(pf, X_OK);
free(pf);
if (ok)
return pp;
pp = strtok(NULL, ":");
}
return NULL;
}
int cmdcheck(char *cmd, char *p)
{
char *dir;
if (strchr(p, '/') != NULL)
sprintf(cmd, "%s\0", p);
else
{
dir = finddir(p);
if (dir == NULL)
return 1;
else
sprintf(cmd, "%s/%s\0", dir, p);
}
return 0;
}
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
dup(pfd[0]);
close(pfd[1]); //the child does not need this end of the pipe
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
dup(pfd[1]);
close(pfd[0]); //the parent does not need this end of the pipe
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
int main(void)
{
int status; //read status when reading cmd in
char ch; //character currently reading
int n, i, x; //(n) count of chars read; (i) cmd args iter; (x) cmd arg iter in cmd array
char buffer[BUFFSIZE]; //read buffer
char *token; //token var when splitting buffer
int pid0, pid1, pid2; //return ID from fork call
int which; //return value from wait (child pID that just ended)
char msg[100]; //messages to print out
char *cmd1, *cmd2; //cmds when piping
char *params[MAXWORDS]; //cmd parameters to send to execve
int fd[2]; //pipe file descriptors
char *pparam1[MAXWORDS]; //cmd "string" on left side of pipe
char *pparam2[MAXWORDS]; //cmd on right side of pipe
for(;;)
{
for (i = 0; i < MAXWORDS; i++)
params[i] = malloc(MAXCHAR);
n = 0;
write(1, "# ", 2);
for(;;)
{
status = read(0, &ch, 1);
if (status == 0)
return 0; //End of file
if (status == -1)
return 1; //Error
if(n == BUFFSIZE)
{
write(1, "Line too long\n", 14);
return 1;
}
buffer[n++] = ch;
if(ch == '\n')
break;
}
buffer[n] = '\0';
x = 0;
token = strtok(buffer, " \t\n\0");
while(token != NULL)
{
strcpy(params[x++], token);
token = strtok(NULL, " \t\n\0");
}
params[x] = 0;
path = getenv("PATH");
if (path == NULL)
{
fprintf(stderr, "PATH environment variable not found.\n");
return 1;
}
n = strlen(path);
path = (char *)malloc(n+1);
if (path == NULL)
{
fprintf(stderr, "Unable to allocate space for copy of PATH.\n");
return 1;
}
cmd1 = malloc(MAXCHAR);
cmd2 = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam1[i] = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam2[i] = malloc(MAXCHAR);
split(params, pparam1, pparam2, x);
//Check first cmd
if(cmdcheck(cmd1, pparam1[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam1[0]);
write(1, msg, strlen(msg));
break;
}
//Check second cmd
if(cmdcheck(cmd2, pparam2[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam2[0]);
write(1, msg, strlen(msg));
break;
}
pipe(fd);
switch (pid0 = fork())
{
case 0: //Child
switch (pid1 = fork())
{
case 0: //Child
runpipe(fd, cmd1, pparam1, cmd2, pparam2);
exit(0);
default:
exit(0);
//break;
case -1: //ERROR
perror("fork-2");
exit(1);
}
default: //Parent
which = wait(&status);
if (which == -1)
{
write(1, "wait failed\n", 12);
exit(1);
}
if (status & 0xff)
sprintf(msg, "process %d terminated abnormally for reason %d\n", which, status & 0xff);
else
sprintf(msg, "process %d terminated normally with status %d\n", which, (status >> 8) & 0xff);
write(1, msg, strlen(msg));
break;
case -1: //ERROR
perror("fork-1");
exit(1);
}
free(cmd1);
free(cmd2);
for (i = 0; i < MAXWORDS; i++)
free(pparam1[i]);
for (i = 0; i < MAXWORDS; i++)
free(pparam2[i]);
free(path);
for (i = 0; i < MAXWORDS; i++)
free(params[i]);
}
return 0;
}
Typing echo one | wc -l at the prompt will only output one with the respective wait print statement following. It has been a few years since I've used C, so am I on the right track?
Thanks.
EDIT:
Here is the runpipe function as it stands now. But the only thing that is printed is the wait statement.
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
const int READ = 0;
const int WRITE = 1;
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
There are a couple of things going on there that are contributing to the unexpected behavior.
The first is that you're forking too much. If you unroll your runpipe() function call into the switch statement in main(), you'll see that you reach the great-grandchild level:
switch (pid0 = fork())
{
case 0: // Child
switch (pid1 = fork())
{
case 0: // GRAND-Child
// function call to runpipe()
switch (pid = fork())
{
case 0: // GREAT-GRAND-Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: // GRAND-Child
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
Which is not necessary. Fork once in main() and then call your runpipe() function.
Related to this issue is where you're creating your pipe. When you fork, the newly created child process inherits all of the parent process's open files (among many other things). This includes the default descriptors 0, 1, and 2 (stdin, stdout, and stderr), as well as any other open files, including the pipe you created called fd. This means that the parent, child, grandchild, and great-grandchild are all inheriting a copy of both ends of the pipe. You correctly close the unused ends inside the runpipe() function (the grandchild's and great-grandchild's copies), but the parent and child in your main() function also have copies!
Since the only pair of processes using the pipe are those created in runpipe(), you can move the declaration of fd and the call to pipe(2) into that function.
These two modifications will resolve your issues.
A completely unrelated issue that just relates to the flow of your shell is that your main() ends up doing its wait(2) on the "parent" process of the runpipe() function. Since that parent is the one running cmd1, your shell is going to return its prompt as soon as cmd1 finishes, instead of when the last command (cmd2 in this case) in the pipeline finishes. You can see the behavioral difference by running something like echo | sleep 10 into your shell and a real shell.
The dup function duplicates a file descriptor, and returns the new duplicate. However, this will not work, as stdin in the child still exists, and the new file descriptor will not be put in place of the standard input.
You must close the standard input file descriptor first, before doing dup. Or use dup2 which will close the destination file descriptor automatically first before doing the duplication:
dup2(pfd[0], STDIN_FILENO);
I have an assignment to create a Linux shell in C. Currently, I am stuck on implementing redirections and pipes. The code that I have so far is below. The main() parses user's input. If the command is built in, then that command is executed. Otherwise, the tokenized input is passed to execute() (I know that I should probably pull the built-in commands into their own function).
What execute() does is loop through the array. If it encounters <, >, or | it should take appropriate action. The first thing I am trying to get to work correctly is piping. I am definitely doing something wrong, though, because I cannot get it to work for even one pipe. For example, a sample input/output:
/home/ad/Documents> ls -l | grep sh
|: sh: No such file or directory
|
My idea was to get each of the directions and piping work for just one case, and then by making the function recursive I could hopefully use multiple redirections/pipes in the same command line. For example, I could do program1 < input1.txt > output1.txt or ls -l | grep sh > output2.txt.
I was hoping that someone can point out my errors in trying to pipe and perhaps offer some pointers in how to approach the case where multiple redirections/pipes are inputted by the user.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
void execute(char **argArray){
char **pA = argArray;
int i = 0;
while(*pA != NULL) {
if(strcmp(argArray[i],"<") == 0) {
printf("<\n");
}
else if(strcmp(argArray[i],">") == 0) {
printf(">\n");
}
else if(strcmp(argArray[i],"|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
else {
if(pid == 0){
execvp(argArray[0], argArray);
printf("Command not found.\n");
}
else
wait(NULL);*/
}
*pA++;
i++;
}
}
int main () {
char path[MAX_PATH_LENGTH];
char buf[BUF_LENGTH];
char* strArray[BUF_LENGTH];
/**
* "Welcome" message. When mash is executed, the current working directory
* is displayed followed by >. For example, if user is in /usr/lib/, then
* mash will display :
* /usr/lib/>
**/
getcwd(path, MAX_PATH_LENGTH);
printf("%s> ", path);
fflush(stdout);
/**
* Loop infinitely while waiting for input from user.
* Parse input and display "welcome" message again.
**/
while(1) {
fgets(buf, BUF_LENGTH, stdin);
char *tokenPtr = NULL;
int i = 0;
tokenPtr = strtok(buf, delims);
if(strcmp(tokenPtr, "exit") == 0){
exit(0);
}
else if(strcmp(tokenPtr, "cd") == 0){
tokenPtr = strtok(NULL, delims);
if(chdir(tokenPtr) != 0){
printf("Path not found.\n");
}
getcwd(path, MAX_PATH_LENGTH);
}
else if(strcmp(tokenPtr, "pwd") == 0){
printf("%s\n", path);
}
else {
while(tokenPtr != NULL) {
strArray[i++] = tokenPtr;
tokenPtr = strtok(NULL, delims);
}
execute(strArray);
}
bzero(strArray, sizeof(strArray)); // clears array
printf("%s> ", path);
fflush(stdout);
}
}
Part of the problem is in the pipe handling code - as you suspected.
else if (strcmp(argArray[i], "|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if ((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if ((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
The first execvp() was probably intended to use argList since you've just copied some material there. However, you've copied i bytes, not i character pointers, and you've not ensured that the pipe is zapped and replaced with a null pointer.
memcpy(argList, argArray, i * sizeof(char *));
argList[i] = 0;
execvp(argList[0], argList);
Note that this has not verified that there is no buffer overflow on argList; Note that there is no space allocated for argList; if you use it, you should allocate the memory before doing the memcpy().
Alternatively, and more simply, you can do without the copy. Since you're in a child process, you can simply zap replace argArray[i] with a null pointer without affecting either the parent or the other child process:
argArray[i] = 0;
execvp(argArray[0], argArray);
You might also note that the second invocation of execvp() uses a variable pA which cannot be seen; it is almost certainly incorrectly initialized. As a moderately good rule of thumb, you should write:
execvp(array[n], &array[n]);
The invocations above don't conform to this schema, but if you follow it, you won't go far wrong.
You should also have basic error reporting and a exit(1) (or possibly _exit(1) or _Exit(1)) after each execvp() so that the child does not continue if it fails to execute. There is no successful return from execvp(), but execvp() most certainly can return.
Finally for now, these calls to execvp() should presumably be where you make your recursive call. You need to deal with pipes before trying to deal with other I/O redirection. Note that in a standard shell, you can do:
> output < input command -opts arg1 arg2
This is aconventional usage, but is actually permitted.
One good thing - you have ensured that the original file descriptors from pipe() are closed in all three processes (parent and both children). This is a common mistake which you have avoided making; well done.