Filehandles, files, directories

ioctl#

Perform a device-control ioctl(2) system call on a filehandle.

ioctl is the direct Perl binding for the POSIX ioctl(2) syscall — the catch-all kernel interface for device-specific operations that do not fit the regular read/write model: terminal window size, serial line parameters, socket device queries, tape positioning, and anything else a driver chooses to expose through a request code. It dispatches FUNCTION against FILEHANDLE, passing SCALAR as the third argument — either a small integer or a pointer to a byte buffer, depending on what the specific FUNCTION expects.

Unlike fcntl, whose request codes live in the Fcntl module, ioctl request codes are almost never exported as ready-made constants. The usual path is:

require "sys/ioctl.ph";

which pulls in the translated <sys/ioctl.h> header. If that file does not exist, or does not define the code you need, build it from the C headers with the h2ph tool shipped with Perl, or define the constant yourself from the kernel header.

Synopsis#

require "sys/ioctl.ph";
ioctl FILEHANDLE, FUNCTION, SCALAR
ioctl($fh, $request, $buf)
my $rv = ioctl($fh, $request, $arg) || -1;

What you get back#

On success, the integer the kernel returned. When that integer is 0, Perl substitutes the dual-valued string "0 but true" — true in boolean context, numeric 0 in numeric context, and exempt from the Argument "..." isn't numeric warning. This means a plain boolean test reliably distinguishes success from failure even for requests whose success value is literally zero:

ioctl($fh, $request, $buf) or die "ioctl: $!";

On failure, ioctl returns undef and sets $! to the relevant errno. The full mapping:

OS returns

Perl returns

-1

undef

0

string "0 but true"

any other value

that integer

When you need the raw kernel return — for request codes that encode information in the return value itself — use the idiom from perlfunc:

my $retval = ioctl($fh, $request, $arg) || -1;
printf "System returned %d\n", $retval;

How SCALAR is used#

SCALAR is handed to the kernel as the third argument of the C ioctl call, and its role depends on the request code:

  • Pointer to a buffer. The usual case. Most ioctl requests read from or write into a C struct whose layout is defined by the driver. Build the buffer with pack before the call and, for requests that write back, decode with unpack afterward. Pre-size the scalar to at least the struct’s length — Perl will grow an undersized scalar automatically, but pre-sizing makes the intent obvious and avoids surprises if the driver writes more than you expected.

  • Small integer. A minority of requests take a bare integer rather than a pointer. If SCALAR has no string value but does have a numeric value, Perl passes the number instead of a pointer. To guarantee this dispatch — even for a scalar that might have been stringified earlier — add 0 to it first:

    my $n = 0 + $n;      # force numeric representation
    ioctl($fh, $request, $n);
    
  • Ignored. For request codes that take no third argument, pass 0 as a placeholder. The kernel ignores it; the convention keeps the call readable.

Global state it touches#

  • $! — set on failure to the errno returned by ioctl(2).

  • Device or descriptor state on FILEHANDLEioctl typically mutates kernel state associated with the underlying file descriptor or its device (terminal attributes, socket options, tape position). That state is not Perl-visible through any other variable; read it back with another ioctl call using the matching TIOCG... / SIOCG... request.

Examples#

Query the terminal window size on Linux. TIOCGWINSZ writes a struct winsize (two unsigned short rows/cols plus two ignored fields) into the buffer:

require "sys/ioctl.ph";

my $winsize = "\0" x 8;
ioctl(STDOUT, TIOCGWINSZ(), $winsize)
    or die "TIOCGWINSZ: $!";
my ($rows, $cols) = unpack('S S', $winsize);
print "terminal is $rows rows by $cols cols\n";

Count bytes available for reading on a socket without consuming them (FIONREAD is widely supported across Unixes):

require "sys/ioctl.ph";

my $pending = pack('L', 0);
ioctl($sock, FIONREAD(), $pending)
    or die "FIONREAD: $!";
my $n = unpack('L', $pending);
print "$n bytes pending\n";

Put a socket into non-blocking mode through the FIONBIO request — an alternative to the fcntl / F_SETFL approach, and the form historically portable to some older systems:

require "sys/ioctl.ph";

my $on = pack('L', 1);
ioctl($sock, FIONBIO(), $on)
    or die "FIONBIO: $!";

Distinguishing “syscall returned 0” from “syscall failed” using the "0 but true" convention:

my $rv = ioctl($fh, $request, $buf);
if (!defined $rv) {
    die "ioctl failed: $!";
} elsif ($rv eq "0 but true") {
    # kernel returned 0 — success with a zero value
} else {
    # kernel returned $rv
}

Rolling your own request code when sys/ioctl.ph is missing or incomplete. The encoding (_IOR / _IOW / _IOWR) is kernel- specific; on Linux the macro expands to a 32-bit value that you can precompute and hardcode:

use constant TIOCGWINSZ => 0x5413;   # Linux <asm-generic/ioctls.h>

my $winsize = "\0" x 8;
ioctl(STDOUT, TIOCGWINSZ, $winsize) or die $!;

Edge cases#

  • Forgot the .ph require: constants like TIOCGWINSZ parse as barewords with no defined value, triggering an EINVAL from the kernel. Either require "sys/ioctl.ph" or define the constant yourself with use constant.

  • "0 but true" return: do not compare the return with == 0 as a failure test. Use a plain boolean (or die) or defined. Comparing for equality to the literal string also works ($rv eq "0 but true") but is rarely necessary.

  • Scalar/integer dispatch surprise: a scalar that was numeric but got stringified — for instance via interpolation into a message — flips from integer-argument to buffer-pointer semantics on the next call. Force numeric representation with 0 + $x when the request code expects an integer.

  • Struct layout is platform-specific. struct winsize, struct termios, struct ifreq, and friends differ between kernels and even between architectures on the same kernel. Do not hardcode pack templates from memory; verify against the target system’s header.

  • Undersized output buffer. If the driver writes more bytes than SCALAR currently holds, the scalar is grown, but you have no hint in Perl that this happened. Pre-size to at least the struct’s length with "\0" x N so that reads past the end of the buffer are driver bugs, not Perl bugs.

  • Not every system implements every request. ioctl itself is POSIX, but specific request codes are driver-defined. An unsupported request fails with ENOTTY or EINVAL; an ioctl call on a system without ioctl(2) at all raises an exception.

  • ioctl vs fcntl: identical argument-processing and return-value convention, different kernel interface. Use fcntl for descriptor flags and POSIX locks; use ioctl for device control codes.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • fcntl — same calling and return-value convention; used for descriptor-level flags and advisory locks rather than device-specific control codes

  • pack / unpack — required for building and decoding the struct buffers most ioctl requests exchange with the kernel

  • open / sysopen — open the device or special file whose driver the ioctl request will talk to

  • binmode — some ioctl requests (e.g. terminal mode changes) interact with PerlIO buffering; switch to raw mode when the driver and PerlIO disagree about line discipline

  • select / sysread — the normal I/O path; reach for ioctl only when the operation you need is not expressible as a read or write