My shell implementation overwriting when I don't want it to - c

I currently have a rudimentary implementation of Bash written in C. However, I'm getting issues when I try to redirect the standard output twice. Here is the relevant code:
Reading in each command:
for ( ; ; ) {
printf ("(%d)$ ", nCmd); // Prompt for command
fflush (stdout);
if ((line = getLine (stdin)) == NULL) // Read line
break; // Break on end of file
list = lex (line);
free (line);
if (list == NULL) {
continue;
} else if (getenv ("DUMP_LIST")) { // Dump token list only if
dumpList (list); // environment variable set
printf ("\n");
}
cmd = parse (list); // Parsed command?
freeList (list);
if (cmd == NULL) {
continue;
} else if (getenv ("DUMP_TREE")) { // Dump command tree only if
dumpTree (cmd, 0); // environment variable set
printf ("\n");
}
process (cmd); // Execute command
freeCMD (cmd); // Free associated storage
nCmd++; // Adjust prompt
}
The part of the shell we're my code is messing up:
if (cmdList->type==SIMPLE)
{
pid_t fork_result;
fork_result = fork();
if (fork_result < 0) {
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == 0) {
if (cmdList->fromType==RED_IN)
{
int fe = open(cmdList->fromFile, O_RDONLY, 0);
dup2(fe, 0);
close(fe);
}
if ((cmdList->toType==RED_OUT) || (cmdList->fromType==RED_APP))
{
int fd = open(cmdList->toFile, O_CREAT | O_WRONLY, 0666);
dup2(fd, 1);
close(fd);
}
execvp(cmdList->argv[0],cmdList->argv);
exit(EXIT_FAILURE);
}
else {
int status;
wait(&status);
}
}
This last snippet of code works exactly how I intend it to when I'm reading in just one simple command. However, the issue arises when I use the for loop to try to redirect stout twice. For example, I try to run:
cat Tests/star.wars > +Bash.tmp
cat +Bash.tmp
cat Tests/stk.txt > +Bash.tmp
cat +Bash.tmp
The first command writes, say, "ABC" to Bash.tmp. However, when I run the second command, I expect it to return "DE". However, I'm getting "DEC" as the output. What is wrong?

O_WRONLY is "write-only" permissions. O_TRUNC is what truncates the file on open.
– Etan Reisner

Related

Redirecting I/O in a custom shell program written in C

I have been working on a custom shell script and have come to a small error when redirecting output with the code given below. In its current state the code works perfectly but when passing to execvp args throws errors such as : (ls ">" no such file or directory). I know this is because it is passing the whole args[] to the parent shell which isn't working. Adding in the args[j] = NULL takes away the "<"/ ">" thus fixing the error, but also causes the redirections to not work anymore. How can I get it to not throw an error but also work properly? I have read multiple versions of this question but cant seem to find an answer. Thanks in advance for any help.
switch (fork()){
case -1:
fprintf(stderr, "error forking");
case 0://CHILD
for(int j = 0; j < size; j++){
if(!strcmp(args[j], "<")){//looking for input character
++ext;
if((in = open(args[j+1], O_RDONLY)) < 0){//open file for reading
fprintf(stderr, "error opening file\n");
}
dup2(in, STDIN_FILENO);//duplicate stdin to input file
close(in);//close after use
//args[j] = NULL;
}//end input chech
if(!strcmp(args[j],">")){//looking for output character
++ext;
out = creat(args[j+1], 0644);//create new output file
dup2(out, STDOUT_FILENO);//redirect stdout to file
close(out);//close after usere
// args[j] = NULL;
}//end output check
if(!strcmp(args[j], ">>")){//looking for append
++ext;
int append = open(args[j+1],O_CREAT | O_RDWR | O_APPEND, 0644);
dup2(append, STDOUT_FILENO);
close(append);
// args[j] = NULL;
}
}//end loop
execvp(args[0],args);//execute in parent
fprintf(stderr, "error in child execi \n");//error
exit(0);
default://PARENT
wait(&status); //wait for child to finish
}//end switch
When you are parsing redirections (e.g. <, >, >>) and doing your open/dup2, you have to strip them from the argument list you pass to execvp.
So, given your args, you need a second (e.g. args_clean) argument list that you only copy over the program name and its arguments.
And, you need an extra increment of j to skip over the redirection file in args (i.e. just doing j + 1 isn't equivalent).
Here's the cleaned up child code [please pardon the gratuitous style cleanup]:
char *args_clean[size];
int cleanidx = 0;
for (int j = 0; j < size; j++) {
if (!strcmp(args[j], "<")) { // looking for input character
++j;
if ((in = open(args[j], O_RDONLY)) < 0) { // open file for reading
fprintf(stderr, "error opening file\n");
}
dup2(in, STDIN_FILENO); // duplicate stdin to input file
close(in); // close after use
continue;
} // end input chech
if (!strcmp(args[j], ">")) { // looking for output character
++j;
out = creat(args[j], 0644); // create new output file
dup2(out, STDOUT_FILENO); // redirect stdout to file
close(out); // close after usere
continue;
} // end output check
if (!strcmp(args[j], ">>")) { // looking for append
++j;
int append = open(args[j], O_CREAT | O_RDWR | O_APPEND, 0644);
dup2(append, STDOUT_FILENO);
close(append);
continue;
}
args_clean[cleanidx++] = args[j];
} // end loop
args_clean[cleanidx] = NULL;
execvp(args_clean[0], args_clean); // execute in parent
fprintf(stderr, "error in child execi \n"); // error
exit(0);
Also, see my answer here for something similar with pipes: fd leak, custom Shell
And, for a full blown shell, see my answer: Implementing input/output redirection in a Linux shell using C and look at the embedded pastebin link

Cat command didn't work correctly with execvp

1) I can't use some commands in this code, such as : cat somefile.txt > somefile2.txt
also I can't use : cat somefile.txt | less
2) When I use commands like : ( cd ../) (cd ./Desktop) and then I want to exit the program, I need to execute exit commands more than one time: ie "if I use 3 cd command, I will need 3 exit commands to end the program"
#define MAX_ARGS 5
// Global Declarations
// Mini Functions
void remove_new_line_char(char line[])
{
int i=0;
while(line[i]!= '\n')
i++;
line[i] = '\0';
}
// Grand Functions
int read_line(char line[])
{
fgets(line, 10000, stdin); // File Get String
remove_new_line_char(line); // Remove New Line Charactere
if (strlen(line) > 512)
{
fprintf(stderr,"The Command exceeded available line length\n");
return 0;
}
if (strcmp(line, "exit") == 0)
exit(0);
return 1;
}
int parse_line(char* args[], char line[])
{
int i=0;
args[i] = strtok(line, " ");
if(args[i] == NULL)
{
printf("Command Line is Empty!\n");
return -1;
}
while (args[i] != NULL)
{
int flag = 0;
if(strcmp(args[i],"&") == 0)
flag = 1;
i++;
args[i] = strtok(NULL, " "); // NULL maintains a static pointer to the previously passed string.
if (args[i] == NULL && flag == 1)
{
args[i-1] = NULL; // Remove & From Argument List and Set Background Flag.
return 1;
}
}
return 0;
}
// Main
int main()
{
char* args[MAX_ARGS]; // Array of Strings
char line[10000]; // String
while(1)
{
printf("Shell> ");
if(read_line(line) == 1) // No Errors
{
int background = parse_line(args, line);
if(background != -1) // Command Line isn't Empty
{
// Fork and Execute
pid_t child_pid = fork();
if(child_pid == 0) // Child
{
if (strcmp(args[0], "cd") == 0 && args[1]!= NULL && args[2] == NULL) // Special Handling For CD
{
//printf("%s\n",args[2]);
int check = chdir(args[1]);
if(check == -1)
fprintf(stderr, "Invalid Directory\n");
}
// Handle if args[1]== NULL, Don't even execvp()
else // Other Functions
{
execvp(args[0], args); // args[0] is actually the command.
fprintf(stderr,"an error occured in execution\n%s\n",strerror(errno));
//fprintf(stderr,"Invalid Instruction\n");
}
}
else // Parent
{
if(background == 0)
waitpid(child_pid, 0);
wait(1000);
}
}
}
}
return 0;
}
I suspect that I can't use any command that has characters like: > < |
Thanks in advance
1) i can't use some commands in this code as : cat somefile.txt > somefile2.txt also i can't use : cat somefile.txt | less
In the standard shell, the > and | are operators interpreted by the shell, not arguments to the command. Since in this case the shell is your program itself, if you must support those operators then you'll need to implement the appropriate redirections yourself. Refer to open(), pipe(), and dup2(), and for the pipe case you'll also need judicious application of close().
2)when i use commands like : ( cd ../) (cd ./Desktop) and then i want to exit the program, i need to execute exit command more than one time "if i use 3 cd command i will need 3 exit command to end the program"
In the special case of the cd command, you fork and then change directory in the child process, but the child does not terminate or exec another process. That leaves you with two copies of your shell running. You need to exit both before control returns to whatever process launched your program. Possibly in that case you want to instead execute chdir without forking (or waiting for a child).

Implementation of Redirection in User-Created Shell is not working properly

I am trying to create a shell similar to bash, with redirection and pipes.
In my main(), I call a parser and then call the function below. The problem I am running into is the fact that when I run my shell, it outputs to the terminal correctly, but it does not output to the file correctly when using the >, 1>, or 2>.
For instance, if I call:
pwd > foo5.txt
I end up receiving:
>
foo5.txt
In the text file I write too as opposed to the stdout (for ">"/"1>") or stderr (for "2>") which I am trying to achieve.
This is my code to fork and create the child process:
pid_t create_process(char *part, int const pipes[][2], int pipenum)
{
pid_t pid; // Initialize variables/pointers/arrays.
char *args[64];
int argc=0, n;
char *arg=strtok(part, " \t");
//char const **filename = args;
while(arg != NULL)
{
args[argc++]=arg;
arg=strtok(NULL, " \t");
}
args[argc++]=NULL;
pid = fork(); // Create Fork.
if(pid == 0)
{
int m;
if(pipes[pipenum][STDIN_FILENO] >= 0)
dup2(pipes[pipenum][STDIN_FILENO], STDIN_FILENO); // FD 0.
if(pipes[pipenum][STDOUT_FILENO] >= 0)
dup2(pipes[pipenum][STDOUT_FILENO], STDOUT_FILENO); // FD 1.
// Close all pipes.
for(m=0; m<64; m++)
{
if(pipes[m][STDIN_FILENO] >= 0)
close(pipes[m][STDIN_FILENO]);
if(pipes[m][STDOUT_FILENO] >= 0)
close(pipes[m][STDOUT_FILENO]);
}
char *filename;
char *newargs[64];
newargs[63] = NULL;
int i = 0;
int j = 0;
for(i = 0; i<64; i++)
{
if (args[i] == ">")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 1);
close(redir);
}
}
else if (args[i] == "2>")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 2);
close(redir);
}
}
else if (args[i] == "2>")
{
i++;
if (args[i] != NULL)
{
filename = args[i];
int redir = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
dup2(redir, 2);
close(redir);
}
}
else if (args[i] == 0)
{
break;
}
else
{
newargs[j] = args[i];
j++;
cout<<"The arg is: " << newargs[j] <<endl;
}
}
execvp(newargs[0], newargs);
fprintf(stderr, "Command not found.\n");
exit(255);
}
else if(pid < 0)
{ // Error checking.
fprintf(stderr, "Fork Failed\n");
}
return(pid);
}
UPDATE: Now my code will not recognize the commands, and the arguments being printed (for error checking) appear as such:
ls > foo5.txt
The arg is: fprintf
The arg is:
The arg is: ▒
Command not found.
I see a few problems here:
First, you have the for loop where you scan the command arguments for redirection syntax (if (strcmp(args[i],">")==0) and so on) - but if the condition is true (meaning you found a redirection character) you're always opening args[2], not args[i+1].
Second (and this is why the redirection syntax gets passed on to the command you're running as command arguments) - once you detect redirection syntax, you don't remove the redirection operator or the target filename from the list of arguments that you pass to execvp().
For instance, if args[] = {"echo", "a", ">", "logfile", 0}, your code detects a request to send the output to a new file called "logfile" and redirects the FDs correctly, but it still passes those three arguments ["a", ">", "logfile"] to the command.
Third - in your loop, you're calling execvp() at the end of each conditional statement - meaning that you don't get to the end of argument processing before you launch the new process. You need to process all the command arguments for shell syntax and then exec the command.
To fix the various problems with arg handling, probably the most effective solution is to build a new argument list as you're processing the raw ones provided by the user. For instance (using a==b as a string equality test for brevity)
if (args[i] == ">")
{
i++; //skip the arg
if (args[i]) { // check we haven't hit the end of the arg list
filename = args[i];
// then open the file, dup it to stdout or whatever, etc...
}
else if (args[i] == "<") // repeat for other redirection syntax...
else { // Finally, handle the case where we didn't identify any shell syntax:
newargs[j] = args[i]; // Copy the arg to the new list, since it isn't "special"
j++; // Size of newargs[] has been increased
}
Note that this still doesn't handle things like no whitespace around the ">" character: "echo foo>file" will just print "foo>file"... Syntax processing gets a little more complicated in that case, as you've got to account for quoting rules and escape characters to process the arguments correctly.

Unix Shell Implementing Cat in C - File Descriptor Issue

I've about got my practice implementation of a Unix shell done, except I'm having an issue with implementing cat when its output is to a file; IE: cat foo.txt > bar.txt - outputting foo's contents to bar.
Let's start from the main function & then I'll define the submethods:
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
handle_user_input();
//determine input and execute foreground/background process
execute_command();
}
background = 0;
}
printf("\n[MYSHELL] $ ");
return 0;
}
handle_user_input just populates the cmd_argv array to execute the user_input, and removes the > and sets an output flag if the user wishes to output to a file. This is the meat of that method:
while (buffer_pointer != NULL) {
cmd_argv[cmd_argc] = buffer_pointer;
buffer_pointer = strtok(NULL, " ");
if(strcmp(cmd_argv[cmd_argc], ">") == 0){
printf("\nThere was a '>' in %s # index: %d for buffer_pointer: %s \n", *cmd_argv,cmd_argc,buffer_pointer);
cmd_argv[cmd_argc] = strtok(NULL, " ");
output = 1;
}
cmd_argc++;
if(output){
filename = buffer_pointer;
printf("The return of handling input for filename %s = %s + %s \n", buffer_pointer, cmd_argv[0], cmd_argv[1]);
return;
}
}
execute_command is then called, interpreting the now populated cmd_argv. Just to give you an idea of the big picture. Obviously, none of these cases match and the create_process method is called:
int execute_command()
{
if (strcmp("pwd", cmd_argv[0]) == 0){
printf("%s\n",getenv("PATH"));
return 1;
}
else if(strcmp("cd", cmd_argv[0]) == 0){
change_directory();
return 1;
}
else if (strcmp("jobs", cmd_argv[0]) == 0){
display_job_list();
return 1;
}
else if (strcmp("kill", cmd_argv[0]) == 0){
kill_job();
}
else if (strcmp("EOT", cmd_argv[0]) == 0){
exit(1);
}
else if (strcmp("exit", cmd_argv[0]) == 0){
exit(-1);
}
else{
create_process();
return;
}
}
Pretty straight forward, right?
create_process is where I'm having issues.
void create_process()
{
status = 0;
int pid = fork();
background = 0;
if (pid == 0) {
// child process
if(output){
printf("Output set in create process to %d\n",output);
output = 0;
int output_fd = open(filename, O_RDONLY);
printf("Output desc = %d\n",output_fd);
if (output_fd > -1) {
dup2(output_fd, STDOUT_FILENO);
close(output_fd);
} else {
perror("open");
}
}
printf("Executing command, but STDOUT writing to COMMAND PROMPT instead of FILE - as I get the 'open' error above \n");
execvp(*cmd_argv,cmd_argv);
// If an error occurs, print error and exit
fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
exit(0);
} else {
// parent process, waiting on child process
waitpid(pid, &status, 0);
if (status != 0)
fprintf (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
}
return;
}
My printed output_fd = -1, and I manage to get the perror("open") inside the else stating: open: No such file or directory. It then prints that it's "writing to COMMAND PROMPT instead of FILE", as I display to the console. Then executes execvp which handles cat foo.txt, but prints it to the console instead of the file.
I realize it shouldn't at this point, as having output_fd = -1 isnt desirable and should be returning another value; but I cant figure out how to use file descriptors correctly in order to open a new/existing file with cat foo.txt > bar.txt and write to it, as WELL AS GET BACK to the command line's stdin.
I have managed to output to the file, but then lose getting back the correct stdin. Could someone please direct me here? I feel like I'm going in circles over something silly I'm doing wrong or looking over.
Any help is greatly GREATLY appreciated.
Why do you use O_RDONLY if you want to write to the file? My guess is that you should use something like:
int output_fd = open(filename, O_WRONLY|O_CREAT, 0666);
(The 0666 is to set up the access rights when creating).
And obviously, if you can't open a redicted file, you shouldn't launch the command.
First, obvious thing I notice is that you've opened the file O_RDONLY. Not going to work so well for output!
Second, basic process for redirecting the output is:
open file for writing
dup stdout so you can keep a copy if needed. same with stderr if redirecting.
fcntl your duplicate to CLOEXEC (alternatively, use dup3)
dup2 file to stdout
exec the command
and finally, are you really passing around command names as global variables? I think this will come back to haunt you once you try and implement cat foo | ( cat bar; echo hi; cat ) > baz or somesuch.

Multi Piping bash-style in C

I know there are many threads that talk about this problem but I don't really understand the way it can be done.
I'm trying to make a shell that can execute a linux command sucha as ps | grep | less
I've donne the parsing by puting every command and its args in a simply linked list.
Here's my implementation that doesn't work. Hope that's clear enough.
if ((son = fork()) < 0)
return printerr_sys("Unable to fork", 0);
if (son == 0)
{
if (first > 1 && data->format[first - 1] &&
is_directing_elt(data->format[first - 1]) == DIRECT_TPIPE)
dup2(tube_p[0], STDIN_FILENO);
first = make_argv(data, first, &argv);
if (next)
{
dup2(tube_v[1], STDOUT_FILENO);
close(tube_v[0]);
}
if (execvp(argv[0], argv) < 0)
return printerr_cmd(argv[0], 1);
}
else
{
if (next)
{
close(tube_v[1]);
cmdline_executer(data, next, tube_v);
}
waitpid(son, &(data->lastcmd), WUNTRACED);
data->lastcmd = WEXITSTATUS(data->lastcmd);
}
return TRUE;
My questions are:
What would be the correct implementation?
Is it possible to do it with recursion?
Do I need to fork from right to left or left to right (logically it give the same result)?
Here's a part of a UNIX Shell I had to implement in C for Operating System subject in my Computer Science career.
/* Executes the command 'buffer' assuming that doesn't contain redirections */
void execute_only_pipes(char* buffer)
{
char *temp = NULL, *pipeCommands[MAX_PIPES], *cmdArgs[MAX_ARGUMENTS];
int newPipe[2], oldPipe[2], pipesCount, aCount, i, status;
pid_t pid;
pipesCount = -1; /* This variable will contain how many pipes the command contains */
/* Counting the number of pipes and splitting them into pipeCommands */
do
{
temp = strsep(&buffer, "|");
if(temp != NULL)
{
if(strlen(temp) > 0)
{
pipeCommands[++pipesCount] = temp;
}
}
} while(temp);
cmdArgs[++pipesCount] = NULL;
for(i = 0; i < pipesCount; i++) /* For each command */
{
aCount = -1;
/* Parsing command & arguments */
do
{
temp = strsep(&pipeCommands[i], " ");
if(temp != NULL)
{
if(strlen(temp) > 0)
{
/* If a parameter is ~, then replace it by /home/user */
if (!strcmp(temp, "~"))
strcpy(temp, home);
cmdArgs[++aCount] = temp;
}
}
} while(temp);
cmdArgs[++aCount] = NULL;
/* If there still are commands to be executed */
if(i < pipesCount-1)
{
pipe(newPipe); /* just create a pipe */
}
pid = fork();
if(pid == 0) /* Child */
{
/* If there is a previous command */
if(i > 0)
{
close(oldPipe[1]);
dup2(oldPipe[0], 0);
close(oldPipe[0]);
}
/* If there still are commands to be executed */
if(i < pipesCount-1)
{
close(newPipe[0]);
dup2(newPipe[1], 1);
close(newPipe[1]);
}
/* Execute it */
int res = execvp(cmdArgs[0], cmdArgs);
if (res == -1)
{
printf("Error. Command not found: %s\n", cmdArgs[0]);
}
exit(1);
}
else /* Father */
{
/* If there is a previous command */
if(i > 0)
{
close(oldPipe[0]);
close(oldPipe[1]);
}
/* do we have a next command? */
if(i < pipesCount-1)
{
oldPipe[0] = newPipe[0];
oldPipe[1] = newPipe[1];
}
/* wait for last command process? */
if(i == pipesCount-1)
{
waitpid(pid, &status, 0);
}
}
}
}
It might be a little buggy (I'm not checking if fork() < 0, etc) but the main idea is correct.
> Is it possible to do it with recursion?
Most of the time I try to avoid recursion, if I can write a similar understandable code without using it.
Processes run independently, so you need to set up the pipe for at least the first pair of commands before you fork, but you're doing that in the child (son == 0). You could code a recursive solution that, as long as there are at least two commands left, creates a pipe, then forks, then runs the first command.

Resources