Filehandles, files, directories

sysopen#

Open a file the low-level way, passing an integer MODE bitmask straight through to the underlying open(2) system call.

sysopen is the escape hatch for when open’s string-mode DWIM is in your way — when you need O_EXCL for an atomic create-if-not-exist, O_NOFOLLOW to refuse symlinks, O_NONBLOCK on a FIFO, or any other flag that has no corresponding "<" / ">" / ">>" string. The flag constants live in Fcntl and are combined with |.

Synopsis#

sysopen FILEHANDLE, FILENAME, MODE
sysopen FILEHANDLE, FILENAME, MODE, PERMS

What you get back#

1 on success, undef on failure (with $! set to the errno from the failed syscall). Unlike open, there is no magic — FILENAME is taken literally, no leading >, <, |, or - is interpreted, no layered mode string is parsed.

If FILEHANDLE is an undefined scalar, it is autovivified into a fresh handle:

sysopen my $fh, $path, O_RDONLY
    or die "sysopen $path: $!";

MODE — the Fcntl bitmask#

MODE is an integer built by OR-ing constants from Fcntl. The access mode is mandatory and mutually exclusive; the modifier flags are optional and combined freely:

Flag

Meaning

O_RDONLY

Open for reading only.

O_WRONLY

Open for writing only.

O_RDWR

Open for reading and writing.

O_CREAT

Create the file if it does not exist.

O_EXCL

With O_CREAT: fail if the file already exists.

O_APPEND

Every write seeks to end-of-file first (atomic append).

O_TRUNC

Truncate the file to zero length on open.

O_NONBLOCK

Non-blocking mode for the resulting descriptor.

O_NOFOLLOW

Fail with ELOOP if the final path component is a symlink.

Import them by name or with a tag:

use Fcntl qw(O_RDWR O_CREAT O_EXCL O_APPEND);
# or
use Fcntl ':DEFAULT';

The legacy values 0, 1, 2 for read-only, write-only, read-write also work on every system Perl supports, but name the constants — the resulting code survives cross-platform review and reads without a glossary.

PERMS — the create-time permission bits#

PERMS is an octal mode (like chmod takes) applied to the inode only when O_CREAT actually creates the file. If PERMS is omitted, Perl passes 0666. The process umask is applied by the kernel on top, so a default umask of 022 yields 0644 on disk.

sysopen my $fh, $path, O_WRONLY | O_CREAT | O_EXCL, 0600
    or die "create $path: $!";

Do not write 0644 as the PERMS argument without a reason. A hard-coded 0644 strips group-write even from users who deliberately set a permissive umask; 0666 plus the user’s umask is the portable default and is what sysopen uses when you omit the argument entirely.

Global state it touches#

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

  • ${^OPEN} — the open pragma’s layer configuration. sysopen applies the same default PerlIO stack open would apply for a layer-less call. Use binmode immediately after a successful sysopen when you need to override or strip layers (typical for binary files, sockets-as-files, or O_NONBLOCK descriptors).

  • Process umask — masks PERMS when O_CREAT creates the file.

Examples#

Atomic create-if-not-exist — the canonical reason to reach for sysopen:

use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
sysopen my $fh, "lock.pid", O_WRONLY | O_CREAT | O_EXCL, 0644
    or die "another instance is already running: $!";
print $fh "$$\n";
close $fh;

Open-or-create for read/write, preserving existing contents:

use Fcntl qw(O_RDWR O_CREAT);
sysopen my $fh, $path, O_RDWR | O_CREAT, 0666
    or die "sysopen $path: $!";

Append-only log with the append guarantee — every write lands at EOF regardless of interleaving with other writers:

use Fcntl qw(O_WRONLY O_CREAT O_APPEND);
sysopen my $log, "/var/log/app.log", O_WRONLY | O_CREAT | O_APPEND, 0644
    or die "open log: $!";
print $log "startup\n";

Open a FIFO without blocking on the reader-side handshake:

use Fcntl qw(O_RDONLY O_NONBLOCK);
sysopen my $fifo, "/tmp/q", O_RDONLY | O_NONBLOCK
    or die "sysopen fifo: $!";

Refuse to follow a symlink at the leaf — useful in directories writable by untrusted users:

use Fcntl qw(O_RDONLY O_NOFOLLOW);
sysopen my $fh, "$dir/config", O_RDONLY | O_NOFOLLOW
    or die "sysopen config: $!";

Force byte-level I/O after opening — strip any :utf8 or :crlf layer the default PerlIO stack would otherwise install:

sysopen my $fh, $path, O_RDONLY or die $!;
binmode $fh;

Edge cases#

  • O_EXCL without O_CREAT is a no-op. The exclusivity check only triggers when O_CREAT asks the kernel to create the file. Always write them together: O_CREAT | O_EXCL.

  • O_EXCL is not a lock. It prevents a successful open on an already-existing file, nothing more. Once the file exists, every subsequent sysopen with O_CREAT | O_EXCL fails. It does not serialize access to the contents — use flock for that.

  • O_EXCL on network filesystems. NFS implementations vary; older NFS versions silently lose the exclusive semantics. Do not rely on O_EXCL across NFSv2.

  • O_CREAT | O_EXCL and symlinks. With both flags set, the kernel refuses to open a pre-existing symlink at the final path component. It does not protect intermediate path components; use O_NOFOLLOW or resolve the directory with opendir plus openat semantics if you need that.

  • O_TRUNC with O_RDONLY. Behavior is undefined by POSIX. Do not combine them.

  • Omitted PERMS. The default is 0666, masked by umask. It is not 0644. Omit PERMS unless you actively want to override the user’s umask.

  • FILEHANDLE as expression. If FILEHANDLE is a bareword, it names a package global. If it is an expression evaluating to a glob, globref, or IO::Handle, that handle is opened. If it is an undefined scalar, a fresh handle is autovivified into it — the lexical form used throughout these examples.

  • No magic on FILENAME. A FILENAME of "-" opens a file literally named -, not STDIN. A leading > opens a file literally named >filename, not a write to filename. This is the entire point of sysopen versus open.

  • Default PerlIO layers still apply. sysopen is “low-level” relative to the mode string, not relative to PerlIO. The resulting handle has the same layer stack as a layer-less open; call binmode to change it.

  • Closed or invalid FILEHANDLE expression. Returns undef with $! set (typically EBADF for a bad existing descriptor; ENOENT, EACCES, EEXIST, ELOOP for the path-level errors).

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • open — higher-level opener with string-mode parsing, pipes, - as STDIN / STDOUT, and layered modes; use it unless you specifically need a flag sysopen exposes

  • fcntl — change flags on an already-open descriptor (e.g. set O_NONBLOCK after the fact); shares the Fcntl constant vocabulary

  • sysread — unbuffered read; works on any handle, not just one obtained from sysopen, despite the name

  • syswrite — unbuffered write, same note

  • Fcntl — the module that exports O_* constants and the :DEFAULT / :flock / :mode tags

  • binmode — set or strip PerlIO layers on the handle after opening

  • umask — the process mask that subtracts bits from PERMS when O_CREAT creates the file