Re: Don't understand why can't get 'size' assoc w/STDOUT
by tchrist (Pilgrim) on Apr 17, 2011 at 03:38 UTC
|
my $winsize = "\0" x 8;
# XXX: should be require sys/ioctl.pl
# value below is for BSD, eg: OpenBSD, darwin=MacOS X, (etc?)
my $TIOCGWINSZ = 0x40087468;
if (ioctl(STDOUT, $TIOCGWINSZ, $winsize)) {
($rows, $cols, $xpix, $ypix) = unpack('S4', $winsize);
} else {
$cols = 80;
}
print "$cols\n";
__END__
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
main() {
struct winsize mywinsize;
int ttyfd;
if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY)) == -1) {
perror("open /dev/tty");
exit(-1);
}
if (ioctl(ttyfd, TIOCGWINSZ, &mywinsize) == -1) {
perror("ioctl TIOCGWINSZ");
exit(-1);
}
printf("TIOCGWINSZ %#08x\n", TIOCGWINSZ );
printf("sizeof struct winsize %d\n", sizeof(struct winsize) );
printf("rows %d\n", mywinsize.ws_row );
printf("cols %d\n", mywinsize.ws_col );
if (fclose(stdout) == EOF) {
perror("close stdout");
exit(-1);
}
exit(0);
}
But you may have to recompile and rerun the C code in DATA to get the proper values for the TIOCGWINSZ and the size of the structure. | [reply] [d/l] [select] |
|
|
Your program (and your complete 'overkill' solution) led me to finding the solution -- as I took you overkill solution and stripped out the C and converted it to perl...and that finally hilighted what was wrong with my original program.
the problem is nothing to do with a "problem with STDOUT" ---
it's the **result** word ".
Changing the line:
my $winsize=0;
to
my $winsize=' ' x 8;
Solves the problem. It works. ( " ' 'x 8 " =~= your " \0 x 8 " )
At this point, the problem is clear. ioctl doesnt' allocate space for the return value -- it expects the space to already be there. The 'Bad address' error message that comes back is a reference to my having given ioctl a bad-address for the result buffer.
Once I give it the needed space (for 4 'shorts') -- it's happy!
ARGGGG!!!! I've been programming in Perl for too long and forgotten my "C" basics...
So *thank you* 'tchrist', for writing your example with *correct code*, that allowed me to find the difference between working and not, and also, thank-you for answering the question I was asking rather than trying to get me to use 'something else'.
| [reply] |
|
|
perl-diddler wrote something that — after copyediting and HTML formatting — ran something like:
Your program (and your complete ‘overkill’ solution) led me to finding the solution — as I took your overkill solution and stripped out the C and converted it to Perl, and that finally highlighted what was wrong with my original program. The problem is nothing to do with a “problem with STDOUT” — it’s the **result** word. Changing the line:
my $winsize=0;
to
my $winsize= ' ' x 8;
Solves the problem. It works. (' ' x 8 " =~= your "\0" x 8)
At this point, the problem is clear: the Perl ioctl doesn’t allocate space for the return value — it expects the space to already be there. The ‘Bad address’ error message that comes back is a reference to my having given the C version of the ioctl syscall a bad address for the result buffer.
Once I give it the needed space (for 4 ‘shorts’) — it’s happy!
ARGGGG!!!! I’ve been programming in Perl for too long and forgotten my “C” basics...
Well, yes: it’s always good to do systems programming is C now and then: it reminds you how it’s done. :)
The Perl ioctl function has documentation in the perlfunc manpage that explains what happened to you, albeit perhaps not quite as clearly as one might wish:
SCALAR will be read and/or written depending on the FUNCTION;
a C pointer to the string value of SCALAR will be passed as the third
argument of the actual ioctl call. (If SCALAR has no string value but
does have a numeric value, that value will be passed rather than a pointer
to the string value. To guarantee this to be true, add a 0 to the scalar
before using it.)
As you have (re?)discovered, there is of course no possible way for Perl to know how large a buffer to prepare for whatever ioctl you happen to be calling to write however much data it wants there. Different ioctls need different things, and Perl cannot know one from another. It just makes the syscall and expects you to Do The Right Thing. After that, well — the kernel is certainly not about to allocate memory for you! :) By assigning your variable a value of integer 0, you managed to trick Perl into supplying a mere number to an ioctl that actually needed not a little numeric value but rather a place to write return values into.
And here is an example from Programming Perl: require "sys/ioctl.ph";
$size = pack("L", 0);
ioctl(FH, FIONREAD(), $size)
|| die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size);
Which also shows the need to pre-allocate enough memory for ioctl to write into. I suppose I could make this point clearer. I tend to find that nowadays most people look at me like I’m trying to address them in Homeric Greek if I ever bring up ioctls — or C coding, for that matter.
My so-called “overkill” solution was there to show you the C code needed to get the right structure size on your system so you could make something big enough, and also of course how to get the right value for the ioctl argument that works on your system — they do vary, as my sample runs demonstrated — instead of praying that you have a proper sys/ioctl.ph installed. My first version was just the C one, but then I figured I might as well show you the magical Inline::C version, which is kinda nifty.
| [reply] [d/l] [select] |
|
|
Please tell me whether this update would have helped make your job easier with ioctl:
=head2 ioctl
ioctl R<FILEHANDLE>, R<FUNCTION>, R<SCALAR>
X<ioctl function>
X<input;using ioctl for>
X<output;using ioctl for>
This function implements the I<ioctl>(2) syscall which controls
I/O. To get the correct function definitions, first you'll
probably have to say:
require "sys/ioctl.ph"; # perhaps /usr/local/lib/perl/sys/ioctl.ph
If I<sys/ioctl.ph> doesn't exist or doesn't have the correct
definitions, you'll have to roll your own based on your C header
files such as I<sys/ioctl.h>. (The Perl distribution includes a
script called I<h2ph> to help you do this, but running it is
nontrivial.) R<SCALAR> will be read or written (or both) depending
on the R<FUNCTION>--a pointer to the string value of R<SCALAR> will
be passed as the third argument of the actual I<ioctl>(2) call. If
R<SCALAR> has no string value but does have a numeric value, that
value will be passed directly rather than a pointer to the string
value. The C<pack> and C<unpack> functions are useful for
manipulating the values of structures used by C<ioctl>. If the
C<ioctl> needs to write data into your R<SCALAR>, it is up to you to
ensure that the string is long enough to hold what needs to be
written, often by initializing it to a dummy value of the correct
size using C<pack>. The following example determines how many
bytes are available for reading using the C<FIONREAD> C<ioctl>:
X<buffer;pre-allocation>
X<unpack function;example of use>
X<ioctl function;example of use>
require "sys/ioctl.ph";
# pre-allocate the right size buffer:
$size = pack("L", 0);
ioctl(FH, FIONREAD(), $size)
|| die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size);
Here is how to detect the current window sizeN<Or rather,
how to get the window size associated with the
STDOUT filehandle.> in rows and columns:
X<window size; determining>
require "sys/ioctl.ph";
# four unsigned shorts of the native size
$template = "S!4";
# pre-allocate the right size buffer:
my $ws = pack($template, ());
ioctl(STDOUT, TIOCGWINSZ(), $ws)
|| die "Couldn't call ioctl: $!";
($rows, $cols, $xpix, $ypix) = unpack($template, $ws);
If I<h2ph> wasn't installed or doesn't work for you, you can I<grep>
the include files by hand or write a small C program to print out
the value. You may also have to look at C code to determine the
stucture template layout and size needed for your system.
X<ioctl function;return value>
X<fcntl function;return value>
X<"0 but true"; special true value>
The return value of C<ioctl> (and C<fcntl>) is as follows:
=begin table picture
=headrow
=row
=cell Syscall Returns
=cell Perl Returns
=bodyrows
=row
=cell C<-1>
=cell C<undef>
=row
=cell C<0>
=cell String "C<0 but true>"
=row
=cell Anything else
=cell That number
=end table
Thus Perl returns true on success and false on failure, yet you
can still easily determine the actual value returned by the
operating system:
$retval = ioctl(...) || -1;
printf "ioctl actually returned %d\n", $retval;
The special string "C<0 but true>" is exempt from warnings
about improper numeric conversions from the B<-w> command-line
option or the C<use warnings> pragma.
Calls to C<ioctl> should not be considered portable. If, say,
you're merely turning off echo once for the whole script, it's
more portable to say:
system "stty -echo"; # Works on most Unix boxen.
Just because you I<can> do something in Perl doesn't mean you
I<ought> to. For still better portability, you might look at the
C<Term::ReadKey> module from CPAN. For almost anything you might want
to use C<ioctl> on, there probably exists a CPAN module that
already does that, and more portably, too, because they usually rope
your system's C compiler into doing the heavy lifting for you.
Is that better? Does it help?
| [reply] [d/l] [select] |
|
|
Re: Don't understand why can't get 'size' assoc w/STDOUT
by davido (Cardinal) on Apr 17, 2011 at 03:31 UTC
|
Perl treats STDOUT as a simple flat output file. Flat files don't have arbitrary restrictions on their maximum line length, nor maximum number of lines. The fact that you've got STDOUT routed to a screen of x,y dimensions isn't relevant. You may want something more like Curses, which gives you a terminal to work with instead of an output file that happens to point to the screen.
| [reply] |
|
|
Nope.
If that was true, then why would the example immediately above your post work?
Perl doesn't treat 'STDOUT' as ANYTHING.
STDOUT is WHATEVER is hooked up to 'FD1' when you start your program.
No, I don't want to use curses, other than to throw them at people who are giving me every reason under the sun why they don't know why it doesn't work.
If I sound irritated, it's because I've went through a thread previous to this that entirely got sidetracked by people who didn't know the answer to the question, so tried to get me to do it some other way. I know a bunch of ways to "HACK" around the the problem, but none of them enlightenment me as to why the original problem doesn't work.
I don't need a solution -- I needed understanding of why it wasn't working.
| [reply] |
Re: Don't understand why can't get 'size' assoc w/STDOUT
by Khen1950fx (Canon) on Apr 17, 2011 at 08:43 UTC
|
There's no bug---the error messages mean exactly what they say. To find the way that you got there, let's start at the beginning.
The first mistake was not using strictures. Always use them. I
noticed that you didn't use a module. There are a bunch of them that can help you with this. In my example, I use
Term::ReadKey.
Next, I took out the $SIG{__WARN__}. You won't need it here because perl will adamantly tell you that it can't find sys/ioctl.ph, at least it does that on my system. I used the
full path to ioctl.ph, but then perl complained that it couldn't find _h2ph_pre.ph. I ended up
deleting the block.
Now for the errors:
my $err = ioctl STDOUT, &TIOCGWINSZ, $winsize;
STDOUT didn't work for me, nor &TIOCGWINSZ, nor $winsize;
instead, I tried it like this:
my $err = ioctl(TTY, &TIOCGWINSZ, $winsize = "");
But &TIOCGWINSZ came back undefined. Define it:
sub TIOCGWINSZ {
0x40087468
}
Now it's workable. Then the next line I used if and used unless in the block. I wasn't able to use your row and col, so I deleted that part. Here's the result:
#!/usr/bin/perl
use strict;
use Term::ReadKey;
use Data::Dumper::Concise;
sub getwinsize (;$) {
my $recheck = $_[0];
my $winsize = 0;
my ( $maxrow, $maxcol );
return ( $maxrow, $maxcol ) if $maxrow && $maxcol && !$recheck;
my $err = ioctl(TTY, &TIOCGWINSZ, $winsize = "");
if ($err) {
print STDERR "ERROR: ioctl on STDOUT: $!\n";
unless ( ioctl( TTY, &TIOCGWINSZ, $winsize = "" ) ) {
print STDERR "ERROR: ioctl on fileno(STDOUT): $!\n";
}
else {
printf STDERR "ioctl on fileno(STDOUT) worked\n";
}
}
else {
printf STDERR "ioctl on STDOUT worked\n";
}
return;
}
print Dumper(GetTerminalSize());
print getwinsize();
sub TIOCGWINSZ {
0x40087468
}
| [reply] [d/l] [select] |
|
|
| [reply] |
|
|
Um....sorry, but you didn't read the original post.
I specifically said NOT to use 'TTY', but to use ioctl with STDOUT.
You aren't answering the question.
| [reply] |
|
|
Can't locate sys/ioctl-bogo.ph in @INC (@INC contains: /usr/local/lib/
+perl/5.8 /usr/lib/perl5/5.10.0/x86_64-linux-thread-multi /usr/lib/per
+l5/5.10.0 /usr/lib/perl5/site_perl/5.10.0/x86_64-linux-thread-multi /
+usr/lib/perl5/site_perl/5.10.0 /usr/lib/perl5/vendor_perl/5.10.0/x86_
+64-linux-thread-multi /usr/lib/perl5/vendor_perl/5.10.0 /usr/lib/perl
+5/vendor_perl .) at ./getrowcol.pl line 7.
(I added "-bogo" so it would fail so you can see the difference).
If you don't have the file you need to generate it with 'h2ph' -- something that your installer should have done when it installed perl. The warnings I suppressed with the '$SIG{__WARN__}...' (cuz no warnings "all" doesn't disable all warnings as it should!!) are:
Constant subroutine __USE_POSIX undefined at /usr/lib/perl5/vendor_per
+l/5.10.0/x86_64-linux-thread-multi/features.ph line 9.
Constant subroutine __USE_POSIX2 undefined at /usr/lib/perl5/vendor_pe
+rl/5.10.0/x86_64-linux-thread-multi/features.ph line 10.
Constant subroutine __USE_POSIX199309 undefined at /usr/lib/perl5/vend
+or_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 11.
Constant subroutine __USE_POSIX199506 undefined at /usr/lib/perl5/vend
+or_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 12.
Constant subroutine __USE_XOPEN undefined at /usr/lib/perl5/vendor_per
+l/5.10.0/x86_64-linux-thread-multi/features.ph line 13.
Constant subroutine __USE_XOPEN_EXTENDED undefined at /usr/lib/perl5/v
+endor_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 14.
Constant subroutine __USE_UNIX98 undefined at /usr/lib/perl5/vendor_pe
+rl/5.10.0/x86_64-linux-thread-multi/features.ph line 15.
Constant subroutine __USE_LARGEFILE undefined at /usr/lib/perl5/vendor
+_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 18.
Constant subroutine __USE_LARGEFILE64 undefined at /usr/lib/perl5/vend
+or_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 19.
Constant subroutine __USE_FILE_OFFSET64 undefined at /usr/lib/perl5/ve
+ndor_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 20.
Constant subroutine __USE_BSD undefined at /usr/lib/perl5/vendor_perl/
+5.10.0/x86_64-linux-thread-multi/features.ph line 21.
Constant subroutine __USE_SVID undefined at /usr/lib/perl5/vendor_perl
+/5.10.0/x86_64-linux-thread-multi/features.ph line 22.
Constant subroutine __USE_MISC undefined at /usr/lib/perl5/vendor_perl
+/5.10.0/x86_64-linux-thread-multi/features.ph line 23.
Constant subroutine __USE_GNU undefined at /usr/lib/perl5/vendor_perl/
+5.10.0/x86_64-linux-thread-multi/features.ph line 25.
Constant subroutine __USE_REENTRANT undefined at /usr/lib/perl5/vendor
+_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 26.
Constant subroutine _POSIX_SOURCE undefined at /usr/lib/perl5/vendor_p
+erl/5.10.0/x86_64-linux-thread-multi/features.ph line 51.
Constant subroutine _POSIX_C_SOURCE undefined at /usr/lib/perl5/vendor
+_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 53.
Constant subroutine _XOPEN_SOURCE undefined at /usr/lib/perl5/vendor_p
+erl/5.10.0/x86_64-linux-thread-multi/features.ph line 55.
Constant subroutine _XOPEN_SOURCE_EXTENDED undefined at /usr/lib/perl5
+/vendor_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 57.
Constant subroutine _LARGEFILE64_SOURCE undefined at /usr/lib/perl5/ve
+ndor_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 59.
Constant subroutine __USE_ISOC99 undefined at /usr/lib/perl5/vendor_pe
+rl/5.10.0/x86_64-linux-thread-multi/features.ph line 107.
Constant subroutine _ATFILE_SOURCE undefined at /usr/lib/perl5/vendor_
+perl/5.10.0/x86_64-linux-thread-multi/features.ph line 112.
Constant subroutine _LARGEFILE_SOURCE undefined at /usr/lib/perl5/vend
+or_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 120.
Constant subroutine __USE_ISOC99 undefined at /usr/lib/perl5/vendor_pe
+rl/5.10.0/x86_64-linux-thread-multi/features.ph line 127.
Constant subroutine __GNU_LIBRARY__ undefined at /usr/lib/perl5/vendor
+_perl/5.10.0/x86_64-linux-thread-multi/features.ph line 175.
Operator or semicolon missing before &__inline at (eval 99) line 1.
Ambiguous use of & resolved as operator & at (eval 99) line 1.
None of those are relevant to this code...
As for your statement:
There's no bug---the error messages mean exactly what they say. To find the way that you got there, let's start at the beginning.
Since you didn't find the problem, it's obvious that, while the error messages may mean "exactly what they say", you don't know what that is any more than I did.
They do mean what they say, but what they say is not exactly clear (or we wouldn't have had so many people like you and me confused over what is(was) going on...)
| [reply] [d/l] [select] |
Re: Don't understand why can't get 'size' assoc w/STDOUT
by wind (Priest) on Apr 17, 2011 at 03:09 UTC
|
my $fh = \*STDOUT;
| [reply] [d/l] |
Re: Don't understand why can't get 'size' assoc w/STDOUT
by tchrist (Pilgrim) on Apr 17, 2011 at 20:54 UTC
|
| [reply] |
|
|
In fact, your "complete solution" appears as in reply to Re^6: Failing to get current TTY's rows & columns....
It's entitled Application of Clarke's 3rd Law and is in a related, but distinct thread, namely, "Failing to get..."
Changing the title of a node in a thread so drasticly is (at least semi-) deprecated, but allowed when the changed title incorporates the original. See Corion's replies in threadiquette - thread etiquette, and tye's comments. tye has also commented and further explained -- repeatedly (as illustrated by the previous citations and others not selected for inclusion here).
| [reply] |
|
|
FWIW, it is neither de-parented nor lost
| [reply] |