I'm writing my own custom shell, and as a part of my project, when the program reads either "<" or ">" character from user input, it needs to redirect stdin and stdout to a custom file. I wrote a function for this below. The part where I'm struggling is that even though I wrote the code for input and output redirections pretty much in the same manner, output redirection seems to work as expected, while input redirection does not. See the code below:
void inOutReDirector(char **toks, char *inputFile, char *outputFile, int *fd0, int *fd1, int *in, int *out) {
fflush(0);
for (int i = 0; toks[i] != NULL; i++) {
if (strcmp(toks[i], "<") == 0) {
toks[i] = NULL;
strcpy(inputFile, toks[i + 1]);
toks[i + 1] = NULL;
*in = 1;
}
if (strcmp(toks[i], ">") == 0) {
toks[i] = NULL;
strcpy(outputFile, toks[i + 1]);
toks[i + 1] = NULL;
*out = 1;
}
}
//input redirection
if (*in == 1) {
if ((*fd0 = open(inputFile, O_RDONLY, 0)) < 0) {
printf("Couldn't open input file: %s", strerror(errno));
exit(1);
}
dup2(*fd0, STDIN_FILENO);
close(*fd0);
}
//output redirection
if (*out == 1) {
if ((*fd1 = creat(outputFile, 0644)) < 0) {
printf("Couldn't open output file: %s", strerror(errno));
exit(1);
}
dup2(*fd1, STDOUT_FILENO);
close(*fd1);
}
}
And here is how I call this function from my main:
int main() {
char *toks[STD_INPUT_SIZE] = {0};
int fd0, fd1, in = 0, out = 0;
char inputFile[64], outputFile[64];
pid_t pid;
while (1) {
//print prompt
//get user input
pid = fork();
if (pid < 0) {
printf("%s \n", strerror(errno));
exit(1);
}
int stopNeeded = strcmp(toks[0], "exit") == 0;
if (pid == 0 && !stopNeeded) {
pathFinder(toks, shellInput, currentDir); //finding path for execv input
inOutReDirector(toks, inputFile, outputFile, &fd0, &fd1, &in, &out); // I/O redirection
if (execv(shellInput, toks) != 0) {
char *errMsg = strerror(errno);
printf("%s \n", errMsg);
//clean the old contents of toks
for (int i = 0; i < STD_INPUT_SIZE; ++i) {
free(toks[i]);
toks[i] = NULL;
}
exit(1);
}
}
if (pid > 0) {
pid_t childPid = waitpid(pid, NULL, 0);
(void) childPid;
}
}
return 0;
}
And here is an example of output redirection from my terminal screen
$ ls > output.txt
$
This creates "output.txt" file and prints the result inside this file in the current directory.
And here is an example of input redirection from my terminal screen
$ cat < output.txt
$
Input redirection does not work correctly. It prints the prompt and waits for the input instead of showing the contents of output.txt in my terminal.
I appreciate any help you can provide in advance!
The problem should be on this line of the function inOutReDirector
if (strcmp(toks[i], ">") == 0) {
should be changed to
else if (strcmp(toks[i], ">") == 0) {
When toks[i] is equal to "<", toks[i] will be set to NULL above, this line calling strcmp will cause SIGSEGV, so the child process will exit, and the subsequent execv will not be executed.
Related
I'm trying to create my own unix shell and I've hit a wall while trying to append a command such as ls to an existing file, for example.
ls >> myOutput
I was able to do a basic redirection using > to print to an output file and figured doing >> would be quite similar, but I guess I'm wrong.
This is my code:
int pid;
int in = 0, out = 0, append = 0, j;
int fd0, fd1, fda;
char* args[MAX_ARGS];
char inFileName[64], outFileName[64];
//used to get arguments for desired command
//i.e. ls -a -l -t
get_args(cmdline, args);
//Commands used to exit the shell.
if(!strcmp(args[0], "quit") || !strcmp(args[0], "exit"))
{
exit(0);
}
pid = fork();
if(pid == 0)
{ /* child process */
for(j = 0; args[j] != '\0'; ++j)
{
if(strcmp(args[j], ">>") == 0)
{
args[j] = NULL;
strcpy(outFileName, args[j + 1]);
printf("You want to append data to existing file\n");
append = 2;
}
if(strcmp(args[j], "<") == 0)
{
args[j] = NULL;
strcpy(inFileName, args[j + 1]);
printf("input file name is %s\n", inFileName);
in = 2;
}
if(strcmp(args[j], ">") == 0)
{
args[j] = NULL;
strcpy(outFileName, args[j + 1]);
printf("output file name is %s\n", outFileName);
out = 2;
}
}
//printf("in is %d and out is %d\n", in, out);
if(!strcmp(args[0], "|"))
{
printf("You want to pipe info\n");
}
if(append)
{
**//here is where my issue occurs**
if((fda = open(outFileName, O_RDWR|O_APPEND)) < 0)
{
perror("Error appending data\n");
exit(0);
}
dup2(fda, STDOUT_FILENO);//1
close(fda);
}
if(in)
{
if((fd0 = open(inFileName, O_RDONLY, 0)) < 0)
{
perror("Couldn't read from input file\n");
exit(0);
}
//Changes where the command will read from STDIN to a input file.
dup2(fd0, 0);
close(fd0);
}
if(out)
{
if((fd1 = creat(outFileName , 0644)) < 0)
{
perror("Coudln't create output file\n");
exit(0);
}
dup2(fd1, STDOUT_FILENO);//1
close(fd1);
}
execvp(*args, args);
perror("exec failed");
exit(-1);
}
else if(pid > 0)
{ /* parent process */
waitpid(pid, NULL, 0);
}
else
{ /* error occurred */
perror("fork failed");
exit(1);
}
Please let me know if you would like further details. Any help would truly be appreciated.
The following function successfully executes any command that doesn't contain pipes, so don't worry about the weird functions. These work. The problem I am having is that whenever I execute any command like the following:
cat file.txt | grep string
the command is successfully executed, but it remains idle, so somehow it gets stuck and no other command can execute. why is this happening?. I think it has something to do with the way I use pipe, dup and fork, so try to approach the problem from these functions. I know you may be arguing that this code doesn't work for other commands with pipes, but I just want to get this particular example to work and to do so I just redirect STDIN to the open file in the first iteration.
int myshell_execute(struct processNode* list, int a)
{
struct processNode* myList = list; // next node to be handled
int pipefd[2];
int in=0;
if (pipe(pipefd) == -1) {
perror("pipe");
myshell_exit(-1);
}
while(myList != NULL)
{
char* program = myList->program; // Get the program to be executed
char ** program_args = myList->program_arguments; // get the programs and arguments to be executed
char ** redirection_string = myList->redirection; //get the part of the command that contains redirection
int *status;
int* stdout;
int stdout_num = 1;
stdout = &stdout_num;
int fileDescriptor;
pid_t pid;
if(strcmp(program,"cd") == 0)
{
return myshell_cd(program_args);
}
else if (strcmp(program,"exit") == 0)
{
return myshell_exit(0);
}
pid = fork();
if(pid == 0)
{
if(in == 1)
{
close(pipefd[1]);
dup2(pipefd[0],0);
close(pipefd[0]);
}
if(sizeOfLine(redirection_string) != 0)
{
redirectionHandler(redirection_string,stdout); // This works. This just handles redirection properly
}
if(*stdout == 1 && myList->next !=NULL)
{
close(pipefd[0]);
dup2(pipefd[1],STDOUT_FILENO); // with this
close(pipefd[1]);
}
if(execvp(program,program_args) !=-1)
{
perror("myshell:");
myshell_exit(-1);
}
else{
myshell_exit(0);
}
}
else if (pid <0)
{
perror("myshell: ");
myshell_exit(-1);
}
else
{
wait(status);
}
in = 1;
myList = myList->next;
}
}
new solution:
int helper_execute(int in, int out, char* program, char ** program_args, char *redirection)
{
pid_t pid;
if ((pid = fork ()) == 0)
{
if (in != 0)
{
dup2 (in, 0);
close (in);
}
if (out != 1)
{
dup2 (out, 1);
close (out);
}
redirectionHandler(redirection);
return execvp (program, program_args);
}
return pid;
}
int myshell_execute(struct processNode* list, int a)
{
int i;
pid_t pid;
int in, fd [2];
struct processNode*new_list = list;
char ** newProgram = new_list->program_arguments;
char ** redirection = new_list->redirection;
char * program = new_list->program;
/* The first process should get its input from the original file descriptor 0. */
in = 0;
/* Note the loop bound, we spawn here all, but the last stage of the pipeline. */
while(new_list->next != NULL)
{
pipe (fd);
/* f [1] is the write end of the pipe, we carry `in` from the prev iteration. */
helper_execute (in, fd [1],program,newProgram,redirection);
/* No need for the write end of the pipe, the child will write here. */
close (fd [1]);
/* Keep the read end of the pipe, the next child will read from there. */
in = fd [0];
new_list = new_list->next;
}
/* Last stage of the pipeline - set stdin be the read end of the previous pipe
and output to the original file descriptor 1. */
if (in != 0)
dup2 (in, 0);
/* Execute the last stage with the current process. */
char* lastProgram = new_list->program;
char ** lastRedirection = new_list->redirection;
char * lastPrArguments = new_list->program_arguments;
redirectionHandler(redirection);
return execvp (lastProgram, lastPrArguments);
}
int main() {
int i=0;
char **input;
struct processNode* list;
int tracker = 0;
while ((input = getline()) != EOF) {
list = create_list(input);
myshell_execute(list,0);
}
return 0;
}
The only problem with this solution is that as soon as one command is executed, the main immediately detects the end of the file, so it exits the shell.
This is because
your parent process also holds the pipe open,
just after forking you call wait. If your first process (cat) fills its output pipe and there is not any process yet available to consume the read end of the pipe then the process stalls forever.
It would look like this:
#define MAX_PIPE_LEN 256
int myshell_execute(struct processNode* list, int a)
{
/* fd0 are the input and output descriptor for this command. fd1
are the input and output descriptor for the next command in the
pipeline. */
int fd0[2] = { STDIN_FILENO, STDOUT_FILENO },
fd1[2] = { -1, -1 };
pid_t pids[MAX_PIPE_LEN] = { 0 };
int pipe_len;
struct processNode* myList; // next node to be handled
int status;
int failed = 0;
for (pipe_len = 0, myList = list;
pipe_len < MAX_PIPE_LEN && myList != NULL;
pipe_len++, myList = myList->next) {
char* program = myList->program; // Get the program to be executed
char ** program_args = myList->program_arguments; // get the programs and arguments to be executed
char ** redirection_string = myList->redirection; //get the part of the command that contains redirection
if(strcmp(program,"cd") == 0) {
return myshell_cd(program_args);
}
else if (strcmp(program,"exit") == 0) {
return myshell_exit(0);
}
if (myList->next != NULL) {
/* The output of this command is piped into the next one */
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe failed");
failed = 1;
break;
}
fd1[0] = pipefd[0];
fd1[1] = fd0[1];
fd0[1] = pipefd[1];
}
pids[pipe_len] = fork();
if (pids[pipe_len] < 0) {
perror("error: fork failed");
failed = 1;
break;
}
if (pids[pipe_len] == 0) {
if (fd0[0] != STDIN_FILENO) {
if (dup2(fd0[0], STDIN_FILENO) == -1) {
perror("error: dup2 input failed");
abort();
}
close(fd0[0]);
}
if (fd0[1] != STDOUT_FILENO) {
if (dup2(fd0[1], STDOUT_FILENO) == -1) {
perror("error: dup2 outut failed");
abort();
}
close(fd0[1]);
}
if (fd1[0] >= 0) {
close(fd1[0]);
}
if (fd1[1] >= 0) {
close(fd1[1]);
}
if(sizeOfLine(redirection_string) != 0) {
redirectionHandler(redirection_string,stdout); // This works. This just handles redirection properly
}
execvp(program, program_args);
perror("error: execvp failed");
abort();
}
if (fd0[0] != STDIN_FILENO) {
close(fd0[0]);
}
if (fd1[1] != STDOUT_FILENO) {
close(fd0[1]);
}
fd0[0] = fd1[0];
fd0[1] = fd1[1];
fd1[0] = fd1[1] = -1;
}
if (myList->next) {
fprintf(stderr, "ERROR: MAX_PIPE_LEN (%d) is too small\n",
MAX_PIPE_LEN);
}
if (fd0[0] >= 0 && fd0[0] != STDIN_FILENO) {
close(fd0[0]);
}
if (fd0[1] >= 0 && fd0[1] != STDOUT_FILENO) {
close(fd0[1]);
}
if (fd1[0] >= 0) {
close(fd1[0]);
}
if (fd1[1] >= 0 && fd1[1] != STDOUT_FILENO) {
close(fd1[1]);
}
/* Now wait for the commands to finish */
int i;
for (i = 0; i < pipe_len; i++) {
if (waitpid(pids[pipe_len - 1], &status, 0) == -1) {
perror("error: waitpid failed");
failed = 1;
}
}
if (failed)
status = -1;
myshell_exit(status);
}
I have created a shell program that can more or less do what the normal linux shell does. My program can redirect input OR output correctly,but not both at the same time. Any solutions online so far, haven't been useful to me.
e.g.
" echo hi kenny > kenny.txt " works
" cat in.txt | less " works
However,(assume in.txt is a random alphabet file)
" sort -u < in.txt > out.txt " does not work for both, only for the input(<).
My code is roughly as follows:
int main(int argc, char* argv[]){
readLine();
if (lineHasSpecialSymbols()) {
if(hasInput()){
inRedirection();
}else
outRedirection();
}
}
Let's assume thats all needed. No pipes etc.
readLine() reads a line from the terminal and saves them in args[]
lineHasSpecialSymbols() detects the first instance of '<' or '>'and returns.
Here's the tricky part in how inRedirection() works:
void inRedirection(void) {
extractCommand("<");
int fd;
if ((pid = fork()) == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
close(0);
//open the file args2[0] and use it as standard input
fd = open(args2[0], O_RDWR);
execvp(args[0], args);
perror("execv");
exit(1);
}
if (pid != 0) {
wait(NULL);
printf("Done ");
printf(args[0]);
printf(".\n");
}
}
outRedirection():
void outRedirection(void) {
extractCommand(">");
int fd;
if ((pid = fork()) == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
close(1);
fd = creat(args2[0], 0644);
execvp(args[0], args);
perror("execv");
exit(1);
}
if (pid != 0) {
wait(NULL);
printf("Done ");
printf(args[0]);
printf(".\n");
}
}
Finally, extractCommand():
void extractCommand(char* symbol) {
int i;
int count = 0;
for (i = 0; args[i] != NULL; i++)
if (!strcmp(args[i], symbol)) {
args[i] = NULL;
while (args[i+1] != NULL) {
args2[count] = args[i+1];
args[i+1] = NULL;
i++;
count++;
}
}
}
Sorry, for the huge code. Here's the problem:
Let's say that I type the command :
" sort -u < in.txt > out.txt "
The code will detect the '<' and extract the command into two parts.
args[0] = "sort" args2[0] = "in.txt" args2[2] = "out.txt"
args[1] = "-u" args2[1] = ">"
It will only the "sort -u < in.txt" and not the rest. My question is how can I change my code to work as an intented? Also, how can I do that for more than two commands? For example : "ls -l /home/user | sort -u | wc -l > in.txt"?
I've thought of some algorithms like making a third args (args3), but that would collide in the case of more that two commands.
I suggest you change your methods.
int outRedirection(void) {
int i;
int j;
for(i = 0; args[i] != NULL; i++) {
// Look for the >
if(args[i][0] == '>') {
args[i] = NULL;
// Get the filename
if(args[i+1] != NULL) {
output_filename[0] = args[i+1];
} else {
return -1;
}
//For- loop to make input AND output functional
for(j = i; args[j-1] != NULL; j++) {
args[j] = args[j+2];
}
return 1;
}
}
return 0;
}
Do the same thing for input and then execute like so:
void IOcommand(void){
if ((pid = fork())== -1){
perror("fork");
exit(1);
}if (pid == 0){
if (input == 1)
freopen(input_filename[0], "r", stdin);
if (output == 1)
freopen(output_filename[0], "w+", stdout);
execvp(args[0],args);
exit(-1);
}
if (pid != 0 ){
wait(NULL);
printf("Done ");
printf(args[0]);
printf(".\n");
}
}
I am implementing a simple shell in c for a class. There are a number of requirements but the one thing that I am concerned about is this sequence of commands:
ls > test
wc < test
which will output the results of the ls command to the file test and the wc
command will then count the number of words, bytes, characters (or something
like that) in that file.
Anyway, the first command works and the test file is successfully created with
the expected content. The wc command doesn't work however. It triggers the error associated with the execv statement "Command can't be executed. My input redirection works as a command like:
grep test < test works perfectly. My question is, Why doesn't my shell recognize the wc command?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
void parse(char buffer[2048], char *arguments[512]){
//these characters(space, tab, new line, return)represent white space
//that separate words
char * delim = " \t\r\n\f";
char * token;
int count = 0;
//Finds the first word in buffer
token = strtok (buffer, delim);
//While a token still exists
while (token != NULL){
//if token is not empty it is added to arguments
if (strlen (token) > 0){
arguments[count]=token;
count++;
}
//Find the next token.
token = strtok (NULL, delim);
arguments[count+1]=NULL;
}
int checkInput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],"<")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectInput(int input,char *arguments[512]){
int in;
int in2;
char*inFile = arguments[input+1];
in = open(inFile, O_RDONLY);
if (in < 0){
perror("Error Opening File");
exit(1);
}
in2 = dup2(in, 0);
if (in2 < 0){
perror("Error redirecting stdin");
exit(1);
}
close(in);
}
int checkOutput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],">")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectOutput(int output,char *arguments[512]){
int out;
int out2;
char*outFile=arguments[output+1];
out = open(outFile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
if (out < 0){
perror("Error Opening File");
exit(1);
}
out2 = dup2(out, 1);
if (out2 < 0){
perror("Error redirecting stdout");
exit(1);
}
close(out);
}
int main(int argc, char **argv){
//buffer is to hold the commands that the user will type in
char buffer[512];
char buffer2[512];
//bin/program_name is the arguments to pass to execv
//if we want to run ls, "/bin/ls" is required to be passed to execv()
char* path = "/bin/";
char * arguments[512];
char * args_copy[512];
//This will be the final path to the program is passed to execv
char prog[512];
char directory[512];
pid_t pid,w;
int status;
int isValid;
int input;
int output;
getcwd(directory,sizeof(directory));
while(1){
isValid = 0;
while(!isValid){
//print the prompt
printf(":");
fflush(stdout);
//get input
fgets(buffer, 512, stdin);
if(buffer[0]!='\n' && buffer[0] != '#'){
isValid=1;
}
}
strcpy(buffer2, buffer);
parse(buffer2, args_copy);
//Handle the builtin functions without before forking
if (strcmp(args_copy[0],"exit")==0){
exit(0);
}
else if (strcmp(args_copy[0],"status")==0){
printf("exit value %d\n",WEXITSTATUS(status));
}else if( strcmp(args_copy[0],"cd") == 0 ){
if(args_copy[1]==NULL){
chdir(directory);
}
else{
chdir(args_copy[1]);
}
}
else{
//fork!
pid = fork();
//Error checking to see if fork works
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
//If pid !=0 then it's the parent
if(pid!=0){
do {
w = waitpid(pid, &status, WUNTRACED | WCONTINUED);
if (w == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
}
else if (WIFSIGNALED(status)) {
printf("killed by signal %d\n", WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
printf("stopped by signal %d\n", WSTOPSIG(status));
}
else if (WIFCONTINUED(status)) {
printf("continued\n");
}
}while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
else{
//parse the user input into an array of strings(arguments)
parse(buffer, arguments);
input = checkInput(arguments);
if (input){
redirectInput(input,arguments);
arguments[input]=NULL;
}
output = checkOutput(arguments);
if (output){
redirectOutput(output,arguments);
arguments[output]=NULL;
}
//First we copy a /bin/ to prog
strcpy(prog, path);
//Then we concancate the program name to /bin/
//If the program name is ls, then it'll be /bin/ls
strcat(prog, arguments[0]);
//pass the prepared arguments to execv and we're done!
int rv=execv(prog, arguments);
if (rv == -1) {
perror("Command can't execute");
exit(EXIT_FAILURE);
}
}
}
}
return 0;
}
This happens because wc is /usr/bin/wc, not /bin/wc.
You can use execvp instead of execv to automatically search through $PATH for your executable. In that case, you would not add /bin/ to the path.
I have looked all over the internet and die.net and can't see to make my code work. My problem is that I am able to redirect the output to a file, but have trouble bringing it back to standard out, I have tried using dup, dup2 and close, but maybe I am using them wrong. Any help would be appreciated, thank you
. My problem begins at the if(myargc >= 3) block when I am trying to redirect the output.
main()
{
int i, myargc =0, background, newfile, file, stdout2, read = 0, write = 0;
pid_t pid;
char input[512], *myargv[60];
while(1)
{
background = 1;
printf("Myshell>");
gets(input);
//scanf("%s", input);
myargc = parser(input, myargv);
if(strcmp(*myargv, "exit") == 0)
{
exit(0);
}
if(strcmp(myargv[myargc-1], "&") == 0)
{
background = 0;
myargv[myargc-1] = '\0';
myargc--;
}
if(myargc >= 3)
{
if(strcmp(myargv[myargc-2], ">") == 0)
{
write = 1;
file = creat(myargv[myargc-1], S_IWUSR);
myargv[myargc-2] = '\0';
if(file < 0)
{
printf("File could not be created.\n");
}
printf("Redirecting output to file %s.\n", myargv[myargc-1]);
fflush(stdout);
stdout2 = dup(STDOUT_FILENO);
//fclose(stdout); // fclose() for type FILE*
newfile = dup2(file, 1); // uses lowest number descriptor (1, since just
// closed stdout)
close(file); // closes old file descriptor duplicate, close() uses int
myargc = myargc-2;
}
}
if ((pid = fork()) == -1 )
{
// if fork fails, print error and exit
perror("Fork failed");
exit(-1);
}
else if (pid == 0) { // child process
if (read == 0 && write == 0)
{
printf("This is the child ready to execute: ");
fflush(stdout);
for (i =0; i < myargc; i++)
{
printf("%s ", myargv[i]);
fflush(stdout);
}
printf("\n");
}
if (execvp(*myargv,myargv) < 0);
{
printf("Execution failed.");
}/* error exit - exec returned */
close(newfile);
dup2(stdout2, STDIN_FILENO);
//close(file);
//if (close(file) == 0)
//{
//dup2(newfile, STDOUT_FILENO);
//close(newfile);
//close(stdout2);
// printf("Reopened stdout\n");
// }
perror("Exec returned");
exit(-1);
}
close(newfile);
if (background == 1) { /* this is the parent -- wait for child to terminate */
wait(pid,0,0);
printf("The parent is exiting now\n");
}else{
waitpid(pid, NULL, WNOHANG); //returns immediately, no wait
}
//test for correct parsing
printf("myargv:\n");
for (i = 0; i < myargc; i++)
{
printf("%s\n", myargv[i]);
}
printf("myargc: %d\n", myargc);
// clear out buffers
memset(&myargv[0], 0, sizeof(myargv));
memset(&input[0], 0, sizeof(input));
}
}