I have been working on this project for a while. The purpose is to make a functioning shell that can do pretty much all the shell commands (except cd). It does almost everything I want it to do, except for a couple things. The first is that when I put an '&' to signify background processing, it does it, but then doesn't print another myshell> line. I can still input something, but the myshell> never shows up, no matter where I put another cout<<"myshell> ";.
Another issue is if I press enter, making myString empty, many times, it crashes the program with a seg fault. Also after I do the '&' background processing and press enter to get the myshell> to come back up, it prints one myshell> but then seg faults on the next hit of enter. I'm sorry if I didn't explain this well, but it is really driving me crazy. Please let me know if you have any suggestions.
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <cstdio>
#include <sys/wait.h>
#include <stdio.h>
/*Function that parses the command the user inputs.
It takes myArgv and myString as inputs.
It returns the value of exitcond, which is used to see if the user wants to exit or not.
Also, this is where myString is tokenized using strok()*/
int parseCommand(char *myArgv[10], char myString[255])
{
int exitcond=0;
if((strcmp(myArgv[0], "exit") == 0)||(strcmp(myArgv[0], "quit")==0))
{
exitcond = 1;
return exitcond;
}
int i;
char *token;
token = strtok(myString," ");
i=0;
while (token != NULL)
{
myArgv[i] = token;
token = strtok(NULL," ");
i++;
}
/*
* Set the last entry our new argv to a null char
* (see man execvp to understand why).
*/
myArgv[i] = '\0';
return exitcond;
}
/*Function that gets the command from the user and sees if they want
background processing or not (presence of '&').
It takes inputs of choose and myString. choose is the variable for
whether background processing is necessary or not, while myString is
an empty character array.
It outputs the value of the choose variable for lter use.*/
int getCommand(int choose, char myString[255])
{
int i;
choose=0;
fgets(myString, 256, stdin);
if (myString[0]=='\0')
{
choose=0;
return choose;
}
for (i=0; myString[i]; i++)
{
if (myString[i]== '&')
{
choose=1;
myString[i]=' ';
}
if (myString[i] == '\n')
{
myString[i] = '\0';
}
}
return choose;
}
/*Main function where all the calling of other functions and processes
is done. This is where the user enters and exits the shell also. All
usage of fork, pid, waitpid and execvp is done here.*/
int main()
{
using namespace std;
int exitCondition=0, i=0, status;
char myString[255];
char *token, *myArgv[10];
pid_t pid, waiting;
int bg=0;
while (!exitCondition)
{
/* print a prompt and allow the user to enter a stream of characters */
cout << "myshell> ";
bg=0;
int choose=0;
bg=getCommand(choose,myString);
exitCondition=parseCommand(myArgv,myString);
if(exitCondition==1)
{
cout<<"Thank you for using my shell.\n";
}
else {
/* while (myString[0]=='\0')
{
cout<<"myshell> ";
bg=getCommand(choose,myString);
}*/
/* The user has a command, so spawn it in a child process */
pid = fork();
if (pid == -1)
{
/* to understand why this is here, see man 2 fork */
cout << "A problem arose, the shell failed to spawn a child process" << endl;
return(1);
}
else if (pid == 0)
{
// Child process
execvp(myArgv[0],myArgv);
cout << "Bad command or file name, please try again!\n" << endl;
return 0;
} else {
/* This makes sure that the spawned process is run in the foreground,
because the user did not choose background */
if(bg==0)
{
waitpid(pid,NULL,0);
}
}
}
}
return 0;
}
Okay, you had three bugs, one of which caused the segfault. Of the others, one would put a garbage argument in the array passed to execvp and the other would leak zombie processes for background jobs.
I've corrected the code and annotated it with where the bugs were along with the fixes [please pardon the gratuitous style cleanup]:
#include <cstdio>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define AVCOUNT 100
#define STRBUFLEN 2000
/*Function that parses the command the user inputs.
It takes myArgv and myString as inputs.
It returns the value of exitcond, which is used to see if the user wants to
exit or not.
Also, this is where myString is tokenized using strok()*/
int
parseCommand(char **myArgv, char *myString)
{
char *token;
char *bp;
int exitcond = 0;
int i;
// NOTE/BUG: original check for exit/quit was here -- at this point
// myArgv is undefined (hence the segfault)
// NOTE/BUG: your original loop -- at the end i was one beyond where it
// should have been so that when myArgv gets passed to execvp it would
// have an undefined value at the end
#if 0
token = strtok(myString, " ");
i = 0;
while (token != NULL) {
myArgv[i] = token;
token = strtok(NULL, " ");
i++;
}
#endif
// NOTE/BUGFIX: here is the corrected loop
i = 0;
bp = myString;
while (1) {
token = strtok(bp, " ");
bp = NULL;
if (token == NULL)
break;
myArgv[i++] = token;
}
/*
* Set the last entry our new argv to a null pointer
* (see man execvp to understand why).
*/
// NOTE/BUG: with your code, i was one too high here
myArgv[i] = NULL;
// NOTE/BUGFIX: moved exit/quit check to here now that myArgv is valid
token = myArgv[0];
if (token != NULL) {
if ((strcmp(token, "exit") == 0) || (strcmp(token, "quit") == 0))
exitcond = 1;
}
return exitcond;
}
/*Function that gets the command from the user and sees if they want
background processing or not (presence of '&').
It takes inputs of choose and myString. choose is the variable for
whether background processing is necessary or not, while myString is
an empty character array.
It outputs the value of the choose variable for lter use.*/
int
getCommand(int choose, char *myString)
{
int i;
choose = 0;
fgets(myString, STRBUFLEN, stdin);
if (myString[0] == '\0') {
choose = 0;
return choose;
}
for (i = 0; myString[i]; i++) {
if (myString[i] == '&') {
choose = 1;
myString[i] = ' ';
}
if (myString[i] == '\n') {
myString[i] = '\0';
break;
}
}
return choose;
}
/*Main function where all the calling of other functions and processes
is done. This is where the user enters and exits the shell also. All
usage of fork, pid, waitpid and execvp is done here.*/
int
main()
{
using namespace std;
int exitCondition = 0;
int status;
char myString[STRBUFLEN];
char *myArgv[AVCOUNT];
pid_t pid;
int bg = 0;
while (!exitCondition) {
// NOTE/BUGFIX: without this, any background process that completed
// would become a zombie because it was never waited for [again]
// reap any finished background jobs
while (1) {
pid = waitpid(0,&status,WNOHANG);
if (pid < 0)
break;
}
/* print a prompt and allow the user to enter a stream of characters */
cout << "myshell> ";
bg = 0;
int choose = 0;
bg = getCommand(choose, myString);
exitCondition = parseCommand(myArgv, myString);
if (exitCondition == 1) {
cout << "Thank you for using my shell.\n";
break;
}
/* while (myString[0]=='\0') { cout<<"myshell> "; bg=getCommand(choose,myString); } */
/* The user has a command, so spawn it in a child process */
pid = fork();
if (pid == -1) {
/* to understand why this is here, see man 2 fork */
cout << "A problem arose, the shell failed to spawn a child process" << endl;
return 1;
}
if (pid == 0) {
// Child process
execvp(myArgv[0], myArgv);
cout << "Bad command or file name, please try again!\n" << endl;
return 1;
}
/* This makes sure that the spawned process is run in the
foreground, because the user did not choose background */
if (bg == 0)
waitpid(pid, &status, 0);
}
return 0;
}
Related
I've been stuck on an issue with my program and just hoping for any help at this point :(
or guidance towards the right direction. In my code, I'm implenting a mini shell in c++ where the user can pipe 2 or more processes together, yet an issue keeps coming up whenever I execute it. Only the first and last commands actually execute so say I run:
cat b.txt | sort | tail -2
only cat b.txt and tail -2 would execute.
Here is my attempt at the whole program, also referenced to this which helped me tremendously with the setup.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <sys/utsname.h>
#include <unistd.h>
using namespace std;
//this variable will take in the line of input submitted by the user
char buf[1024];
//PIDs for the two child processes
pid_t pid[300];
//these will be use to check the status of each child in the parent process
int status;
int status2;
int pid_num = 1;
//initializes the pipe
int pipeA[2] = {-1,-1};
int g = 0;
void first_command(int pipeA[], char * command[], bool pipeExists){
if(pipeExists){
dup2(pipeA[1], 1);
close(pipeA[0]);
}
// this will run command[0] as the file to execute, and command as the arg
execvp(command[0], command);
printf("Can not execute FIRST command, please enter a valid command \n");
exit(127);
}
void other_command(int pipeA[], char * command0[], int index){
dup2(pipeA[0], 0);
close(pipeA[1]);
execvp(command0[0], command0);
printf("Can not execute SECOND command, please enter a valid command\n");
exit(127);
}
void main_func() {
//stay inside the loop and keep asking the user for input until the user quits the program
while (fgets(buf,1024,stdin) != NULL){
//initialize a boolean to check if user wants to pipe something, set to false by default until we check with user
bool pipeExists = false;
//initialize this arrays to NULL so anything that store in them gets cleared out.
//these arrays will hold the commands that the user wants to carry out.
char * command[1024] = {NULL, NULL, NULL};
char *command0[1024] = {NULL, NULL, NULL};
char *command1[] = {NULL, NULL, NULL};
char *command2[] = {NULL, NULL, NULL};
char *command3[] = {NULL, NULL, NULL};
char ** my_commands[] = {
command0,
command1,
command2,
command3,
NULL
};
//Important to delete mark the last byte as 0 in our input
buf[strlen(buf) -1] = 0;
//initialize this number to zero to start save the tokens at this index
int index = 0;
//a char * to hold the token saved by strtok
char * ptr;
ptr = strtok(buf, " \"");
//Loop through 'buf' and save tokens accordingly
while(ptr != NULL){
// if the user types exit at any moment, the program will exit gracefully and terminate
if(strcmp( ptr, "exit" ) == 0){
exit(0);
}
//if ptr is equal to | user wants to pipe something and we change pipeExists to true
if(strcmp( ptr, "|" ) == 0){
pipeExists = true;
index= 0;
ptr = strtok(NULL, " ");
}
//enter here while user doesnt want to user pipes
if(!pipeExists){
command[index] = ptr;
ptr = strtok(NULL, " ");
index++;
}
//enter here if user want to use pipes
if(pipeExists){
command0[index] = ptr;
ptr = strtok(NULL, " ");
index++;
}
g++;
printf("%s %i\n", ptr, g);
}
for (int s = 0; my_commands[s] != NULL; s++) {
cout << command0[s] << " \n" << endl;
}
//if pipes exists then initialize it
if(pipeExists){
pipe(pipeA);
}
//create first child
if ((pid[0] = fork()) == 0) {
//pass in the pipe, commands and pipe to function to execute
first_command(pipeA, command, pipeExists);
}
else if(pid[0] < 0){
//error with child
cerr<<"error forking first child"<<endl;
}
// if pipe exists create a second process to execute the second part of the command
if(pipeExists){
for(int f = 0; my_commands[f] != NULL; f++) {
//create second child
if ((pid[f] = fork()) == 0) {
other_command(pipeA, command0, index);
}
else if(pid[f] < 0){
//error with second child
cerr<<"error forking child "<< pid_num << endl;
}
}
pid_num++;
}
//if the pipe was created then we close its ends
if(pipeExists){
for(int z = 0; z < pid_num; z++) {
close(pipeA[z]);
}
}
//wait for the first child that ALWAYS executes
if ( (pid[0] = waitpid(pid[0], &status, 0)) < 0)
cerr<<"error waiting for first child"<<endl;
//wait for the second child but only if user wanted to created to use piping
if(pipeExists){
for(int j = 1; j < pid_num; j++) {
if ( (pid[j] = waitpid(pid[j], &status2, 0)) < 0){
printf("Status: %d", pid[j]);
cerr<<"error waiting for child " << j <<endl;
}
}
}
pid_num = 1;
}//endwhile
}
After running my program for the first time around it runs correctly but the loop does not continue.
I have tried adding more forks into my function but it seems to not work.
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main(){
int pipefd[2];
int rs;
pid_t cpid;
char* args1[256];
char* args2[256];
char cmd1[256];
char cmd2[256];
char path1[10];
char path2[10];
//starts while loop
while(true){
//creates pipe
rs = pipe(pipefd);
if (rs < 0){
perror("pipe");
exit(1);
}
//gets comands from user
cout << "Command 1";
cin.getline(cmd1,256);
cout << "command 2";
cin.getline(cmd2,256);
//checks id with commands are quit
if (strcmp(cmd1,"quit") == 0)
break;
if (strcmp(cmd2,"quit") == 0)
break;
char *token;
token = strtok(cmd1," ");
int i=0;
//splits char arrays up
while(token != NULL){
args1[i] = token;
token = strtok(NULL, " ");
i++;
}
args1[i] = NULL;
token = strtok(cmd2," ");
i = 0;
while(token != NULL){
args2[i] = token;
token = strtok(NULL, " ");
i++;
}
args2[i] = NULL;
strcpy(path1,args1[0]);//copis the command to the path file
strcpy(path2,args2[0]);
//forks and creates child process
rs = fork();
if (rs == 0){//child process
close(pipefd[1]);//close write end of pipe
close(0);//close standard input
dup(pipefd[0]);//duplicate read end of pipe into standard
input
close(pipefd[0]);//close read end of pipe
rs = execvp(path2,args2);//runs program 2
if (rs < 0){
perror("execl");
exit(1);
}
}
else{//PARENT PROCESS
close(pipefd[0]);//close read end of pipe
close(1);//close standard input
dup(pipefd[1]);//duplicate write end of pipe into standard
input
close(pipefd[1]);//clsoe write end of pipe
rs = execvp(path1,args1);//runs command 1
if (rs < 0){
perror("execl");
exit(1);
}
}
}
return 0;
}
After going through the loop the first time the user should be asked for enter in two more commands or be able to quit out of the function
rs = execvp(path1,args1);//runs command 1
This line in the "parent process" replaces the current program. There is no while loop after this succeeds anymore, only program 1.
Think of it this way. When user inputs m pairs of commands to your program, how many processes do you expect to be spawned? You expect a total of 2m processes each corresponding to a command but you only fork m times each corresponding to an iterations of the while loop in your current code.
You should instead fork a different process for program 1 as well, similar to how you did it for program 2.
I m trying to implement a program using pipes where parent process accepts a string and passes it to child process. Need to be done with only single pipe. How does the pipe read & write accepts string.
Here is my sample code! all!
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{
int pid[2];
ssize_t fbytes;
pid_t childpid;
char str[20], rev[20];
char buf[20], red[20];
pipe(pid);
if ((childpid = fork()) == -1) {
perror("Fork");
return(1);
}
if (childpid == 0) {
// child process close the input side of the pipe
close(pid[0]);
int i = -1, j = 0;
while (str[++i] != '\0') {
while(i >= 0) {
rev[j++] = str[--i];
}
rev[j] = '\0';
}
// Send reversed string through the output side of pipe
write(pid[1], rev, sizeof(rev));
close(pid[0]);
return(0);
} else {
cout << "Enter a String: ";
cin.getline(str, 20);
// Parent process closing the output side of pipe.
close(pid[1]);
// reading the string from the pipe
fbytes = read(pid[0], buf, sizeof(buf));
cout << "Reversed string: " << buf;
close(pid[0]);
}
return 0;
}
You never pass the string to be reversed to the child, so it reverses some random garbage and sends it to the parent.
Minor issues:
write(pid[1], rev, sizeof(rev));
close(pid[0]); // Should be pid[1]
return(0); // Should be _exit(0)
The reason you don't want to return from main in the child is that you don't know what consequences that will have. You may call exit handlers that manipulate real world objects that the parent expects to remain intact.
I am writing a c program for a class that is a small shell. The user inputs a command, and the code executes it using the exec() function.
I need to have a fork in the process so all the work is done in the child process. The only problem is that the child won't terminate properly and execute the command. When I run the code without the fork, it executes commands perfectly.
The problem seems to be coming from where I am creating the string to be used in the execv call. It's the line of code where I call strcpy. If I comment that out, things work fine. I also tried changing it to strncat with the same problem. I'm clueless as to what's causing this and welcome any help.
#include <sys/wait.h>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <unistd.h>
using namespace std;
string *tokenize(string line);
void setCommand(string *ary);
string command;
static int argument_length;
int main() {
string argument;
cout << "Please enter a unix command:\n";
getline(cin, argument);
string *ary = tokenize(argument);
//begin fork process
pid_t pID = fork();
if (pID == 0) { // child
setCommand(ary);
char *full_command[argument_length];
for (int i = 0; i <= argument_length; i++) {
if (i == 0) {
full_command[i] = (char *) command.c_str();
// cout<<"full_command " <<i << " = "<<full_command[i]<<endl;
} else if (i == argument_length) {
full_command[i] = (char *) 0;
} else {
full_command[i] = (char *) ary[i].c_str();
// cout<<"full_command " <<i << " = "<<full_command[i]<<endl;
}
}
char* arg1;
const char *tmpStr=command.c_str();
strcpy(arg1, tmpStr);
execv((const char*) arg1, full_command);
cout<<"I'm the child"<<endl;
} else if (pID < 0) { //error
cout<<"Could not fork"<<endl;
} else { //Parent
int childExitStatus;
pid_t wpID = waitpid(pID, &childExitStatus, WCONTINUED);
cout<<"wPID = "<< wpID<<endl;
if(WIFEXITED(childExitStatus))
cout<<"Completed "<<ary[0]<<endl;
else
cout<<"Could not terminate child properly."<<WEXITSTATUS(childExitStatus)<<endl;
}
// cout<<"Command = "<<command<<endl;
return 0;
}
string *tokenize(string line) //splits lines of text into seperate words
{
int counter = 0;
string tmp = "";
istringstream first_ss(line, istringstream::in);
istringstream second_ss(line, istringstream::in);
while (first_ss >> tmp) {
counter++;
}
argument_length = counter;
string *ary = new string[counter];
int i = 0;
while (second_ss >> tmp) {
ary[i] = tmp;
i++;
}
return ary;
}
void setCommand(string *ary) {
command = "/bin/" + ary[0];
// codeblock paste stops here
You said:
Its the line of code where I call
strcpy.
You haven't allocated any memory to store your string. The first parameter to strcpy is the destination pointer, and you're using an uninitialized value for that pointer. From the strcpy man page:
char *strcpy(char *s1, const char *s2);
The stpcpy() and strcpy() functions copy the string s2 to s1 (including
the terminating `\0' character).
There may be other issues, but this is the first thing I picked up on.
I am very new to C++. In my code i am using readline to get the user input which is a command and that input will be processed accordingly. When user enters a command named "setflag", i should set a flag to TRUE in all the active sessions of my processes.
The code works just fine. I am able to update the flag in all the processes simultaneously using signals.
Problem:
Please compile the code as: g++ -lreadline -lncurses alex123.cpp -o alex123
Spawn three processes (am refering to them as P1, P2, P3) by executing the binary "alex123" on separate terminals. In P1 issue "setflag" command. You can see that "MYFLAG" is set to TRUE in P1. Also you can see that P2 and P3 are able to receive the signal. But in P2 and P3 you wont see the command prompt. Only if you press the "enter" key on P2 and P3, you will get the command prompt back.
I want this in such a way that command prompt should automatically come. Pls help me.
alex123.cpp
## Heading ##
#include "stdio.h"
#include "iostream"
#include "signal.h"
#include "sys/time.h"
#include "string"
#include "stdlib.h"
#include "sstream"
#include "sys/types.h"
#include "unistd.h"
#include "/usr/include/readline/readline.h"
#include "/usr/include/readline/history.h"
using namespace std;
bool myFlag = false;
void updateFlag(int i)
{
cout << "\nGot the signal.";
myFlag= true;
//To give line feed from within the code itself. I need the fix here.
putc(10,stdout); // This doesn't work.
}
int getOtherPids(string &pidList)
{
char pid[10];
string pidStr;
FILE *stream;
//Command the search the process alex123
std::string cmd = "pgrep -x alex123";
stream = popen(cmd.c_str(),"r");
if (stream == NULL)
{
cout << "\nWhat the hell\n";
return 0;
}
while (fgets(pid, 10, stream) != NULL)
{
for(int i=0;i<10;i++)
{
int p = (int)pid[i] - 48;
if ((p >= 0) && (p <= 9))
pidStr.append(1,pid[i]);
}
pidStr.append(1,' ');
pidList.append(pidStr);
pidStr.clear();
}
int status = pclose(stream);
if (status == -1)
{
cout << "\nWhat the hell!! AGAIN !!\n"; return 0;
}
return 1;
}
void notifyOthers()
{
string pidList;
string notifyCmd;
//Convert pid of type int to a string
int my_pid = getpid();
ostringstream os;
os << my_pid;
string myPid = os.str();
//Get other existing alex123 process list to which notification has to be sent.
if (!getOtherPids(pidList))
{
cout << "!!WARNING!! Couldn't get active pids\n";
return;
}
size_t pos = pidList.find(myPid.c_str());
if (pos == string::npos)
{
cout << "!!WARNING!! Other Active process list is empty.\n";
return;
}
//Remove current session pid from the pid list string.
pidList.replace(pos,myPid.length()+1,"");
//If there are no other existing alex123 processes, return from the function.
if(strcmp(pidList.c_str(),"") == 0) return;
pidList.replace(pidList.length()-1,1,"\0");
//Send SIGUSR1 signal to others.
notifyCmd = "kill -SIGUSR1 " + pidList;
system(notifyCmd.c_str());
}
int main()
{
char *foo;
for(;;)
{
//If SIGUSR1 is caught it means that some other process has set the myFlag to TRUE by issuing a "setflag" command.
//So it should be set here also.
signal(SIGUSR1, updateFlag);
cout << "\nMYFLAG:"<< myFlag <<endl;
foo = readline("<alex> ");
if(strcmp(foo,"exit") ==0) { return 0; }
if(strcmp(foo,"setflag") == 0)
{
//Set myFlag to TRUE in the current process from where "setflag" command was issued.
myFlag= true;
//Inform all other processes that myFlag has been updated.
notifyOthers();
}
}
return 0;
}