Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

Saturday, September 27, 2014

As thick as a phonebook

$ cat .
cat: .: Is a directory

$ perl -pe '' .
Huh.

Turns out you can open(2) a directory just fine, so Perl doesn't bat an eye over that.  It's only the subsequent read(2) that will fail, indicated by readline (or read, or sysread) returning undef, which you're supposed to check for.  Yeah, like anyone actually checks for these things.

The worst part is that autodie won't save your ass in this situation:
use autodie;
use warnings;

open FH, "<", ".";
1 while <FH>;
close FH;

print "Uncaught: $!\n";
Apparently, not much can be done about this.  :(

Unclogged

Older, wiser programmers will have figured out that the bug in my previous post was due to an uncaught SIGPIPE.  I'd completely forgotten about those.  :)

The shifting nature of the bug (which is what really had me confused) was due to a race condition between the parent process writing to the pipe, and the child closing it (by exiting).  Here's an overly simplified version of what happens in both processes after the clone():
/* parent - writer */
write(...)
close(...)
waitpid(...)

/* child - reader */
execve("/bin/false", ...)
exit(1)
If the parent attempts to write to the pipe after it has been closed on the other side by the child's exit(), then a SIGPIPE obviously occurs.  However, since our simple string is small enough to fit in a pipe's buffer, the parent may very well get the chance to close the pipe before the child.  At this point, the child's close() will simply discard any data in the pipe's buffer and exit; its exit status will then be returned by the parent's waitpid(), stored in $?, and cause Perl's close() to return a false value.

The script example given in the previous post is simple enough (without autodie) for the parent to get there first.  Adding autodie then introduces just enough complexity for the parent to take a little bit more time, giving the child a chance to exit first.  (Even using strace is enough to influence the result, making this a true heisenbug.)

Note that in the parent-closes-first case, the failure has nothing to do with the pipe itself (hence why $! is left empty), but is simply due to the child returning a non-zero exit status.  (Thus, replacing false with true would make the script fail some times, but not always.  Now there's a real head-scratcher.)

Thursday, September 25, 2014

Clogged

This one had me stumped for a while:
#!/usr/bin/perl

use 5.014;
use autodie;

open FALSE, "|-", "false";
print FALSE "Hello world!";
say "Closing filehandle:";
close FALSE or die "close() doesn't die...";
say "...but doesn't succeed either";
What made it even confusing was that commenting out autodie allowed close to at least (silently) fail and return a false value (without even an error message in $! as would be expected).  I used autodie to catch any unchecked errors, dammit, not to hide them even further!

I guess that's what I get for living a life sheltered away from the raw, bare-metal, non-child-proof world of C programming.  :)

Wednesday, August 13, 2014

It's 1975 all over again

This one had me confused for a moment:
$ date
Wed Aug 13 14:34:53 EDT 2014
$ re.pl
$ # The join() is only for cosmetic reasons
$ join " " => localtime
6 36 14 13 7 114 3 224 1
$ # Let's grab only the HH:MM part (items 2 and 1)
$ my ($h, $m) = localtime[2,1]
$VAR1 = 56;
$VAR2 = 44;
Huh?

Oh, I see: I forgot my parentheses.
$ ($h, $m) = (localtime)[2,1]
$VAR1 = 14;
$VAR2 = 41;
Much better.  But where the hell did the first set of values come from?
$ join " " => localtime[2,1]
56 31 8 17 10 75 1 320 0
$  scalar localtime[2,1]
Mon Mar 10 02:35:04 1975
Dang, I forgot that localtime() can take an argument, a Unix time number (just like localtime(3) takes a time_t as argument).  This is all starting to make sense.
$ [2,1] . ''
ARRAY(0x9f9dd0c)
$ [2,1] + 0
164751992
$ scalar localtime 164751992
Sat Mar 22 15:26:32 1975
(I'm still a bit surprised that Perl didn't say a word about this.)

Tuesday, August 12, 2014

Dear Time::Piece,

Dear Time::Piece,
re.pl
$ use Time::Piece

$ my $now = Time::Piece->new->strftime("%F %T")
2014-08-12 17:36:04
$ Time::Piece->strptime($now, "%F %T")
Runtime error: Error parsing time at /usr/lib/perl/5.18/Time/Piece.pm line 469.
I hate you.

(more info)