the last two days I've been trying to come up with a suitable solution to make this shell simulator that executes linux command run with fork(), dup2(), close(), pipes() and wait() functions. Up to now I have achieved an implementation that leaves me unsatisfied because I have been able to execute a command but I can't get the information from the stdout to be passed to a following command by means of pipes, for example, when writing ls 'l | wc, it would be possible to execute the first command, second of the second that will be carried out according to the information obtained with the first. I would appreciate if someone could clarify what the error in my code is so that I can implement something that allows me to enter a sequence of more than 3 commands that can share their information through the pipe line and print a final result. :)
#include <errno.h> //presentan un informe de error
#include <iostream> //Emplear cin cout c++
#include <stdio.h> //librerÃa estandar de entrada y salida
#include <string.h> //Para manejo de strigs
#include <sys/types.h> // obtener información sobre ficheros
#include <sys/wait.h> //permite el uso de wait
#include <unistd.h> //para poder llamar fork
using namespace std;
// Strig processor
int strProcessor(char *words[50]) {
char command[500]; // Array of char that recieves a full command line
cout << "myshell$"; // print specified value
cin.getline(command, 500); // getline instead of simple cin to read spaces
char *word =
strtok(command, " "); // Pointer for each token starting at the first one
int index = -1;
while (word != NULL) { // Iterate the array to save each string
index += 1; // increase the number of the index
words[index] = word; // Inicialize the each necessary space
word = strtok(NULL, " "); // Save next string of the pointer
}
return index; // Return index because it will be important for later on
}
// Single command processor
int splitCommand(char *words[50], char *singleCommand[50], int index, int start) {
int newIndex = 0; //aux
while (start <= index && strcmp(words[start], "|")) {
singleCommand[newIndex] = words[start];
start += 1;
newIndex += 1;
}
start += 1;
singleCommand[newIndex] = NULL;
return start;
}
//Count the number of commands
int commandCounter(char *words[50], int index){
int counter = 1;
for(int i=0;i<=index;i++) {
if (!strcmp(words[i],"|") && i != index) {
counter += 1;
}
}
return counter;
}
int process(){
int READ = 0;
int WRITE = 1;
int fd[20][2]; // file directory for pipe
int index = -1; // useful index
char *words[50]; // Save all tokens in an array
int start = 0; // useful index
char *singleCommand[20]; // Save an individual command with NULL at the end
//Handling no command written
while (index == -1) {
index = strProcessor(words);
}
//Exit if user types it
if (!strcmp(words[0], "exit")) {
return 0;
}
//Establish the number of commands to exe
int commandNumber = commandCounter(words, index);
//Create a piper for each command
for (int i = 0; i < commandNumber - 1;i++) {
pipe(fd[i]);
}
for (int i = 0; i < commandNumber;i++) {
start = splitCommand(words, singleCommand, index, start); // Get first command
int pid=fork();
if (pid == 0) {
if (i == 0) {
dup2(fd[i][1], STDOUT_FILENO);
close(fd[i][1]);
close(fd[i][0]);
execvp(singleCommand[0], singleCommand);
} else if (i > 0) {
dup2(fd[i-1][0], STDOUT_FILENO);
close(fd[i-1][1]);
close(fd[i-1][0]);
dup2(fd[i][1], STDOUT_FILENO);
close(fd[i][1]);
close(fd[i][0]);
execvp(singleCommand[0], singleCommand);
}
} else {
if (i == 0) {
int stat;
wait(&stat);
close(fd[i][1]);
close(fd[i][0]);
cout << "Process: " << pid << " exits with status:" << stat << endl;
} else if (i > 0) {
int stat;
wait(&stat);
wait(&stat);
close(fd[i-1][1]);
close(fd[i-1][0]);
close(fd[i][1]);
close(fd[i][0]);
cout << "Process: " << pid << " exits with status:" << stat << endl;
}
}
}
return 0;
}
int main() {
process();
return 0;
}
Example input:
ls -alF / | grep bin | cat -n
The displayed messages of the shell should be like:
Process 16480 exits with 0
Process 16481 exits with 0
1 drwxr-xr-x 2 root root 4096 Mar 30 04:07 bin/
2 drwxr-xr-x 2 root root 12288 Mar 30 04:07 sbin/
Process 16482 exits with 0
ls -alF / | grep bin | tr a-z A-Z | rev | cat -n
The displayed messages of the shell should be like:
Process 16554 exits with 0
Process 16555 exits with 0
Process 16556 exits with 0
1 /NIB 70:40 03 RAM 6904 TOOR TOOR 2 X-RX-RXWRD
2 /NIBS 70:40 03 RAM 88221 TOOR TOOR 2 X-RX-RXWRD
Process 16557 exits with 0
Process 16558 exits with 0
Related
I am trying to create a shell in c++. It creates a child process which executes a command and pipes the response back to the parent. I want to specify if the second argument of a command is -o then I would like to redirect the output of the command to a file. (output.txt).I used dup() to redirect output to my file. However, when I run the program and enter for example wc -o fileName the program creates the file output.txt but does not write to it when I specify to print the result of my child process.
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <cstring>
#include <fcntl.h>
#include <vector>
#include <sys/wait.h>
int main(){
// array of file descriptors for parent and child
int filedes[2];
char foo[4096];
char** argv;
std::cout << "$$-> ";
char command[128];
std::cin.getline(command, 128);
if(strlen(command) != 0) {
std::vector<char *> args;
char *prog = strtok(command, " ");
char *tmp = prog;
while(tmp != NULL) {
args.push_back(tmp);
tmp = strtok(NULL, " ");
}
argv = new char *[args.size() + 1];
for (int k = 0; k < args.size(); k++) {
argv[k] = args[k];
}
argv[args.size()] = NULL;
}
char* newargc = argv[0];
char *newargv[] = {newargc,argv[2],NULL};
if(pipe(filedes) < 0){
std::cout << "There was an error creating the pipe";
}
int pid = fork();
if(pid == 0){
// writing to the pipe
// close read end of pipe
close(filedes[0]);
close(STDOUT_FILENO);
dup(filedes[1]);
if(strcmp(argv[1],(char*)"-o") == 0 ||strcmp(argv[1], (char*) "-b") == 0){
execvp(newargv[0], newargv);
}
else{
execvp(argv[0],argv);
}
}
else if (pid > 0) {
std::cout << "This is the parent process\n";
while(wait(NULL) > 0);
close(filedes[1]);
int output_fd = open("output.txt", O_CREAT, O_TRUNC, O_RDWR);
read(filedes[0], foo, sizeof(foo));
if(strcmp(argv[1],(char*)"-o") == 0){
close(STDOUT_FILENO);
dup(output_fd);
write(output_fd, foo, sizeof(foo));
}
else if(strcmp(argv[1], (char*) "-b") == 0){
int stdoutHolder = dup(STDOUT_FILENO);
close(STDOUT_FILENO);
dup(output_fd);
std::cout<< foo;
dup2(stdoutHolder, 1);
}
std::cout << foo;
}
//pid is less than 0 if error
else{
std::cout << "There is an error.";
}
return 0;
}
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 am trying to pipe data from one child process to another. When I run this, it hangs. If I don't make it wait for the first child process, it goes back to the top of the loop prompting for commands without giving the expected output, and when I prompt it to quit, it dumps all of the output I was expecting. I had it working with just one child process, but then the second execvp killed the parent process, and I didn't get back to the top of the loop prompting for more commands.
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
using namespace std;
int main(int argc, const char * argv[]) {
bool quit=0;
char quitArray[] = "quit";
int pipeReturnValue, fork1ReturnValue, fork2ReturnValue, pipefd[2], checkForQuit;
//Enter a loop where each iteration prompts for two single-line inputs
while (!quit) {
//Get command 1
char command1[128];
printf("Enter command 1: ");
fgets(command1,128,stdin);
command1[strlen(command1) -1] = 0;
//Exit if user enters quit
checkForQuit = strncmp(command1, quitArray, 4);
if (checkForQuit == 0) {
exit(0);
}
//Get command 2
char command2[128];
printf("Enter command 2: ");
fgets(command2,128,stdin);
command2[strlen(command2) -1] = 0;
//Exit if user enters quit
checkForQuit = strncmp(command2, quitArray, 4);
if (checkForQuit == 0) {
exit(0);
}
//Open pipe
pipeReturnValue = pipe(pipefd);
if (pipeReturnValue < 0) {
perror("Pipe failed");
exit(1);
}
//Fork 1
fork1ReturnValue = fork();
if(fork1ReturnValue < 0) {
perror("Fork failed");
exit(1);
}
else if (fork1ReturnValue == 0) {
//Fork 2
fork2ReturnValue = fork();
if (fork2ReturnValue < 0) {
perror("Fork 2 failed");
}
else if (fork2ReturnValue == 0) {
//close read end of pipe
close(pipefd[0]);
//parse command 1 arguments
//store tokens in array
char *arguments[6] = {};
arguments[0] = strtok(command1, " ");
int tokenCounter = 0;
while (arguments[tokenCounter] != NULL) {
tokenCounter++;
arguments[tokenCounter] = strtok(NULL, " ");
}
//dup stdo to pipe
dup2(pipefd[1], 1);
//execute arguments
execvp(arguments[0], arguments);
}
else {
wait(&fork2ReturnValue);
//close write end of pipe
close(pipefd[1]);
//parse command 2 arguments
//store tokens in array
char *arguments[6] = {};
arguments[0] = strtok(command2, " ");
int tokenCounter = 0;
while (arguments[tokenCounter] != NULL) {
tokenCounter++;
arguments[tokenCounter] = strtok(NULL, " ");
}
//dup stdin to pipe
dup2(pipefd[0], 0);
//exec
execvp(arguments[0], arguments);
}
}
else {
wait(&fork1ReturnValue);
}
}
return 0;
}
I finally figured it out. I needed to open the pipe after the first fork rather than before.
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
using namespace std;
int main(int argc, const char * argv[]) {
bool quit=0;
char command1[128],
command2[128],
quitArray[] = "quit";
int pipeReturnValue,
fork1ReturnValue,
fork2ReturnValue,
checkForQuit,
pipefd[2];
//Loop where each iteration prompts for two single-line inputs
while (!quit) {
//Get command 1
printf("Enter command 1: ");
fgets(command1,128,stdin);
command1[strlen(command1) -1] = 0;
//Exit if user enters quit
checkForQuit = strncmp(command1, quitArray, 4);
if (checkForQuit == 0) {
quit = 1;
exit(0);
}
//Get command 2 and trim trailing new line character
printf("Enter command 2: ");
fgets(command2,128,stdin);
command2[strlen(command2) -1] = 0;
//Exit if user enters quit
checkForQuit = strncmp(command2, quitArray, 4);
if (checkForQuit == 0) {
quit = 1;
exit(0);
}
//Fork to create 1st child process, return error if fork fails
fork1ReturnValue = fork();
if(fork1ReturnValue < 0) {
perror("Fork 1 failed");
exit(1);
}
//Open pipe, return error if fork fails
pipeReturnValue = pipe(pipefd);
if (pipeReturnValue < 0) {
perror("Pipe failed");
exit(1);
}
//First child process
else if (fork1ReturnValue == 0) {
//Fork to create 2nd child process, return error if fork fails
fork2ReturnValue = fork();
if (fork2ReturnValue < 0) {
perror("Fork 2 failed");
}
//Second child process
else if (fork2ReturnValue == 0) {
//close read end of pipe
close(pipefd[0]);
//Parse command 1 arguments, store tokens in an array
char *arguments[6] = {};
arguments[0] = strtok(command1, " ");
int tokenCounter = 0;
while (arguments[tokenCounter] != NULL) {
tokenCounter++;
arguments[tokenCounter] = strtok(NULL, " ");
}
//Dup standard output to write side of pipe
dup2(pipefd[1], 1);
//Execute arguments from command 1
execvp(arguments[0], arguments);
}
//First child code continued
else {
//Wait for child 2 to to terminate
wait(&fork2ReturnValue);
//Close write end of pipe
close(pipefd[1]);
//Parse command 2 arguments, store tokens in array
char *arguments[6] = {};
arguments[0] = strtok(command2, " ");
int tokenCounter = 0;
while (arguments[tokenCounter] != NULL) {
tokenCounter++;
arguments[tokenCounter] = strtok(NULL, " ");
}
//dup standard input to read side of pipe
dup2(pipefd[0], 0);
//Execute arguments from command 2
execvp(arguments[0], arguments);
}
}
//Parent process continued
else {
//Wait for child 1 to terminate
wait(&fork1ReturnValue);
}
//return to top of loop
}
return 0;
}
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;
}