I'm building a small linux shell and am trying to implement the > operator to redirect the output of the commands to a file.
The issue I have is that when I try to run something like ls > test.txt, I get a Bad Address (EFAULT) error.
However, if I try it without the redirection, everything works as expected.
I've trimmed the code to a minimum to test only for ls, but i still get the same error, here's the code.
int saved_stdout;
__pid_t id = fork();
if (id == 0) {
saved_stdout = dup(1);
int fd = open("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
char* args[] = {"\0"};
execvp("ls", args);
fprintf(stderr, "Value of errno: %d\n", errno);
perror("Error printed by perror");
} else {
int status;
waitpid(id, &status, 0);
if (saved_stdout) {
dup2(saved_stdout, 1);
close(saved_stdout);
}
}
Does anyone as an idea on what I'm doing wrong here?
Thanks a lot
The execvp function expects the argument array to be terminated by a null pointer, not an empty string.
You should also remember that the argument array includes argv[0].
So the array should be like
char* args[] = { "ls", NULL };
Does anyone as an idea on what I'm doing wrong here?
The main problem is likely that your arguments to execvp() are incorrect:
char* args[] = {"\0"};
execvp("ls", args);
There are two things definitely wrong with that:
The argument array needs to be terminated with a null pointer. "\0" is not a null pointer; rather it is an array containing two null characters, and it decays to a valid, therefore non-null, pointer.
Even if "\0" were a null pointer, you would be short one argument. The first element of the argument vector, at index 0, should be a pointer to a string representing the program name.
In other words:
char* args[] = { "ls", NULL };
execvp("ls", args);
Additionally, the redirection you are performing is not consistent with the POSIX shell's treatment of the > redirection operator. In that form, that operator redirects only the standard output, not the standard error. Furthermore, it should open the designated file write-only, not read/write, because writing to it is all the program needs to do. Opening it read/write could cause it to fail to redirect to an existing file on which the user has write permissions but not read permissions.
Furthermore, The file mode you specify for the event that a new file is created also produces behavior inconsistent with the POSIX shell's. You should specify read/write permissions for user, group, and other, and let that be modified according to the umask in effect:
int fd = open("test.txt", O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
Related
I'm trying to write a code which manipulates standard input and output and redirect them to files, and then use execvp (also tried other exec's) to run a program that simply uses printf and scanf , but the execvp fails..
Relevant code:
int pid2 = fork();
if (pid2 == 0) {
int fdInput = open("myinputfile", O_RDONLY);
close(0);
dup(fdInput);
int fdOutput = open("results.txt", O_WRONLY | O_CREAT | O_TRUNC);
close(1);
dup(fdOutput);
char* tmp[]={"...somepath/prog"};
execvp("...somepath/prog", tmp);
}
My prog:
int main(){
int x;
scanf("%d",&x);
printf("Hello World! %d",x);
return 0;
}
myinputfile contains only -> 4
I tried two main things:
copying the code from prog and hardcoding it into my code instead of the call to execvp, which works fine and I can see "Hello world! 4" in results.txt
running "mypath" in the terminal manually which also seems to work(with the standard I/O).
I can't understand why it's not working, I tried everything I could think of..
Your array of arguments passed to execvp() is not NULL-terminated.
Per the POSIX exec() documentation:
...
The argument argv is an array of character pointers to null-terminated strings. The application shall ensure that the last member of this array is a null pointer. These strings shall constitute the argument list available to the new process image. The value in argv[0] should point to a filename string that is associated with the process being started by one of the exec functions.
...
Your code should be
int pid2 = fork();
if (pid2 == 0) {
int fdInput = open("myinputfile", O_RDONLY);
close(0);
dup(fdInput);
int fdOutput = open("results.txt", O_WRONLY | O_CREAT | O_TRUNC);
close(1);
dup(fdOutput);
// note the NULL terminator
char* tmp[]={"...somepath/prog", NULL };
execvp("...somepath/prog", tmp);
}
I'm try to use system("echo 1 > /sys/class/afile") to set a file to 1.
If I use it on console it works well, but if I run my C program it shows me:
sh: echo: I/O error
I already try to set it with following code:
char i[1];
i[0]=1;
int fd1 = open("/sys/class/afile",O_CREAT | O_WRONLY);
int ret = write(fd1,i,strlen(i));
if(ret > 0)
printf("Ok\n");
else
printf("nOk\n");
close(fd1);
The result is "Ok" but the file didn't change.
strlen(i) yields undefined behavior because i is not a string; the array lacks room for null termination. Also, i[0]=1; does not put a '1' character in the array, but rather puts a byte with value 1, which is a "^A character".
Instead try write(fd, "1", 1) - no need for any variables or strlen.
Here
int fd1 = open("/sys/class/afile",O_CREAT | O_WRONLY);
you need to provide mode in the third argument of open() since you are using O_CREAT.
From the manual page of open
int open(const char *pathname, int flags, mode_t mode);
It says
O_CREAT
If pathname does not exist, create it as a regular file.
The mode argument specifies the file mode bits be applied when
a new file is created. This argument must be supplied when
**O_CREAT or O_TMPFILE is specified in flags;**
For e.g you can try this
int fd1 = open("/sys/class/afile",O_CREAT | O_WRONLY, 0664);
And do check return value of open() system call with perror().
Also here
char i[1];
int ret = write(fd1,i,strlen(i));
as #R pointed, strlen(i) causes undefined behavior since i is declared of 1 byte & there is no space for \0 char to null terminate the char buffer.
There are some problems in your program, first I wasn't able to write file without sudo persmission:
~/Documents/src/progs : $ echo 1 > /sys/class/afile
bash: /sys/class/afile: Permission denied
If you try to do the same thing with some file in local directory(not in root folders), you should be able to write your file like this:
#include <stdio.h>
int main() {
FILE* pFile = fopen("afile", "w");
fprintf(pFile, "1\n");
fclose(pFile);
}
Aside from using popen() (as was discussed in this question) is this a valid way of doing it ?
Say we had a program who's name is hexdump_dup and wanted the program to output the exact output of the hexdump command.
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755); // (line 8)
write(fd, "/usr/bin/hexdump $#;", 20); // (line 9)
close(fd);
return (0);
}
Also could someone briefly explain what line 8 and 9 do, and how afterwards the command gets executed ? Like when, where does it say to execute the command or what makes the command execute ?
After this
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755); // (line 8)
write(fd, "/usr/bin/hexdump $#;", 20);
you need to execute hexdump_dup executable, for that you need to use either system() or exec() family function. For e.g
system("./hexdump_dup 1 2 3"); /* after creating binary file(hexdump_dup) & writing command into it, you need to run it, for that use system() or exec() */
This
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755);
will create the hexdump_dup binary if it doesn't exist before & if exists before it will truncate its content to 0. You can refer the man page of open() , it says
int open(const char *pathname, int flags, mode_t mode);
The argument flags must include one of the following access
modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening
the file read-only, write-only, or read/write, respectively.
O_CREAT
If the file does not exist it will be created. The
owner (user ID) of the file is set to the effective
user ID of the process.
O_TRUNC
If the file already exists and is a regular file and
the open mode allows writing (i.e., is O_RDWR or
O_WRONLY) it will be truncated to length 0. If the
file is a FIFO or terminal device file, the O_TRUNC
flag is ignored.
Lastly this
write(fd, "/usr/bin/hexdump $#;", 20);
writes 20 bytes containing array of characters /usr/bin/hexdump $#; in this case into a file where fd points i.e it will put this into hexdump_dup file.
Here $# means when you execute hexdump_dup like
./hexdump_dup 1 2 3
it will take all the parameters to be passed.
I want to redirect the output to a specific file when the program encounters a ">" and for it to get the input from a file if it finds a "<".
This works for the input, but when I try the output to file it doesn't write anything in the actual file nor does it show it in the terminal.
I can't seem to figure out why, can anyone help?
switch(fork()){
case 0:
for(i=0; i<num; i++){
if(strcmp(argv[i], ">") == 0){
if(fd1 = open(argv[i+1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR) < 0){
perror("cant open file");
exit(0);
}
dup2(fd1, 1);
close(fd1);
argv[i] = NULL;
}
if(strcmp(argv[i], "<") == 0){
if(fd0 = open(argv[i+1], O_RDONLY) < 0){
perror("cant open file");
exit(0);
}
dup2(fd0, 0);
close(fd0);
}
}
execvp(argv[0], argv);
The primary problem is that the = operator has lower precedence than the < operator. Thus, in your expression
fd1 = open(argv[i+1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR) < 0
, the return value of open() is compared to 0, and then the result of that comparison -- either 1 or 0 -- is assigned to fd1. The newly-opened file descriptor is lost, and goes unused. The actual result of the comparison is most likely 0 (the file was successfully opened), so your dup2() call a few lines later attempts to dupe file descriptor 0 onto file descriptor 1. In all likelihood that works, but any attempt by the program you exec to write to that file descriptor very likely fails.
You ought to write it as
(fd1 = open(argv[i+1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0
Alternatively, be a little less clever and separate that into two statements, so that the precedence question does not arise in the first place.
You have the same problem when you redirect input, so I'm doubtful of your claim that "This works for the input", but because in that case you'll end up just duping file descriptor 0 onto itself, the problem will manifest simply as the redirection not working as expected -- input will still come from the original program's standard input, not the standard input you designate.
Even that might go unnoticed, however, if you allow the shell to perform the input redirection instead of quoting the redirection operator so as to get it through to the program as an argument.
Continuing on this problem, but I'll reiterate:
For a homework assignment I have to write a basic shell including redirection. The program uses readline to prompt for input, parses the input string, and breaks it down into the executable name, the arguments, and the input/output file(s), if applicable. After parsing the string, it forks and the child execv()'s to the executable that was passed in. I'm using dup2() to change the file descriptors after the fork and before the execv, but am having a problem once the program has execv'd to the new executable. If in my shell I run ls > foo.out, I get: ls: write error: Bad file descriptor
Here is the code for my child process (this is after fork()):
int _child(struct command *c){
int ret;
/* When given `ls > foo.out`, these next two lines output:
** c->infile is (null)
** c->outfile is foo.out
*/
printf("c->infile is %s\n",c->infile);
printf("c->outfile is %s\n",c->outfile);
if(c->infile){
int fd = open( c->infile, O_RDONLY);
int _fd_dup = dup2(fd,0);
close(0);
if(_fd_dup != 0){
fprintf(stderr, "Failed to redirect command input.\n");
return 0;
}
}
if(c->outfile){
int fd = open( c->outfile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
int _fd_dup = dup2(fd,1);
close(1);
if(_fd_dup != 1){
fprintf(stderr, "Failed to redirect command output.\n");
return 0;
}
}
I do not get the "Failed to redirect command output." error. As I mentioned, this is a homework assignment so I'm not looking for anyone to fix this, but rather point me in the right direction.
The problem is in this bit of code:
int _fd_dup = dup2(fd,1);
close(1);
You should be closing fd, not 1. You have the same problem in the fd 0 case, too.