Unix Shell Implementing Cat in C - File Descriptor Issue - c

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.

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

Saving commands in a user-created shell to a file before executing execvp in C

I made a shell and everything works great except I can't seem to save executable commands in a history file. However, all the invalids commands get saved.
Here is the child portion:
// child
} else if (pid == 0){
for (int i = 0; i < len_of_arguments; i++){
if ( arguments[i + 1] == NULL ){
fprintf(Shell_hist, "%s", arguments[i]);
} else {
fprintf(Shell_hist, "%s ", arguments[i]);
}
}
fprintf(Shell_hist, "\n");
execvp(arguments[0], arguments);
fprintf(stderr, "ERROR: command does not exist.\n");
exit(SUCCESS);
Can someone please explain to me why the for loop won't run with successful executables? Shouldn't the for loop always execute before the execvp()?

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).

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

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

How to do shell redirection C

I have a tokenizer that takes in input from the user. From there I went on to scan for ">" or "<" for potential redirection.
for(i=0;i<n;i++){ //I/O redirection
//printf("extracted token is %s\n",tokens[i]);
//get the
if (strcmp(tokens[i],"<")==0) {
printf("found < !!!!\n");
if(tokens[i+1] == NULL){ //if there isn't a input file
printf("Please have a input file\n");
break;
}else if(tokens[i-1] == NULL){ //if there isn't a output file
printf("Pleae have a output file\n");
break;
}
infile = 1;
outfile = 1;
fd=fopen(tokens[i-1],"w");
fclose(fd);
}
}
The above code just deals with "<" redirection. Note that this is just a small snippet of the code that is wrapped around a while loop (shell design). After it passes this for loop, I have this:
for(i=0;i<n;i++){
//printf("extracted token is %s\n",tokens[i]);
char *ex = "exit";
char *his = "history";
if(strcmp(tokens[0],his) == 0 && max_cmd == 0 ){ //get history
for(j=0;j<counter;j++){
printf("%i. %s\n",j+1,historic_cmd[j]);
}
}else if(strcmp(tokens[0], his) ==0 && max_cmd == 1){
for(j=0; j<CMD_MAX;j++){
printf("%i. %s\n",j+1,historic_cmd[j]);
}
}else if(strcmp(tokens[0],ex) == 0){ //exit program
exit(2);
}else{ //forking
pid = fork();
if(pid){
pid=wait(NULL);
if(infile > 0)
dup2(fileno(fd),0);
if(outfile > 0)
dup2(fileno(fd),1);
}else{
if(execvp(tokens[0],tokens)){
puts(strerror(errno));
exit(127);
}
}
}
} // end of for loop
}//end of while loop for user input
I am confused on why it isn't performing the redirection. If I type in the following:
ps > s
It creates the file s in the working directory, but its empty. Am I using "dup2" incorrectly?
Sample output from "ps > s":
user$>ps > s
ps: illegal option -- >
usage: ps [-AaCcEefhjlMmrSTvwXx] [-O fmt | -o fmt] [-G gid[,gid...]]
[-u]
[-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]
ps [-L]
ps: illegal option -- >
usage: ps [-AaCcEefhjlMmrSTvwXx] [-O fmt | -o fmt] [-G gid[,gid...]]
[-u]
[-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]
ps [-L]
ps: illegal option -- >
usage: ps [-AaCcEefhjlMmrSTvwXx] [-O fmt | -o fmt] [-G gid[,gid...]]
[-u]
[-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]
ps [-L]
found > !!!!!----------------------------------------------------
You replace stdin and stdout of your parent process (within the if(pid)), and it doesn't affect your child. Move dup2 stuff to the else branch, right before execvp.
This is the flow you should be using to do redirection in C.
int file_stdin;
int file_stdout;
int file_stderr;
/*--------------------------------------------------------------------
; code to set infile and outfile I'll assume that if redirection isn't
; being used, the appropriate filehandle will be -1.
;
; file_stdin should be a file that is opened using O_RDONLY.
; file_stdout should be a file that is opened using O_WRONLY.
; file_stdout should be a file that is opened using O_WRONLY.
;
; Be wary of using a file descriptor opened for both reading and writing
; and using the same file descriptor---it will probably not do what you
; expect it to do.
;
; Also note, you can also use file descriptors from the pipe() system
; call (to allow piping from command to command) or network sockets, or
; anything that can be read/written from a file descriptor.
;---------------------------------------------------------------------*/
pid_t child;
child = fork();
if (child < 0)
report_error(errno);
else if (child > 0)
{
int status;
int rc;
/*------------------------------------------------------------------
; wait for the child process. More code needs to be added to
; descriptor errors and what not, but that's left as an exercise for
; the reader
;-----------------------------------------------------------------*/
rc = wait(&status);
}
else
{
/*--------------------------------------------------------------------
; child process. Set up redirection if required. Once the rediretion
; has been set (using dup2()), the original file descriptor is closed
; as it is no longer needed and just wastes file descriptors in the
; child process.
;-------------------------------------------------------------------*/
if (file_stdin > -1)
{
dup2(file_stdin,STDIN_FILENO);
close(file_stdin);
}
if (file_stdout > -1)
{
dup2(file_stdout,STDOUT_FILENO);
close(file_stdout);
}
if (file_stderr > -1)
{
dup2(file_stderr,STDERR_FILENO);
close(file_stderr);
}
/*------------------------------------------------------------------
; close any unneeded open files here ...
;------------------------------------------------------------------*/
/*------------------------------------------------------------------
; PROGRAM, ARGV and ENVP need to be replaced with appropriate values
;------------------------------------------------------------------*/
if(execve(PROGRAM,ARGV,ENVP) < 0)
_exit(EXIT_FAILURE);
}
Obviously, the code above will need to be adapted to your project, but this is the general flow.

Resources