Nicolas Williams wrote:
> On Wed, Sep 02, 2009 at 07:10:35PM +0200, Vladimir Kotal wrote:
>>   http://cr.opensolaris.org/~vkotal/daemon_libc-4471189.onnv/
> 
>  - Could you make daemon() replace fildes 0, 1 and 2 the way the ssh
>    version (from OpenBSD) did it: open /dev/null, dup2() into 0, 1 and
>    2, then close the /dev/null fildes.  Also, please check for errors
>    when opening /dev/null for this.

First the obvious stuff:

Checking the return value of open() is useful without any dispute given
PSARC/2009/378.

I think that in case noclose is 0 and one of the open()/dup2() fails
then daemon() should return -1 to signal to the caller it was not able
to perform its job as requested.

Now, there is this "put as little potential failure points into the path
as possible versus if we fail it should be as graceful as possible"
sort-of-dispute :)

Although I liked the solution without the extra descriptor better, given 
the comment about signal handlers I am more inclined towards the 
open+dup2-only solution. In that case it would be nice to handle the
dup2() failures so in case the process already closed any of 
STD{IN,OUT,ERR}_FILENO descriptors (before the call) daemon() should not 
attempt to call dup2() needlessly. When handling failures, attempt to 
rollback naturally comes to mind:

        int fd, fd1;

        ...

         if (noclose == 0) {
                 /*
                  * Missing the PRIV_FILE_READ privilege may prevent the
                  * opening of /dev/null to succeed.
                  */
                 if ((fd = open("/dev/null", O_RDWR)) < 0)
                         return (-1);

                 /*
                  * If any of the functions fails it should have as little
                  * impact on the caller as possible.
                  */
                 if ((fd != STDIN_FILENO) && dup2(fd, STDIN_FILENO) < 0) {
                         close(fd);
                        /*
                         * even if fd was STDOUT_FILENO or STDERR_FILENO
                         * we want to leave those untouched (least
                         * surprise).
                         */
                         return (-1);
                 }
                 if ((fd != STDOUT_FILENO) && dup2(fd, STDOUT_FILENO) < 0) {

                         close(fd);
                         /* try to reopen stdin, if it was redirected */
                        if (fd != STDIN_FILENO) {
                                        fd1 = open("/dev/stdin", O_RDWR);
                                dup2(fd1, 0);
                                close(fd1);
                        }
                         return (-1);
                 }
                 if ((fd != STDERR_FILENO) && dup2(fd, STDERR_FILENO) < 0) {

                         close(fd);
                         /* try to reopen std{in,out} if redirected */
                        if (fd != STDIN_FILENO) {
                                        fd1 = open("/dev/stdin", O_RDWR); 

                                dup2(fd1, STDIN_FILENO);
                                close(fd1);
                        }
                        if (fd != STDOUT_FILENO) {
                                fd1 = open("/dev/stdout", O_RDWR);
                                dup2(fd1, STDOUT_FILENO);
                                close(fd1);
                        }
                         return (-1);
                 }

                 if (fd > STDERR_FILENO)
                         close(fd);
         }


The rollback path can still fail because of missing privileges or such, 
it's just best effort.

Also, any of the 0,1,2 file descriptors could be associated with other 
than stdin/out/err before daemon() is called. However, after returning 
with failure, most of the consumers would want to print some sort of 
error to stderr and exit afterwards so this approach seems fine to me. 
On the other hand, this is different than the behavior of daemon() 
elsewhere. Still, preserving compatibility in the error path is probably 
too strict. Looking at the above code again, the rollback is probably 
too much and its benefit is questionable.

So, in the end I think it could look like this, with a note added to the 
man page (that a failure in case noclose is 0 might influence 
descriptors 0,1,2):

        int fd;

        ...

         if (noclose == 0) {
                 /*
                  * Missing the PRIV_FILE_READ privilege may prevent the
                  * opening of /dev/null to succeed.
                  */
                 if ((fd = open("/dev/null", O_RDWR)) < 0)
                         return (-1);

                 /*
                  * Also, if any of the descriptor redirects fails
                  * we should return with error to signal to the caller
                  * that his request cannot be fulfilled properly.
                  */
                 if ((fd != STDIN_FILENO) && dup2(fd, STDIN_FILENO) < 0) {
                         close(fd);
                         return (-1);
                 }
                 if ((fd != STDOUT_FILENO) && dup2(fd, STDOUT_FILENO) < 0) {
                         close(fd);
                         return (-1);
                 }
                 if ((fd != STDERR_FILENO) && dup2(fd, STDERR_FILENO) < 0) {
                         close(fd);
                         return (-1);
                 }

                 if (fd > STDERR_FILENO)
                         close(fd);
         }


Incremental webrev is here (contains merge diffs):
   http://cr.opensolaris.org/~vkotal/daemon_libc-4471189.onnv.incr1/

New webrev is here:
   http://cr.opensolaris.org/~vkotal/daemon_libc-4471189.onnv.checks/


v.

Reply via email to