I'm writing a shell script for class in C and I got my program to execute all commands except for file redirection. Can someone help figure out how I need to go about file redirection, I know I need to do it when fork returns 0, but I'm not sure how to do it.
Any help would be appreciated.
CODE:
Here is an updated version of my code. Creates the file fine but then i'm not sure what to do after. I close the correct file descriptors I think then duplicate my file descriptor to the lowest open descriptor. after dup besides closing the correct file descriptor can you help me what to do?
args[] is the array that i store the input (i.e. ls -l, ls -l > out)
if ((cpid = fork()) == 0)
{
for ( i = 0; i < size; i++)
{
if ( *args[i] == '<' )
{
if ((fd = open(args[i+1], O_RDONLY)) == -1)
{
perror("open");
exit(2);
}
else
{
close(0);
dup(fd);
close(fd);
}
}
if ( *args[i] == '>' )
{
if ((fd = open(args[i+1], O_WRONLY | O_CREAT, 0644)) == -1)
{
perror("open");
exit(2);
}
else
{
close(1);
dup(fd);
close(fd);
}
}
}
execve(path,args,environ);
perror("execve");
exit(2);
}
Check out the dup function here
Related
I'm making a simple shell using c and when trying to redirect the output to a file, I cannot open that file or view its content to make sure my redirection actually worked, that is after exiting the simple shell and returning to the "actual" shell. I get Output.txt: Permission denied
Does anyone know what is happening? Here is part of my code
Note that redir is 1 when in args[] we have '>'.
while(1) {
bg = 0;
int cnt = getcmd("\n>> ", args, &bg);
int pid = fork();
if(pid == 0) {
if (redir == 1) {
int stdout = dup(1);
close(1);
int fd = open(args[2], O_CREAT | O_TRUNC | O_WRONLY, S_IRWXU);
dup2(fd,1);
redir = 0;
}
if (execvp(args[0], args) < 0) {
printf("exec failed\n");
exit(1);
}
exit(0);
}
else if (pid == -1){
printf("ERROR: fork failed\n");
exit(1);
}
}
I'm trying to re-implement The pipe "|" operator.
The program will be executed as follows:
$ ./exe infile cmd cmd2 cmd3 cmdn outfile.
where at first i'm gonna read from infile process infile's data through the commands and finally pipe it to outfile.
IMPLEMENTAIONS:
My pseudo code looks like the following:
change infile descriptor to stdin.
loop each command.
pipe();
fork();
if (we're in the child process)
change stdin to read_end of pipe.
execute command.
else if (we're in the parent process)
change stdout to write_end of pipe.
execute command.
wait for child process.
change outfile descriptor to stdout.
CODE:
int infile_fd = open(args->infile, O_RDONLY);
dup2(infile_fd, STDIN_FILENO);
// how many commands
while(i < args->len)
{
// error handling stuff
args->cmds[i].cmd = check_exist(args->cmds[i], args);
if (pipe(args->cmds[i].fd) == -1)
{
perror("piping failed\n");
exit(EXIT_FAILURE);
}
if ((args->cmds[i].pid = fork()) == -1)
{
perror("fork failed\n");
exit(EXIT_FAILURE);
} else {
// child process
if (args->cmds[i].pid == 0)
{
close(args->cmds[i].fd[WRITE_END]);
dup2(args->cmds[i].fd[READ_END], STDIN_FILENO);
if (execve(args->cmds[i].cmd, args->cmds[i].flags, env) == -1)
{
perror("execve failed\n");
exit(EXIT_FAILURE);
}
i++;
}
else
{
// parent process
close(args->cmds[i].fd[READ_END]);
dup2(args->cmds[i].fd[WRITE_END], STDOUT_FILENO);
if (execve(args->cmds[i].cmd, args->cmds[i].flags, env) == -1)
{
perror("execve failed\n");
exit(EXIT_FAILURE);
}
wait(NULL);
i++;
}
}
}
int outfile_fd = open(args->outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
// change stdout to outfile
dup2(outfile_fd, STDOUT_FILENO);
OUTPUT:
output is garbage, commands read data off the stack and start showing env variables.
EXPECTED OUTPUT:
data first being read from "infile" and then passed through commands until the end of the piping channel at "outfile".
It would be silly to ask what am I doing wrong, because I'm probably doing it all wrong, so a better question: How can I do it right?
I have a problem with dup2 syscall.
I added a while loop(a part of my program) that running over a students directory,
each student has a "c" file that I compile to an "exe" file. the students' programs scan from
testInput file (using dup2 to take the keyboard) 2 numbers, add them and then the answers is written down to programOutPut file.
afterward, I compare the 2 programs with a comparison program I wrote and it helps me with WEXITSTATUS to know whether a student succeeded in the test.
The problem is that I try to write the grade sheet into the file and then print it also to the screen.
somehow it only appears on the file or the screen but not both.
while (myDirent = readdir(dir)) {
if (strcmp(myDirent->d_name, ".") == 0 || strcmp(myDirent->d_name, "..") == 0)
continue;
if ((pid = fork()) == 0) {
status=execlp("gcc", "gcc", "-o", mainPath, cPath, NULL); //compiling students code to main.out path
if (status == -1) {
fprintf(stderr, "gcc Exec failed\n");
exit(-1);
}
}
wait(&status);
fdin = open(testInputPath, O_RDONLY); //test input file I wrote to compare with student's output
if(fdin==-1){
fprintf(stderr, "Error opening test input file\n");
exit(-1);
}
fdout = open(programOutputPath, O_WRONLY | O_CREAT | O_TRUNC,0777); //opening file for each student
if(fdout==-1){
fprintf(stderr, "Error opening Student's program output file\n");
exit(-1);
}
if ((pid = fork()) == 0) {
dup2(fdin, 0);
dup2(fdout, 1);
status= execlp(mainPath,mainPath, NULL);
if (status == -1) {
fprintf(stderr, "Student's main Exec failed\n");
exit(-1);
}
}
wait(&status);
fdresults = open("results.txt", O_WRONLY | O_CREAT | O_APPEND, 0777); //grades sheet
if (fdresults == -1) {
fprintf(stderr, "Error opening results.csv file\n");
exit(-1);
}
if ((pid = fork()) == 0) {
status= execlp("./comp.out", "./comp.out", programOutputPath, expectedOutputPath, NULL); //compare program I wrote that simply compare 2 files and return value to status
if (status == -1) {
fprintf(stderr, "Compare Exec failed\n");
exit(-1);
}
}
wait(&status);
**dup2(fdresults, 1); //trying to write to the file the grades
printf("%s,%d\n", myDirent->d_name,WEXITSTATUS(status));
dup2(fdscreen, 1); // trying to return to stdout unsuccessfuly**
}//end of while loop
First, do not open a file that is already open (source). Right now, you are opening testInputPath and programOutputPath during each iteration of the the loop without closing them. This can lead to undefined behavior.
It's unclear where fdscreen came from. If you do something similar to this answer, then you can achieve what you're looking for using dup(2). You should flush the corresponding userspace file buffer using fflush(3) before calling dup2.
A Better Solution
A much better solution is to open results.txt outside the loop, and then use dprintf(3) to write directly to the file. This is more elegant, and avoids having to change stdout. So, you can easily replace the last three lines of the loop with
dprintf(fdresults, "%s,%d\n", myDirent->d_name, WEXITSTATUS(status));
Don't forget to close fdresults after the loop is complete. In general, each open(2) should have a corresponding close(2). Using this approach is strongly encouraged over changing stdout every iteration of the loop.
Round 2
After reading some of the answers, my revised code is:
int pid = fork();
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
if (in) { //if '<' char was found in string inputted by user
int fd0 = open(input, O_RDONLY, 0);
dup2(fd0, STDIN_FILENO);
close(fd0);
in = 0;
}
if (out) { //if '>' was found in string inputted by user
int fd1 = creat(output, 0644);
dup2(fd1, STDOUT_FILENO);
close(fd1);
out = 0;
}
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
free(res);
}
It works, but seems the standard output isn't being reconnected or something to that effect. Here is execution:
SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output
'<' and '>' both work, but after they are executed there is no output.
Round 1
I have been working on a relatively simple shell in C for a while now, but I am having trouble implementing input (<) and output (>) redirection. Help me find the issues in the following code:
int fd;
int pid = fork();
int current_out;
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_out = dup(0);
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
dup2(current_out, 1);
free(res);
}
I may have some unnecessary material in there because I have been trying different things to get it to work. I am not sure what is going wrong.
You have way too many file descriptors open after your redirection. Let's dissect the two paragraphs:
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_in = dup(0); // Fix for symmetry with second paragraph
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
I'm going to be charitable and ignore the fact that you are ignoring errors. However, you will need to error check your system calls.
In the first paragraph, you open a file and capture the file descriptor (it might well be 3) in the variable fd. You then duplicate the file descriptor over standard input (STDIN_FILENO). Note, though, that file descriptor 3 is still open. Then you do a dup(0) (which, for consistency, should be STDIN_FILENO), getting another file descriptor, perhaps 4. So you have file descriptors 0, 3 and 4 pointing at the same file (and, indeed, the same open file description — noting that an open file description is different from an open file descriptor). If your intention with current_in was to preserve the (parent) shell's standard input, you have to do that dup() before you do the dup2() that overwrites the output. However, you would be better off not altering the parent shell's file descriptors; it is less overhead than re-duplicating the file descriptors.
Then you more or less repeat the process in the second paragraph, first overwriting the only record of file descriptor 3 being open with the fd = creat(...) call but obtaining a new descriptor, perhaps 5, then duplicating that over standard output. You then do a dup(1), yielding another file descriptor, perhaps 6.
So, you have stdin and stdout of the main shell redirected to the files (and no way of reinstating those to the original values). Your first problem, therefore, is that you are doing the redirection before you fork(); you should be doing it after the fork() — though when you get to piping between processes, you will need to create pipes before forking.
Your second problem is that you need to close a plethora of file descriptors, one of which you no longer have a reference for.
So, you might need:
if ((pid = fork()) < 0)
...error...
else if (pid == 0)
{
/* Be childish */
if (in)
{
int fd0 = open(input, O_RDONLY);
dup2(fd0, STDIN_FILENO);
close(fd0);
}
if (out)
{
int fd1 = creat(output , 0644) ;
dup2(fd1, STDOUT_FILENO);
close(fd1);
}
...now the child has stdin coming from the input file,
...stdout going to the output file, and no extra files open.
...it is safe to execute the command to be executed.
execve(cmd[0], cmd, env); // Or your preferred alternative
fprintf(stderr, "Failed to exec %s\n", cmd[0]);
exit(1);
}
else
{
/* Be parental */
...wait for child to die, etc...
}
Before you do any of this, you should ensure that you've already flushed the shell's standard I/O channels, probably by using fflush(0), so that if the forked child writes to standard error because of a problem, there is no extraneous duplicated output.
Also note that the various open() calls should be error-checked.
You have way too many file descriptors open after your redirection. The code which you need is this.
if (pid == 0)
{ /* for the child process: */
// function for redirection ( '<' , '>' )
int fd0,fd1,i,in=0,out=0;
char input[64],output[64];
// finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that
for(i=0;argv[i]!='\0';i++)
{
if(strcmp(argv[i],"<")==0)
{
argv[i]=NULL;
strcpy(input,argv[i+1]);
in=2;
}
if(strcmp(argv[i],">")==0)
{
argv[i]=NULL;
strcpy(output,argv[i+1]);
out=2;
}
}
//if '<' char was found in string inputted by user
if(in)
{
// fdo is file-descriptor
int fd0;
if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
perror("Couldn't open input file");
exit(0);
}
// dup2() copies content of fdo in input of preceeding file
dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0
close(fd0); // necessary
}
//if '>' char was found in string inputted by user
if (out)
{
int fd1 ;
if ((fd1 = creat(output , 0644)) < 0) {
perror("Couldn't open the output file");
exit(0);
}
dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
close(fd1);
}
execvp(*argv, argv);
perror("execvp");
_exit(1);
// another syntax
/* if (!(execvp(*argv, argv) >= 0)) { // execute the command
printf("*** ERROR: exec failed\n");
exit(1);
*/
}
else if((pid) < 0)
{
printf("fork() failed!\n");
exit(1);
}
else { /* for the parent: */
while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors)
}
}
Here's what's happening. After you call fork() there are two processes executing that are duplicates of the original process. The difference is in the return value of fork() which is stored in pid.
Then both processes (the shell and the child) redirect their stdin and stdout to the same files. I think you were trying to save the previous fd in current_out, but as Seth Robertson points out, this doesn't currently work, since the wrong file descriptor is being saved. The parent also restores its stdout, but not stdin.
You could fix this bug, but you can do better. You don't actually have to redirect parent's output, just the child's. So simply check pid first. Then there is also no need to restore any file descriptors.
I'm working on a simple shell, but right now I am just trying to understand redirection. I'm just hard coding an ls command and trying to write it to a file for now. Currently, the ls runs, and the output file is created, but the output still goes to stdout and the file is blank. I'm confused as to why. Thanks in advance.
Here is my code:
int main()
{
int ls_pid; /* The new process id for ls*/
char *const ls_params[] = {"/bin/ls", NULL}; /* for ls */
int file; /* file for writing */
/* Open file check user permissions */
if (file = open("outfile", O_WRONLY|O_CREAT) == -1) /* , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP */
{
perror("Failed to open file");
_exit(EXIT_FAILURE);
}
ls_pid = fork(); /* Create new process for ls */
if (ls_pid == -1) /* error check */
{
perror("Error forking ls (pid == -1)");
_exit(EXIT_FAILURE);
}
else if (ls_pid == 0) /* Child of ls */
{
/* Redirect output to file */
if (dup2(file, STDOUT_FILENO) == -1) /* STDOUT_FILENO = 1 */
{
perror("Error duping to file");
_exit(EXIT_FAILURE);
}
close(file);
execvp("ls", ls_params); /* create the sort process */
/* execlp("ls", "ls", NULL); */
/* if this does not end the program, something is wrong */
perror("Exec failed at sort");
_exit(EXIT_FAILURE);
}
else /* ls parent (1) */
{
/* wait for child */
if (wait(NULL) == -1)
{
perror("fork failed on parent");
_exit(EXIT_FAILURE);
}
}
}
I don't see any problem. I tried your code by myself. And it works! My simplified code looks like:
int main(void) {
int fd;
char* const param[] = {"/bin/ls", NULL};
fd = open("outfile", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0) {
perror("open failed\n");
exit(0);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork failed\n");
exit(0);
}
else if (pid == 0) {
dup2(fd, STDOUT_FILENO);
close(fd);
execvp("ls", param);
}
else {
wait(NULL);
}
}
I compile an execute it, and find the expected results in outfile.
The only difference is that I feed open with the S_IRUSER | S_IWUSER permission options since otherwise the open fails. I see similar thing in your code but somehow you commented them...