I created a simple shell in Linux using fork() and execvp(). It works fine with cat, ls etc. but when I try to redirect its output like ./hello.o > output.txt it doesn't work.
I am guessing I didn't provide the write path to look for the definitions. My shell is currently searching on /bin/ path where most of the commands are stored.
Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ARG_SIZE 100 // MAX LENGTH FOR ARGUMENTS
#define PATH "/bin/" // PATH FOR ARGUMENTS
int main() {
char inputLine[BUFSIZ];
char *argv[ARG_SIZE];
// for path + argv
char programPath[200];
while (1) {
printf("myshell> ");
// check if ctrl + D is pressed
if (fgets(inputLine, BUFSIZ, stdin) == NULL)
break;
inputLine[strlen(inputLine) - 1] = '\0';
// check if exit is typed
if (strcmp(inputLine, "exit") == 0)
break;
int i = 0;
argv[0] = strtok(inputLine, " \n");
for (i = 0; argv[i] && i < ARG_SIZE-1; ++i)
argv[++i] = strtok(NULL, " \n");
// create a fork call
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
// parent
if (pid != 0) {
wait();
// child
} else {
strcat(programPath, argv[0]);
// will not return unless it fails
execvp(programPath, argv);
perror("execvp");
exit(1);
}
}
return 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;
}
When running this program after the first iteration the program stops. This is because of execv function. What can I do so my loop still continues on until the user types quit.
I have tried creating a fork process before doing the execv in the child process but that does 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;
pid_t cpid2;
rs = pipe(pipefd);
char* args1[256];
char* args2[256];
if (rs < 0){
perror("pipe");
exit(1);
}
char cmd1[256];
char cmd2[256];
char path1[10];
char path2[10];
while(true){
cout << "Command 1";
cin.getline(cmd1,256);
cout << "command 2";
cin.getline(cmd2,256);
if (strcmp(cmd1,"quit") == 0)
break;
if (strcmp(cmd2,"quit") == 0)
break;
char *token;
token = strtok(cmd1," ");
int i=0;
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]);
strcpy(path2,args2[0]);
rs = fork();
if (rs < 0){
perror("Fork");
exit(1);
}
if (rs == 0){//child process
close(pipefd[1]);
close(0);
dup(pipefd[0]);
close(pipefd[0]);
rs = execvp(path2,args2);
if (rs < 0){
perror("execl");
exit(EXIT_FAILURE);
}
}
else{//PARENT PROCESS
close(pipefd[0]);
close(1);
dup(pipefd[1]);
close(pipefd[1]);
wait(&rs);
rs = execvp(path1,args1);
if (rs < 0){
perror("execl");
exit(EXIT_FAILURE);
}
}
};
return 0;
}
After outputting the answer the function should then ask the user for two more commands, This should go on until the user types in quit.
First of all exit(EXIT_FAILURE) is making your program exit.So remove it.
And you can put the possible exception creating code in try {} catch {} block.
In your case like this..
try{
rs = execvp(path1,args1);
if (rs < 0){
perror("execl");
//exit(EXIT_FAILURE);
}
}catch(int y){
continue;//here you can put your handling logic
}
By this you will get the desired output.
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 trying to create a minimal code to use pipe/fork/execlp.
So far so good, I am using execlp with bash -c, so if I do.
echo asd |./a.out cat
> asd
So it is working as expected.
But if I try to use anything that needs a TTY, it does not work.
Like ./a.out vim, I get "Vim: Warning: Input is not from a terminal"
And the vim that was open does not works as expected.
I tried to find on the internet an example on how to open a TTY, the only one that I found was:
http://www.danlj.org/lad/src/minopen.c
My Code, so far is:
#include <iostream>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
typedef struct pCon{
int fout[2];
int fin[2];
int fd[2];
int pid1, pid2;
} connectionManager;
std::string command = "";
/*
* Implementation
*/
void childFork(connectionManager *cm);
int main(int argc, char *argv[]) {
int size;
if(argc < 2) exit(1);
else command = argv[1];
connectionManager *cm = new connectionManager;
pipe(cm->fd);
if((cm->pid1 = fork()) == -1)exit(1);
if (cm->pid1 == 0)
{
const unsigned int RCVBUFSIZE = 2000;
char echoString[RCVBUFSIZE];
while((size = read(fileno(stdin),echoString,RCVBUFSIZE)) > 0)
write(cm->fd[1], echoString, size);
close(cm->fd[1]);
}
else
childFork(cm);
return 0;
}
void childFork(connectionManager *cm){
char *buffer = new char[2000];
int size;
close(cm->fd[1]);
dup2(cm->fd[0], 0);
close(cm->fd[0]);
pipe(cm->fout);
if((cm->pid2 = fork()) == -1)exit(1);
if (cm->pid2 == 0)
{
close(cm->fout[0]);
int returnCode = execlp("bash", "bash", "-c", command.c_str(), NULL);
if(returnCode!=0)
std::cerr << "Error starting the bash program" << std::endl;
}
else
{
close(cm->fout[1]);
while((size = read(cm->fout[0], buffer, 2000 )) > 0 )
write(fileno(stdout), buffer, size);
}
}
I tried to keep the minimal necessary code to make it work.
Is there any way to implement TTY on this code, I know that does not seems to be such trivial task.
Can someone help me with that?
I also tried to open the tty and dup it, but no luck so far.
Try to use pseudo terminal. You can use opentty. For your purpose you can use forkpty which combines pty with fork. I've created a small example for you. About the same as your program, just it works. I've kept it simple, so I don't handle the terminal control characters.
#include <pty.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
int main(int argc, char *argv[])
{
if (argc<1) return 1;
int master;
pid_t pid = forkpty(&master, NULL, NULL, NULL); // opentty + login_tty + fork
if (pid < 0) {
return 1; // fork with pseudo terminal failed
}
else if (pid == 0) { // child
char *args[] = { argv[1], argv[2], NULL }; // prg + 1 argument
execvp(argv[1], args); // run the program given in first param
}
else { // parent
struct termios tios;
tcgetattr(master, &tios);
tios.c_lflag &= ~(ECHO | ECHONL);
tcsetattr(master, TCSAFLUSH, &tios);
while(1) {
fd_set read_fd, write_fd, err_fd;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FD_ZERO(&err_fd);
FD_SET(master, &read_fd);
FD_SET(STDIN_FILENO, &read_fd);
select(master+1, &read_fd, &write_fd, &err_fd, NULL);
if (FD_ISSET(master, &read_fd))
{
char ch;
int c;
if (c=read(master, &ch, 1) != -1) // read from program
write(STDOUT_FILENO, &ch, c); // write to tty
else
break; // exit when end of communication channel with program
}
if (FD_ISSET(STDIN_FILENO, &read_fd))
{
char ch;
int c=read(STDIN_FILENO, &ch, 1); // read from tty
write(master, &ch, c); // write to program
}
}
}
return 0;
}
For compiling use -lutil .
While running a new tty device appears in /dev/pts .
vim accepts it as a terminal.
I have create following test case for simulating the issue.I have compiled the source code and able to simulate the issue.
1)When the system command,we got some console out ( i.e your job submitted) which is redirect to file using dup2 and create file .stdout.
When I try to read this file as I need job submission information and I did not get data which was on console out. I was able to get data which I wrote it.( confirm file operation).
Can we not read console output from the file which is create by the child process.
*change rundir and cmd
#include <string>
#include <vector>
#include <utility>
#include <sstream>
#include <fstream>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <map>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
int child();
int main()
{
string rundir = "/temp";
pid_t id =child();
string _xafile;
string _afile;
string _afilecmd;
string line;
stringstream ss(stringstream::in | stringstream::out);
ss<<int(id);
_xafile = rundir;
_xafile = _xafile + "/" + ss.str()+".stdout";
cout<<" _xafile is "<<_xafile<<endl;
string cmd ="cat "+ _xafile + ">" + rundir + "/" + "process.txt";
_afile = rundir;
_afile = _afile + "/" + "process.txt";
_afilecmd = "rm -rf "+ _afile;
system(cmd.c_str());
ifstream xafile(_afile.c_str());
while(xafile)
{
string word;
xafile >> word;
cout<<word<<" ";
/* try to read console output but did not read data */
}
system(_afilecmd.c_str());
return 0;
}
int child()
{
string rundir = "/tmp";
string cmd = " tool <input file> -o <outputfile>";
const char* std_out_file;
pid_t pid = fork();
if (pid < 0) {
return -1;
}
if (pid == 0) {
pid_t mypid = getpid();
stringstream sm;
sm << rundir << "/";
if (strlen(std_out_file) == 0)
sm << mypid << ".stdout";
else
sm << std_out_file;
int fd = open(sm.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1);
dup2(fd, 2);
int fd2 = open("/dev/zero", O_RDONLY);
dup2(fd2, 0);
cout<<cmd <<endl<<endl;
// execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
system(cmd.c_str());
/*When system call it generate console output like : your job submitted */
/* redirect to file */
exit(-1);
}
if (pid > 0)
return pid;
cout<<" child is done"<<endl;
return 0;
}
It's not entirely clear what your thinking is here - your code appears to fork a child which does a bunch of fancy io stuff to try and redirect your applications stdout to a file, and then runs a command with system(). The command in your code has it's own redirects, specifically via "-o" so it probably isn't writing to stdout.
Back in the parent process, you try to open for reading the same file your child process will open for writing. You've got no synchronization, so they could happen in any order. You appear to be trying to use "cat" to read the file and trying to read the stdout of the cat?
What I think you are actually trying to do is the C/C++ equivalent of Perl's
$foo = `ls -l /tmp`;
or something similar - to execute a command and capture the output.
A better way to do this would be to use pipes.
#include <iostream>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
enum { WritePipe = 1, ReadPipe = 0 };
int main(int argc, const char** argv)
{
int pipes[2]; // each process is going to have its own file descriptor.
if(pipe(pipes) != 0) {
perror("pipe failed");
return -1;
}
pid_t pid = fork();
if(pid == 0) {
// child
close(pipes[ReadPipe]); // close the parent pipe in our context.
// redirect stdout and stderr to the pipe.
dup2(pipes[WritePipe], STDOUT_FILENO);
dup2(pipes[WritePipe], STDERR_FILENO);
// close(STDERR_FILENO); // <- or just do this to close stderr.
int result = system("ls -ltr /etc");
close(pipes[WritePipe]);
return result;
}
if(pid < 0) {
perror("fork failed");
return -1;
}
// Parent process has launched the child.
close(pipes[WritePipe]);
char buffer[4097];
int bytesRead;
while((bytesRead = read(pipes[ReadPipe], buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytesRead] = 0;
std::cout << buffer;
}
std::cout << std::endl;
close(pipes[ReadPipe]);
return 0;
}