jmacloue has asked for the wisdom of the Perl Monks concerning the following question:

I wrote a script which forks an unpriviledged child as it shown in perlipc example and found out that after a successul socketpair() call there is an error recorded in $!:

#!/usr/bin/perl -w use strict; use IO::Socket; print "Before socketpair() call: $!\n"; my @s = IO::Socket->socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX) or die "socketpair: $!\n"; print "After socketpair() call: $!\n";

It prints out:

Before socketpair() call: After socketpair() call: Illegal seek

Despite socketpair() is successful (sockets work as expected and the script does not die) an "Illegal seek" is recorded in $! so I had to explicitly say $!=undef after the call to get rid of it or use a block with "local $!;".

Usually it is not a problem as most errors are reported in some other way (e.g., func() or die "$!") but when I need to change process uid/gid the only way to check for error is to see if $! is set, and if $! stays littered with an error from socketpair() the check fails:

$) = $run_gid; die "Setting egid: $!" if $!;

I tried to see if I'm doing anything wrong so I tried strace'ing my script, to get the following:

socketpair(PF_LOCAL, SOCK_STREAM, 1, [3, 4]) = 0 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGET +S, 0x7fff3ff30d00) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGET +S, 0x7fff3ff30d00) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) ioctl(4, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGET +S, 0x7fff3ff30d00) = -1 ENOTTY (Inappropriate ioctl for device) lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) ioctl(4, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGET +S, 0x7fff3ff30d00) = -1 ENOTTY (Inappropriate ioctl for device) lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) fcntl(3, F_SETFD, FD_CLOEXEC) = 0 fcntl(4, F_SETFD, FD_CLOEXEC) = 0

As we see, the system socketpair() call is followed by two lseek() calls somewhere inside Perl interpreter, which fail with ESPIPE, which is recorded in $!, but socketpair() still returns success.

Any insights on how to do this (socketpair/fork/drop privileges) properly?

Replies are listed 'Best First'.
Re: Strange behaviour: $! is set after successful socketpair() call
by kennethk (Abbot) on Apr 12, 2014 at 20:53 UTC
    From $! in perlvar:
    Many system or library calls set errno if they fail, to indicate the cause of failure. They usually do not set errno to zero if they succeed. This means errno , hence $! , is meaningful only immediately after a failure
    You can't use $! as an indicator of whether a call failed. I am not particularly familiar with what you are attempting, but assuming using $! to check success on modifying $) is appropriate, I would probably implement that bit as:
    EGID_BLOCK: { my $cache = $!; $) = $run_gid; die "Setting egid: $!" if $! and $! ne $cache; }
    This brings it more in line with the principle of least surprise, since the funky checks are all localized to the block. This could also function using localization or explicit undef.

    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: Strange behaviour: $! is set after successful socketpair() call
by LanX (Saint) on Apr 12, 2014 at 21:38 UTC
Re: Strange behaviour: $! is set after successful socketpair() call
by jmacloue (Beadle) on Apr 16, 2014 at 15:10 UTC

    Oops, thanks for pointing this out. It looks like I approached the problem from the wrong side - instead of trying to prevent socketpair() from putting anything in $! I should have reset $! when checking its value is the only mean to check for errors.

    So, it's more accurate to use the following:

    $! = undef; $) = $run_gid; die "Setting egid: $!" if $!;

    (e.g., put the assignment close to when you do the call which may fail)