I/O · Fixed-length data

sysseek#

Reposition a filehandle at the system level, bypassing PerlIO buffering.

sysseek moves FILEHANDLE’s kernel file-descriptor offset to a new byte position by calling the underlying lseek(2) directly. It is the companion of sysread and syswrite: the three form the unbuffered I/O family, which talks to the kernel without going through Perl’s :perlio or stdio-like buffer stack. Use sysseek on handles you access with the sys-family; use seek on handles you access with read, readline, or print.

Synopsis#

sysseek FILEHANDLE, POSITION, WHENCE
sysseek $fh, 0, 0              # rewind to start
sysseek $fh, 0, 1              # report current position
sysseek $fh, -1024, 2          # 1024 bytes before EOF

What you get back#

The new absolute byte offset on success, or undef on failure (with $! set). A position of zero is returned as the dualvar string "0 but true" so that the value prints as 0 in numeric context yet remains truthy in boolean context — a check like if (sysseek ...) is safe at offset zero.

defined(sysseek $fh, $offset, 0)
    or die "sysseek to $offset failed: $!";

Because the return value carries the new offset, sysseek doubles as a “where am I” primitive — the reason the idiomatic systell is one line:

use Fcntl 'SEEK_CUR';
sub systell { sysseek($_[0], 0, SEEK_CUR) }

WHENCE values#

WHENCE is an integer with three meaningful values. Prefer the symbolic constants from Fcntl — they also make the call portable to platforms that do not use 0 / 1 / 2:

  • 0 / SEEK_SETPOSITION is measured from the start of the file. POSITION must be non-negative.

  • 1 / SEEK_CURPOSITION is added to the current position. Negative values move backward, positive values forward. sysseek $fh, 0, 1 returns the current offset without moving the pointer.

  • 2 / SEEK_ENDPOSITION is added to the end-of-file offset. POSITION is typically zero or negative.

use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);

sysseek $fh, 0,   SEEK_SET;    # rewind
sysseek $fh, 0,   SEEK_END;    # move to EOF
sysseek $fh, -$n, SEEK_CUR;    # $n bytes back from here

Bytes, not characters#

sysseek operates on byte offsets, always. Even when the handle has a character-oriented layer such as :encoding(UTF-8), the offset it accepts and returns is a raw byte count. This matches the kernel view but is irrelevant in practice: sysseek is meant for handles that do not use PerlIO layers, and mixing it with a decoding layer is one of the confusion traps listed below.

If the data is character-oriented, use seek and tell on the buffered side instead of sysseek.

Do not mix with buffered I/O#

sysseek talks directly to the file descriptor. read, readline, print, write, seek, tell, and eof talk to the PerlIO buffer sitting in front of the descriptor. The buffer’s position and the kernel’s position are independent — a sysseek moves the kernel pointer but does not flush or invalidate the buffer, so the next buffered read returns stale bytes from before the sysseek, and the next buffered write lands wherever the buffer thought it was.

The rule is simple: pick one family per handle.

Global state it touches#

  • $! — set to the system error message on failure.

  • Unlike seek, sysseek does not flush or discard the PerlIO buffer and does not clear the EOF flag on the handle. That asymmetry is the source of the “mixing” hazard above and the reason the two families must not share a handle.

Examples#

Random-access read of a fixed-length record. The file holds 128-byte records; jump straight to record 42:

use Fcntl 'SEEK_SET';
sysopen my $fh, $path, O_RDONLY or die $!;
sysseek $fh, 42 * 128, SEEK_SET or die "sysseek: $!";
sysread $fh, my $rec, 128       or die "sysread: $!";

Report the current offset without moving the pointer — the systell idiom:

use Fcntl 'SEEK_CUR';
my $pos = sysseek $fh, 0, SEEK_CUR;
defined $pos or die "sysseek: $!";
print "at byte $pos\n";

Append to a file held open for read-write, then rewind to replay from the start:

use Fcntl qw(SEEK_SET SEEK_END);
sysseek $fh, 0, SEEK_END or die $!;
syswrite $fh, $record     or die $!;
sysseek $fh, 0, SEEK_SET or die $!;

Zero is still true — the dualvar lets you write the terse form without accidentally treating a successful rewind as a failure:

if (my $pos = sysseek $fh, 0, 0) {
    # entered even when $pos stringifies to "0"
    printf "rewound; pos=%d\n", $pos;
}
else {
    die "sysseek failed: $!";
}

Seek past end-of-file to create a sparse hole, then write the tail marker:

use Fcntl 'SEEK_SET';
sysseek $fh, 1_000_000, SEEK_SET or die $!;
syswrite $fh, "END"               or die $!;

Edge cases#

  • Unseekable handles: pipes, sockets, TTYs, and most special devices fail with $! set to ESPIPE (Illegal seek). The return value is undef; always check with defined.

  • Do not mix with buffered I/O on the same handle. sysseek bypasses the PerlIO buffer; read, readline, print, write, seek, tell, and eof use it. Interleaving the two families produces silently wrong results. Pick one family per handle and stay with it.

  • Zero stringifies as "0 but true". Boolean tests work at offset zero — if (sysseek ...) stays truthy — but string comparisons against the literal "0" do not:

    my $pos = sysseek $fh, 0, 0;
    $pos == 0     or die;           # true
    $pos eq "0"   and die "nope";   # false — the string is "0 but true"
    

    Use defined for success/failure detection and numeric comparison for the offset value.

  • Encoding layers: sysseek on a handle with an :encoding(...) layer is almost always a mistake — the byte offset can land mid codepoint, and the sys-family is meant for raw bytes anyway. If the data needs decoding, use seek with the buffered family.

  • Negative offsets with SEEK_SET: POSITION must be non-negative when WHENCE is 0. A negative value fails with EINVAL.

  • Very large files: sysseek returns the kernel’s full 64-bit offset; positions beyond 2 GiB round-trip correctly through subsequent sysseek calls on all supported 64-bit builds.

  • Closed filehandle: returns undef and sets $!; under use warnings a sysseek() on closed filehandle warning is emitted.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • seek — buffered counterpart; use it on handles accessed via read, readline, or print

  • sysread — the unbuffered read paired with sysseek; both skip PerlIO and speak directly to the file descriptor

  • syswrite — the unbuffered write; the third member of the family that belongs on the same handle as sysseek

  • sysopen — opens a handle at the descriptor level, the usual way to get a handle you will later drive with the sys-family

  • tell — buffered-side current-offset query; pair it with seek, not with sysseek

  • Fcntl — source of the SEEK_SET, SEEK_CUR, SEEK_END constants