I'm trying to implement I/O redirection in a shell that I'm writing for a Unix-like OS (xV6). In the manual that I'm reading for the OS, I found the following code that would run in the shell for the execution of the cat command:
char *argv[2];
argv[0] = "cat";
argv[1] = 0;
if(fork() == 0) {
close(0);
open("input.txt", O_RDONLY);
exec("cat", argv);
}
I modified the code to run in my shell which has the argv array located in another function, but it maintains the functionality. For some reason, when I run the cat < input.txt
the shell outputs:
cat: -: Bad file descriptor
cat: closing standard input: Bad file descriptor
I'm still new to OS programming so I'm not exactly clear on all of the functionality of I/O redirection, but I think the code I have should work. What could be causing the problem. I have the code for the I/O redirection below:
case '<':
ecmd = (struct execcmd*)cmd;
rcmd = (struct redircmd*)cmd;
if(fork() == 0){
close(0);
open("input", O_RDONLY);
execvp(ecmd->argv[0], ecmd->argv );
}
runcmd(rcmd->cmd);
break;
I did strace -e open ls
and got:
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
a.out sh.c test
+++ exited with 0 +++
For some reason, this code for the case works, but I'm not sure why:
case '<':
rcmd = (struct redircmd*)cmd;
close(rcmd->fd);
if(open(rcmd->file, rcmd->mode) < 0){
printf(2, "Cannot open file %s\n", rcmd->file);
perror(rcmd->file);
exit(1);
}
runcmd(rcmd->cmd);
break;
You can't just close stdin and then open another file. It doesn't automatically become your new stdin. What you want is to use the syscall dup2 which can "redirect" a file descriptor to another.
int fd = open("input.txt", O_RDONLY);
dup2(fd, 0); // stdin now points to fd
close(fd);
For more info see man 2 dup2. Note that open and dup2 can both fail, so you should check their return values if this is a concern.
EDIT: This will actually work sometimes because POSIX guarantees the kernel will always allocate the lowest free file-descriptor. But it is not thread-safe and is just bad style in general. I recommend to always do the dup2 even though in some cases you could get away without it.