Bad file descriptor when running cat command in a shell

loremIpsum1771 picture loremIpsum1771 · Sep 14, 2015 · Viewed 7.9k times · Source

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;

EDIT

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 +++

EDIT 2

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;

Answer

jforberg picture jforberg · Sep 15, 2015

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.