[comp.unix.internals] dup2

rbj@uunet.UU.NET (Root Boy Jim) (02/08/91)

In article <1991Feb8.002436.21328@usenet.ins.cwru.edu> chet@po.CWRU.Edu writes:
>Doug Schmidt writes:
>
>>	I'm curious, is it possible to implement the dup2() system
>>call using only routines available in the standard C library and other
>>existing system calls?
>
>When you really get down to it.  the kernel is going to have to do the
>nitty-gritty duplication for you, otherwise it gets tricky.  If you have
>an fcntl(..., F_DUPFD, ...), it's straightforward. 
>
>Here's how we do it in bash:

[You may see this twice. Sorry if you do]

Well, a clear case of two left feet not knowing what the right hand is
doing. The solution is trivial once you grasp the recursive nature of it.

I was gonna tell y'all to look in ~emacs/src/sysdep.c, but the
version there is broken! I feel like a fool, as I sent it off to
Larry Wall. Dup and dup2 can fail, altho people treat it as if it can't.
I just debugged the following routine. Appended is a test driver.

dup2(old,new)
{
#ifdef F_DUPFD
	close(new);
	return(fcntl(old, F_DUPFD, new));
#else
        register int fd, ret;
        close(new);
        fd = dup(old);
        if (fd == -1) return(-1);
        if (fd == new) return(new);
        ret = dup2(old,new);
        close(fd);
        return(ret);
#endif
}

main(c,v) char *v[];
{
        register int a,b;
        (c > 2) || (printf("usage: %s srcfd dstfd\n",v[0]),exit(1));
        a = atoi(v[1]); b = atoi(v[2]);
        printf("dup2(%d,%d) = %d\n",a,b,dup2(a,b));
}
-- 

	Root Boy Jim Cottrell <rbj@uunet.uu.net>
	I got a head full of ideas
	They're driving me insane

rbj@uunet.UU.NET (Root Boy Jim) (02/13/91)

In article <richard.666334818@fafnir.la.locus.com> richard@locus.com (Richard M. Mathews) writes:
>I have not yet seen a correct version of code for dup2() posted.  The
>versions posted by Paul Falsted, Doug Gwyn, and Root Boy Jim all fail
>the following test case because they close "fd2" too soon:

I suppose it depend on your interpretation. If you try dup2(-1,fileno(stdout))
and end up with nowhere to print to, have you not made a copy of your
input file descriptor? What does the shell do with "cmd >&- 2>&1"?

And what is the "meaning" of dup2(1,1)? Anyone who trys to do this
probably has a logic error somewhere, regardless of the definition.
In any case, I am willing to add "if (new == old) return(new);" to my code.

I also claim that I handled errno properly. It is not necessary
to preserve the old value of errno if dup2 returns true.

And Guy, you are right, any system that has neither dup2 nor fcntl
probably has lots of problems. However, my version works in V6!
-- 
		[rbj@uunet 1] stty sane
		unknown mode: sane

richard@locus.com (Richard M. Mathews) (02/14/91)

rbj@uunet.UU.NET (Root Boy Jim) writes:
>richard@locus.com (Richard M. Mathews) writes:
>>I have not yet seen a correct version of code for dup2() posted.  The
>>versions posted by Paul Falsted, Doug Gwyn, and Root Boy Jim all fail
>>the following test case because they close "fd2" too soon:

>I suppose it depend on your interpretation.

Well, since we are trying to simulate a V7 system call, I would think
that the V7 code would be the best place to go to see what we should
emulate.  I went to the oldest code I could find without bothering
our tape librarian.  The was a version of the Locus Operating System,
circa 1983, based on BSD4.1.  I doubt that Locus made changes to dup2
way back then.  I don't know if Berkeley might have changed dup2 from
V7.  All references to code and documentation below refer to this
system.

>If you try dup2(-1,fileno(stdout))
>and end up with nowhere to print to, have you not made a copy of your
>input file descriptor? What does the shell do with "cmd >&- 2>&1"?

The above mentioned code returns without touching fd2 if the getf()
fails on fd1.  It certainly makes sense that if you return -1 you should
leave the state of the process unchanged.  By your argument, dup2(-1,fd)
should be equivalent to close(fd) and should return success if the close
succeeds.  This clearly is not the way dup2 is documented (it says, "-1
is returned if the given file descriptor is invalid").

>And what is the "meaning" of dup2(1,1)? Anyone who trys to do this
>probably has a logic error somewhere, regardless of the definition.
>In any case, I am willing to add "if (new == old) return(new);" to my code.

The above mentioned code has a special case to make sure it treats this
as a no-op (though it does validate fd1).

>I also claim that I handled errno properly. It is not necessary
>to preserve the old value of errno if dup2 returns true.

The above mentioned code uses EBADF.  Fcntl uses EINVAL.  You definitely
got that errno wrong.  As for preserving errno on success, it certainly
is true that V7 dup2 did do so, but it isn't clear that you need to.
The manual says in intro(2) that, "Errno is not cleared on successful
calls."  It doesn't promise that errno isn't changed.  I think it is a
nice extra feature to add those few lines of code to preserve errno.

Richard M. Mathews			 Freedom for Lithuania
richard@locus.com				Laisve!
lcc!richard@seas.ucla.edu
...!{uunet|ucla-se|turnkey}!lcc!richard

rbj@uunet.UU.NET (Root Boy Jim) (02/15/91)

richard@locus.com (Richard M. Mathews) writes:
? rbj@uunet.UU.NET (Root Boy Jim) writes:
? >richard@locus.com (Richard M. Mathews) writes:
? >>I have not yet seen a correct version of code for dup2() posted.  The
? >>versions posted by Paul Falsted, Doug Gwyn, and Root Boy Jim all fail
? >>the following test case because they close "fd2" too soon:

? >I suppose it depends on your interpretation.

And my interpretation was from the manual page. Remember, I am
not writing Posix library code, or a Gwynix emulation package,
but something I can plug into a piece of code that I'm trying
to compile on a system that may not have dup2.

? >If you try dup2(-1,fileno(stdout))
? >and end up with nowhere to print to, have you not made a copy of your
? >input file descriptor?
?
? By your argument, dup2(-1,fd) should be
? equivalent to close(fd) and should return success if the close succeeds. 

Sure, why not? I have made a copy of what you gave me.
With lemons, I make lemonade. Garbage in, garbage out. 

And close always succeeds. Or rather, the only way it can fail is
by attempting to close something which is not open. In any case,
it's closed after the call.
 
? >What does the shell do with "cmd >&- 2>&1"?

? The above mentioned code returns without touching fd2 if the getf()
? fails on fd1.  

You're right, it complains. Sorry, not a good example. Or is it?
Shouldn't I be able to close stderr that way? The answer is debatable.

? It certainly makes sense that if you return -1 you should
? leave the state of the process unchanged.

Interesting notion. I'm not sure it's possible, tho, especially
in a system call with complex interactions.

? This clearly is not the way dup2 is documented (it says, "-1
? is returned if the given file descriptor is invalid").

Regarding dup2, you can't use "clearly" and "documented" in the
same sentence. Even the SunOS manual pages, which are more
specific than earlier manuals, are rather nebulous about the exact
sequence. You deserve what you get.

? >And what is the "meaning" of dup2(1,1)? Anyone who trys to do this
? >probably has a logic error somewhere, regardless of the definition.
? >In any case, I am willing to add "if (new == old) return(new);" to my code.
? 
? The above mentioned code has a special case to make sure it treats this
? as a no-op (though it does validate fd1).

That is the meaning, but what is the "meaning"? I claim that anyone
who calls dup2 with identical arguments has made a logic error.
Often, dup2(old,new) is followed by close(old). The end result
is the same. Perhaps a better solution would be to simply return
an error while leaving the identical descriptor alone.

? >I also claim that I handled errno properly. It is not necessary
? >to preserve the old value of errno if dup2 returns true.
? 
? The above mentioned code uses EBADF.  Fcntl uses EINVAL.  You definitely
? got that errno wrong. 

Touche. I threw the fcntl part in at the last minute.

This has turned into a rather dry discussion on minutiae, which
obscures the beauty of the recursive solution. To be honest, I
didn't write it, altho I did fix the implementation.

Or rather, I did the 20% of the code that solves 80% of the cases.
Most people don't even check dup or dup2 because they "always work".
At least I made the attempt. The objections you bring up are
rather gray areas. To my mind, it's not clear that the standard
definition is truly optimal, or that it applies all that often.

? As for preserving errno on success, it certainly
? is true that V7 dup2 did do so

Because it was done in the kernel, where you have to go out
of your way to alter a variable in user space.
And it was done directly, rather than glommed onto something else.

? but it isn't clear that you need to.

Exactly.

? The manual says in intro(2) that, "Errno is not cleared on successful
? calls."  It doesn't promise that errno isn't changed.  I think it is a
? nice extra feature to add those few lines of code to preserve errno.

I believe the fcntl errno should be remapped correctly.

On preserving the previous errno upon successful return,
I disagree. In fact I believe it is actually counterproductive. 

As more and more "system calls" are replaced with
library functions to more general, powerful and modern interfaces,
this task gets harder and harder, all for dubious results. 

And some day someone will complain when errno changes mysteriously.
Better to declare open season on it now before people start caring.

If you didn't test it immediately, then you didn't really
care about it and it's gone forever.

However, I thank you for your observations. It's refreshing to know
that the man who smugly wrote "We think of everything" didn't.

? Richard M. Mathews			 Freedom for Lithuania
? richard@locus.com				Laisve!
? lcc!richard@seas.ucla.edu
? ...!{uunet|ucla-se|turnkey}!lcc!richard
-- 
		[rbj@uunet 1] stty sane
		unknown mode: sane