Avoid writing to a closed file descriptor

I am launching a process through /etc/rc.local and don't expect to be able to write to stdout / stderr because it is daemonized and even if it would work, it would be interleaved with other output making it difficult at best.

That said, my utility is just a shell script, nothing fancy and I can check to see if stdout / stderr are closed, but they appear to be open. So, for curiosity's sake, I attempted to write, but I clearly can see they're closed:

Code:
printf: write error on stdout


The code I'm using to check if they're closed:
Code:
(: >&1) 2>/dev/null

I use either 1 or 2 to check for stdout and stderr.

Now, the reason I care is that I am trying to generalize my code such that it works if it is called through an rc script or interactively. I could tell the script to not write to stdout / stderr, but thought that it would be nice if I could check if they were open / closed to be dynamic.

I think one caveat with this is that this won't work for fd 2 since it is being used here. So, this would only work for fd1 then, right?
 
Code:
#!/bin/sh
echo -n 2>/dev/null  >/dev/fd/1
NO_STDOUT=$?
echo -n ""  >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty



exec 1>&-
echo -n 2>/dev/null  >/dev/fd/1
NO_STDOUT=$?
echo -n ""  >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty


exec 2>&-
echo -n ""  >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty
 
Testing in isolation works the same as the method I'm testing above. I will test this in the scenario where it is failing which is a rc.local script.
 
Testing in isolation works the same as the method I'm testing above. I will test this in the scenario where it is failing which is a rc.local script.
Did not test myself, but fed your question to AI and here is an answer:

In general, if a process is daemonized, it will not have an associated controlling terminal and standard input, output, and error (stdin, stdout, stderr) will be closed by default. However, it is still possible for a daemonized process to write to a file or a logging service, even if it cannot write to the console.

In your case, it seems that you are trying to determine whether stdout and stderr are closed by using the shell redirection operator ">&", followed by a null command ":", and redirecting any errors to /dev/null. This is a clever way to check if a file descriptor is open, but it is not perfect, as you have observed.

The reason that your check failed for stderr is that you redirected stderr to /dev/null when you ran your check. You can modify your code to avoid this problem by using a different file descriptor number to redirect stderr to /dev/null, like this:


Code:
# Check if stdout is open
(: >&3) 2>/dev/null
if [ $? -eq 0 ]; then
    echo "stdout is open"
else
    echo "stdout is closed"
fi

# Check if stderr is open
(: >&4) 2>/dev/null
if [ $? -eq 0 ]; then
    echo "stderr is open"
else
    echo "stderr is closed"
fi

This code redirects stdout to file descriptor 3 and stderr to file descriptor 4 before checking whether they are open. By using different file descriptor numbers, you can avoid overwriting the error messages that might be printed to stderr during the check.

It is worth noting that even if stdout and stderr are open, they may not be connected to the console. In a daemonized process, stdout and stderr are often redirected to log files or to a logging service like syslog, so you may not see their output unless you know where to look.
 
I tried this in the rc script in a similar fashion and it behaved the same as the check I had done:

#!/bin/sh
echo -n 2>/dev/null >/dev/fd/1
NO_STDOUT=$?
echo -n "" >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty



exec 1>&-
echo -n 2>/dev/null >/dev/fd/1
NO_STDOUT=$?
echo -n "" >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty


exec 2>&-
echo -n "" >/dev/fd/2
NO_STDERR=$?
echo NO_STDOUT=$NO_STDOUT NO_STDERR=$NO_STDERR >/dev/tty

I will try the other approach to see if that is any different. In the meantime, I have implemented a simple fix which is to say, I disable writing to those file descriptors when the program is launched through the rc script. Otherwise, it will use them. It is simple enough, but I was hoping to have it be generic and transparent so I wouldn't need to remember to do it again.
 
Back
Top