Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris

reading from a file after a seek isn't working for me

by samwyse (Scribe)
on Oct 21, 2009 at 19:58 UTC ( #802517=perlquestion: print w/replies, xml ) Need Help??
samwyse has asked for the wisdom of the Perl Monks concerning the following question:

The following is putting the right stuff ('hello world\n') into the file, and the calls to tell() return what I'd expect. But my loop isn't printing anything, and the read() doesn't seem to be advancing the file pointer. Is there something I'm missing?
@argv = qw(/bin/echo hello world); open(SAVOUT, '>&STDOUT') or die $!; open(STDOUT, '+>', "/tmp/stdout.log") or die $!; system(@argv); print SAVOUT "before=", tell(STDOUT), "\n"; seek(STDOUT, 0, 0) or die $!; print SAVOUT "after=", tell(STDOUT), "\n"; while (1) { read STDOUT, $_, 8192; last unless $_; print SAVOUT "stdout=", $_; } print SAVOUT "at end=", tell(STDOUT), "\n"; close STDOUT;
And here's the output:
before=12 after=0 at end=0

Replies are listed 'Best First'.
Re: reading from a file after a seek isn't working for me
by ikegami (Pope) on Oct 21, 2009 at 20:15 UTC

    I changed

    read STDOUT, $_, 8192;


    my $rv = read STDOUT, $_, 8192; die "read: $!\n" if !defined($rv);


    before=0 after=0 read: Bad file descriptor

    There are problems with opening STDOUT without closing it first.

    I changed

    open(STDOUT, '+>', "/tmp/stdout.log") or die $!;


    close(STDOUT); open(STDOUT, '+>', "/tmp/stdout.log") or die $!;


    before=0 after=0 stdout=hello world at end=12
      @ikegami: Fellow Monks, can you please explain in detail the need for the explicit close here?

      Normally opening with an existing FH closes the original file or at least I never noticed a problem in cutting this corner in one-shots, one-liners or inline shell scripts (but usually avoiding read, sysread, tty's and STDIN/OUT/ERR).


      Update: - ok, any takers for this riddle with more time? Will summarize if pointed correctly with keywords and RTFM's to check :)

      From perldoc -f close:

      You donít have to close FILEHANDLE if you are immediately going to do another "open" on it, because "open" will close it for you. (See "open".) However, an explicit "close" on an input file resets the line counter ($.), while the implicit close done by "open" does not.

      There are a few more notes on pipes, but those don't seem to match the opener's situation either. Skimming perlopentut I didn't see pointers of interest - au contraire, it even _seems_ to imply that reopening w/o close (my reading on the lack of close() in the Playing with STDIN/STDOUT section) for STDIN/STDOUT is fine. Or is there indeed some hardcoded magic of it being STDOUT we insist to read from??

      What do I miss?

        can you please explain in detail the need for the explicit close here?

        I think it has to do with PerlIO in combination with an implementation peculiarity.

        When you compare the straces of both variants, you'll see something like:

        # with explicit close close(1) = 0 open("/tmp/stdout.log", O_RDWR|O_CREAT|O_TRUNC, 0666) = 1 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2ba69a30) = -1 ENOTTY (I +nappropriate ioctl for device) lseek(1, 0, SEEK_CUR) = 0 fstat(1, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 fcntl(1, F_SETFD, 0) = 0 # without explicit close open("/tmp/stdout.log", O_RDWR|O_CREAT|O_TRUNC, 0666) = 4 ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff023c1390) = -1 ENOTTY (I +nappropriate ioctl for device) lseek(4, 0, SEEK_CUR) = 0 fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 dup2(4, 1) = 1 close(4) = 0 fcntl(1, F_SETFD, 0) = 0

        Now, the issue is (I think) that although the dup2 does create a copy of fd 4 as fd 1 at the system level (and in fact does also close the old fd 1), it does not copy the PerlIO part, which is only being handled properly, when the filehandle is being created directly using Perl's open.  For this reason, the filedescriptor is considered invalid from the PerlIO point of view (—> the "Bad file descriptor" message). This is checked at the beginning of Perl's read using PerlIOValid(f)1 (even before doing any read system call).

        Don't ask (me), however, why the indirect dup2-technique is being used in the first place instead of simply closing the filedescriptor before the open...  (Presumably, it did work before the the introduction of PerlIO, and might just not have been adapted appropriately since.)


        1  see perlio.c:

        #define Perl_PerlIO_or_Base(f, callback, base, failure, args) \ if (PerlIOValid(f)) { \ const PerlIO_funcs * const tab = PerlIOBase(f)->tab;\ if (tab && tab->callback) \ return (*tab->callback) args; \ else \ return PerlIOBase_ ## base args; \ } \ else \ SETERRNO(EBADF, SS_IVCHAN); \ return failure ... SSize_t Perl_PerlIO_read(pTHX_ PerlIO *f, void *vbuf, Size_t count) { Perl_PerlIO_or_Base(f, Read, read, -1, (aTHX_ f, vbuf, count)); }
        No, sorry

        (You asked if I could explain. I can't. I don't know why it behaves as it does.)

      Great! Thanks!

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://802517]
Approved by Corion
Front-paged by bart
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2016-10-01 00:30 GMT
Find Nodes?
    Voting Booth?
    Extraterrestrials haven't visited the Earth yet because:

    Results (574 votes). Check out past polls.