--- name: sysseek signature: 'sysseek FILEHANDLE, POSITION, WHENCE' since: 5.004 status: documented categories: ["I/O", "Fixed-length data"] --- ```{index} single: sysseek; Perl built-in ``` *[I/O](../perlfunc-by-category) · [Fixed-length data](../perlfunc-by-category)* # 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`](sysread) and [`syswrite`](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`](seek) on handles you access with [`read`](read), [`readline`](readline), or [`print`](print). ## Synopsis ```perl 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`](undef) on failure (with [`$!`](../perlvar) set). A position of zero is returned as the dualvar string [`"0 but true"`](../perlop) 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. ```perl 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: ```perl 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`](../../Fcntl) — they also make the call portable to platforms that do not use `0` / `1` / `2`: - `0` / `SEEK_SET` — `POSITION` is measured from the **start of the file**. `POSITION` must be non-negative. - `1` / `SEEK_CUR` — `POSITION` 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_END` — `POSITION` is added to the **end-of-file** offset. `POSITION` is typically zero or negative. ```perl 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`](seek) and [`tell`](tell) on the buffered side instead of `sysseek`. ## Do not mix with buffered I/O `sysseek` talks directly to the file descriptor. [`read`](read), [`readline`](readline), [`print`](print), [`write`](write), [`seek`](seek), [`tell`](tell), and [`eof`](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. - Handle used with [`sysread`](sysread) / [`syswrite`](syswrite) → reposition with `sysseek`. - Handle used with [`read`](read) / [`print`](print) / [`readline`](readline) → reposition with [`seek`](seek). ## Global state it touches - [`$!`](../perlvar) — set to the system error message on failure. - Unlike [`seek`](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: ```perl 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: ```perl 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: ```perl 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: ```perl 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: ```perl use Fcntl 'SEEK_SET'; sysseek $fh, 1_000_000, SEEK_SET or die $!; syswrite $fh, "END" or die $!; ``` ## Edge cases - **Unseekable handles**: pipes, sockets, `TTY`s, and most special devices fail with [`$!`](../perlvar) set to `ESPIPE` (`Illegal seek`). The return value is [`undef`](undef); always check with `defined`. - **Do not mix with buffered I/O on the same handle**. `sysseek` bypasses the PerlIO buffer; [`read`](read), [`readline`](readline), [`print`](print), [`write`](write), [`seek`](seek), [`tell`](tell), and [`eof`](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: ```perl 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`](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`](undef) and sets [`$!`](../perlvar); under `use warnings` a `sysseek() on closed filehandle` warning is emitted. ## Differences from upstream Fully compatible with upstream Perl 5.42. ## See also - [`seek`](seek) — buffered counterpart; use it on handles accessed via [`read`](read), [`readline`](readline), or [`print`](print) - [`sysread`](sysread) — the unbuffered read paired with `sysseek`; both skip PerlIO and speak directly to the file descriptor - [`syswrite`](syswrite) — the unbuffered write; the third member of the family that belongs on the same handle as `sysseek` - [`sysopen`](sysopen) — opens a handle at the descriptor level, the usual way to get a handle you will later drive with the sys-family - [`tell`](tell) — buffered-side current-offset query; pair it with [`seek`](seek), not with `sysseek` - [`Fcntl`](../../Fcntl) — source of the `SEEK_SET`, `SEEK_CUR`, `SEEK_END` constants