I am writing a program to implement a simple shell in C
but when I execute the program at the end when I type exit it does not exit.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#define BUFFER_LEN 1024
int main()
{
char line[BUFFER_LEN]; //get command line
char *argv[100]; //user command
char *path = "/bin/"; //set path at bin
char progpath[20]; //full file path
int argc; //arg count
while (1) {
printf("My shell>> "); //print shell prompt
if (!fgets(line, BUFFER_LEN, stdin)) { //get command and put it in line
size_t length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
break; //if user hits CTRL+D break
}
if (strcmp(line, "exit") == 0) { //check if command is exit
exit(0);
break;
}
char *token; //split command into separate strings
token = strtok(line, " ");
int i = 0;
while (token != NULL) {
argv[i] = token;
token = strtok(NULL, " ");
i++;
}
argv[i] = NULL; //set last value to NULL for execvp
argc = i; //get arg count
for (i = 0; i < argc; i++) {
printf("%s\n", argv[i]); //print command/args
}
strcpy(progpath, path); //copy /bin/ to file path
strcat(progpath, argv[0]); //add program to path
for (i = 0; i < strlen(progpath); i++) { //delete newline
if (progpath[i] == '\n') {
progpath[i] = '\0';
}
}
int pid = fork(); //fork child
if (pid == 0) { //Child
execvp(progpath, argv);
fprintf(stderr, "Child process could not do execvp\n");
} else { //Parent
wait(NULL);
printf("Child exited\n");
}
}
}
I am executing in on cygwin hence a.exe is generated.
execution of program
$ ./a.exe
My shell>> ls
ls
a.exe p3.4.cpp p3.6.cpp p3.6.cpp~ p3.7.cpp p3.7.cpp~ p3.cpp shell.c shell.c~ test.txt
Child exited
My shell>> cat test.txt
cat.txt
Child process could not do execvp
My shell>> exit
exit
Child process could not do execvp
My shell>> exit()
exit()
Child process could not do execvp
My shell>> exit
exit
Child process could not do execvp
My shell>> exit
exit
Here you can see in the above output the program is not able to exit when I am typing an exit on the command prompt. What is the mistake I am doing in above program?
You probably mixed removing the new line with the handling of CTRL-D. Your code
if (!fgets(line, BUFFER_LEN, stdin)) { //get command and put it in line
size_t length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
break; //if user hits CTRL+D break
}
removed the new line only if fgets fails, e.g. end of file has been reached.
Also, line[length-1] will yield undefined behaviour if length==0.
I'd replace the logic:
if (!fgets(line, BUFFER_LEN, stdin)) { //get command and put it in line
break; //if user hits CTRL+D break
} else {
line[strcspn(line, "\n")] = '\0';
}
strcmp(line, "exit\n")==0
What happens when you do not include the newline (\n) ?
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 have to develop a simple shell in C using system calls fork()/execvp(). So far my code takes in a command, splits it up using strtok into an array argv and then I call fork to create a child and execute the command. Im working on this in ubuntu where most of the commands are in the /bin/ directory, so I append the program name (for example /bin/ls) and use that for the first arg of execvp and then I give it the argv array. My program works if I type in the command "ls", but when trying other commands or even "ls -l" I'm getting an "ls: invalid option." Im not sure what I'm doing wrong here.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_LEN 1024
int main(){
char line[BUFFER_LEN]; //get command line
char* argv[100]; //user command
char* path= "/bin/"; //set path at bin
char progpath[20]; //full file path
int argc; //arg count
while(1){
printf("My shell>> "); //print shell prompt
if(!fgets(line, BUFFER_LEN, stdin)){ //get command and put it in line
break; //if user hits CTRL+D break
}
if(strcmp(line, "exit\n")==0){ //check if command is exit
break;
}
char *token; //split command into separate strings
token = strtok(line," ");
int i=0;
while(token!=NULL){
argv[i]=token;
token = strtok(NULL," ");
i++;
}
argv[i]=NULL; //set last value to NULL for execvp
argc=i; //get arg count
for(i=0; i<argc; i++){
printf("%s\n", argv[i]); //print command/args
}
strcpy(progpath, path); //copy /bin/ to file path
strcat(progpath, argv[0]); //add program to path
for(i=0; i<strlen(progpath); i++){ //delete newline
if(progpath[i]=='\n'){
progpath[i]='\0';
}
}
int pid= fork(); //fork child
if(pid==0){ //Child
execvp(progpath,argv);
fprintf(stderr, "Child process could not do execvp\n");
}else{ //Parent
wait(NULL);
printf("Child exited\n");
}
}
}
The invalid option is because fgets() keeps the '\n' when you press enter, try this
if(!fgets(line, BUFFER_LEN, stdin))
break;
size_t length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
when you are trying to call ls -l you are passing "-l\n" as the option, hence the message.
You will have to change
strcmp(line, "exit\n")
to
strcmp(line, "exit")
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUFFER_LEN 1024
int main(){
char user_input[BUFFER_LEN]; //get command line
char* argv[120]; //user command
int argc ; //argument count
char* path= "/bin/"; //set path at bin
char file_path[50];//full file path
while(1){
printf("Simple Shell>> "); // Greeting shell during startup
if(!fgets(user_input,BUFFER_LEN, stdin)){
break; //break if the command length exceed the defined BUFFER_LEN
}
size_t length = strlen(user_input);
if(length == 0){
break;
}
if (user_input[length - 1] == '\n'){
user_input[length - 1] = '\0'; // replace last char by '\0' if it is new line char
}
//split command using spaces
char *token;
token = strtok(user_input," ");
int argc=0;
if(token == NULL){
continue;
}
while(token!=NULL){
argv[argc]=token;
token = strtok(NULL," ");
argc++;
}
argv[argc]=NULL;
strcpy(file_path, path); //Assign path to file_path
strcat(file_path, argv[0]); //conctanate command and file path
if (access(file_path,F_OK)==0){ //check the command is available in /bin
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) { //child process
if (execvp(file_path,argv) == -1) {
perror("Child proccess end");
}
exit(EXIT_FAILURE);
}
else if (pid > 0) { // parent process
wpid = waitpid(pid, &status, WUNTRACED);
while (!WIFEXITED(status) && !WIFSIGNALED(status)){
wpid = waitpid(pid, &status, WUNTRACED);
}
}
else {
perror("Fork Failed"); //process id can not be null
}
}
else {
printf("Command is not available in the bin\n"); //Command is not available in the bin
}
}
}
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've been working on creating my own Unix Shell in C to get practice with its interworkings...I'm having some issues getting my process to run in the background while allowing my shell to continue taking user input. If you could take the time to dissect what I've got below it would be much appreciated!
My variables are below, just incase that helps understand things more...
#define TRUE 1
static char user_input = '\0';
static char *cmd_argv[5]; // array of strings of command
static int cmd_argc = 0; // # words of command
static char buffer[50]; // input line buffer
static int buffer_characters = 0;
int jobs_list_size = 0;
/* int pid; */
int status;
int jobs_list[50];
Here is my main function.
int main(int argc, char **argv)
{
printf("[MYSHELL] $ ");
while (TRUE) {
user_input = getchar();
switch (user_input) {
case EOF:
exit(-1);
case '\n':
printf("[MYSHELL] $ ");
break;
default:
// parse input into cmd_argv - store # commands in cmd_argc
parse_input();
//check for zombie processes
check_zombies();
if(handle_commands() == 0)
create_process();
printf("\n[MYSHELL] $ ");
}
}
printf("\n[MYSHELL] $ ");
return 0;
}
Parse Input...I know, I can't get readline to work on this box :(
If provided the & operator, create the job in the background... (see below)
void parse_input()
{
// clears command line
while (cmd_argc != 0) {
cmd_argv[cmd_argc] = NULL;
cmd_argc--;
}
buffer_characters = 0;
// get command line input
while ((user_input != '\n') && (buffer_characters < 50)) {
buffer[buffer_characters++] = user_input;
user_input = getchar();
}
// clear buffer
buffer[buffer_characters] = 0x00;
// populate cmd_argv - array of commands
char *buffer_pointer;
buffer_pointer = strtok(buffer, " ");
while (buffer_pointer != NULL) {
cmd_argv[cmd_argc] = buffer_pointer;
buffer_pointer = strtok(NULL, " ");
//check for background process execution
if(strcmp(cmd_argv[cmd_argc], "&")==0){
printf("Started job %d\n", getpid());
make_background_job();
}
cmd_argc++;
}
}
Make background job. Closes child process STDIN, opens new STDIN, and executes.
void make_background_job()
{
int pid;
pid = fork();
fclose(stdin); // close child's stdin
fopen("/dev/null", "r"); // open a new stdin that is always empty
fprintf(stderr, "Child pid = %d\n", getpid());
//add pid to jobs list
jobs_list[jobs_list_size] = getpid();
/* printf("jobs list %d", *jobs_list[jobs_list_size]); */
jobs_list_size++;
execvp(*cmd_argv,cmd_argv);
// this should never be reached, unless there is an error
fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
}
The meat of my job control. Fork spawns child, returns 0 for child and PID for parent.
void create_process()
{
pid_t pid;
pid = fork();
status = 0;
switch(pid){
case -1:
perror("[MYSHELL ] $ (fork)");
exit(EXIT_FAILURE);
case 0:
make_background_job();
printf("\n\n----Just made background job in case 0 of create_process----\n\n");
break;
default:
printf("\n\n----Default case of create_process----\n\n");
// parent process, waiting on child...
waitpid(pid, &status, 0);
if (status != 0)
fprintf (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
else
break;
}
}
My problem is when I execute a job in the background, its executing the command twice, and exiting out of the shell. (It functions correctly otherwise if no background process is enabled). Where am I getting confused? I think it may have to do with issues regarding my PID's, as I'm not populating the list correctly either in 'make_background_job'
Here is my output, the example.sh just throws out helloWorld:
[MYSHELL] $ ./example.sh &
Started job 15479
Child pid = 15479
Child pid = 15481
Hello World
Hello World
What seems to happen is
in main() the prompt is displayed, expecting a command
when a command is input, parse_input() is called
it builds the commands array until it finds & where it calls make_background_jobs()
that function forks quickly, and executes in parallel, in two processes, execvp()
execvp() replaces each of the two processes to execute the command
thus two "Hello world" appear.
The problem is in make_background_jobs() where, I think, the expected behavior was that only one of the two processes should execute the command, and the other one (father) returns, to keep the program active.
This can be solved by modifying that function, making the father process return:
void make_background_job()
{
int pid;
pid = fork();
if (pid) return; // The father process returns to keep program active
...
edit
I gave it a try, removing the unnecessary
void make_background_job()
{
int pid;
pid = fork();
if ( ! pid)
{
fclose(stdin); // close child's stdin
fopen("/dev/null", "r"); // open a new stdin that is always empty
fprintf(stderr, "Child Job pid = %d\n", getpid());
//add pid to jobs list
jobs_list[jobs_list_size] = getpid();
/* printf("jobs list %d", *jobs_list[jobs_list_size]); */
jobs_list_size++;
execvp(*cmd_argv,cmd_argv);
// this should never be reached, unless there is an error
fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
exit(1);
}
waitpid(pid, &status, 0);
}
The background job is created in another process. The father waits for the job to complete.
void parse_input()
{
// clears command line
while (cmd_argc != 0) {
cmd_argv[cmd_argc] = NULL;
cmd_argc--;
}
buffer_characters = 0;
// get command line input
while ((user_input != '\n') && (buffer_characters < 50)) {
buffer[buffer_characters++] = user_input;
user_input = getchar();
}
// clear buffer
buffer[buffer_characters] = 0x00;
// populate cmd_argv - array of commands
char *buffer_pointer;
buffer_pointer = strtok(buffer, " ");
int ok = 0;
while (buffer_pointer != NULL) {
cmd_argv[cmd_argc] = buffer_pointer;
buffer_pointer = strtok(NULL, " ");
//check for background process execution
if(strcmp(cmd_argv[cmd_argc], "&")==0){
ok = 1;
break;
}
cmd_argc++;
}
if (!ok) cmd_argv[cmd_argc = 0] = NULL; // If no & found, reset commands
}
Only parses input.
Below a new handle_commands() that return 0 if there is a command to play, and the main follows.
int handle_commands() { return cmd_argc > 0 ? 0:1; }
int main(int argc, char **argv)
{
printf("[MYSHELL] $ ");
while (TRUE) {
user_input = getchar();
switch (user_input) {
case EOF:
exit(-1);
case '\n':
printf("[MYSHELL] $ ");
break;
default:
// parse input into cmd_argv - store # commands in cmd_argc
parse_input();
//check for zombie processes
check_zombies();
if(handle_commands() == 0)
make_background_job(); // Call directly the bg job
printf("\n[MYSHELL] $ ");
}
}
printf("\n[MYSHELL] $ ");
return 0;
}
The main() calls directly make_background_job().
There is only one fork() in make_background_job. create_process() has been removed.