php-io-extensions / posi
PHP-Controllable UNIX POSIX Extension
Package info
github.com/php-io-extensions/posi
Language:C
Type:php-ext
Ext name:ext-posi
pkg:composer/php-io-extensions/posi
Requires
- php: >=8.0
README
This project provides bindings to the UNIX Portable Operating System Interface (POSIX).
Extension repository: https://github.com/php-io-extensions/posi
Highlights
- Retrieve file descriptors to replace
fopen - Read and write to file descriptors beyond streams, replacing
freadandfwrite - Direct access to
fcntl - Direct access to
ioctl
Requirements
- PHP 8.3+
Compilation Requirements
-
A C language toolchain
-
Manual installation
- Zephir PHP
- ext-zephir_parser
-
Automated installation
- PIE
Manual compilation instructions
Zephir
- Clone this repository and checkout the release to use (or master for the latest updates)
cd posi- Run the installer for your platform
- Raspberry Pi:
bash install-debian-trixie.sh - macOS:
bash install-macos.sh - Jetson Orin (Nano Super):
bash install-jetpack6.sh - The installer will compile the extension and add it to your PHP ini automatically
- Raspberry Pi:
PIE
- NOTE: If using Laravel Herd to serve PHP, use Zephir via the install scripts.
- Clone this repository and checkout the release to use (or master for the latest updates)
cd posipie install
Automated compilation instructions
-
pie install php-io-extensions/posi -
The installer should compile and stash the extension where the default PHP binary stores its ini.
-
If PIE does not automatically enable the extension, add it to your
php.ini(module name is typicallypposix):extension=posi
Usage
POSIX I/O is invoked through the Posi\System class. All methods are static.
<?php use Posi\System; $fd = System::open('/dev/null', O_RDWR); System::close($fd);
open(), fcntl(), and ioctl() take the same flag and command constants as C (O_RDONLY, F_GETFL, and so on). Define them in PHP or load them from your platform headers; values differ by OS.
C-ish global helper style
This extension can also be used in a C-ish style through global helper methods by installing microscrap/posix.
composer require microscrap/posix
That package wraps Posi\System and exposes global functions like posix_open, posix_read, and posix_write. See the repository for details: https://github.com/microscrap/posix.
You can also roll your own helper layer, or use this extension directly without helper packages.
Static Methods API
System::open(string $filepath, int $flags, int $mode = 0644): int
Opens a path and returns a file descriptor (non-negative integer), or -1 on failure (same semantics as C open(2)).
$filepath— device node, pipe, socket path, or regular file path.$flags—O_*access mode and creation flags (e.g.O_RDWR,O_CREAT | O_TRUNC).$mode— permission bits whenO_CREATis set (default0644).
Example — open an existing file for reading
<?php use Posi\System; // O_RDONLY is platform-specific; 0 on Linux/glibc. $fd = System::open('/etc/hosts', 0); if ($fd < 0) { throw new RuntimeException('open failed'); } $chunk = System::read($fd, 256); System::close($fd);
Example — create and truncate a file for read/write
<?php use Posi\System; // Linux/glibc: O_RDWR | O_CREAT | O_TRUNC $flags = 2 | 64 | 512; $fd = System::open('/tmp/posi-demo.txt', $flags, 0644); if ($fd < 0) { throw new RuntimeException('open failed'); } System::write($fd, "hello\n", 6); System::close($fd);
System::close(int $fd): int
Closes a file descriptor. Returns 0 on success and -1 on failure (same semantics as C close(2)).
Example
<?php use Posi\System; $fd = System::open('/dev/null', O_RDWR); $result = System::close($fd); // $result === 0 on success
System::chmod(string $path, int $mode): int
Sets permission bits on a path. Returns 0 on success and -1 on failure (same semantics as C chmod(2)).
$path— file or directory path.$mode— permission bits (e.g.0644). Combine withS_ISUID,S_ISGID, andS_ISVTXwhere your platform supports them.
Changing modes on paths you do not own, or setting set-user-ID bits, typically requires appropriate privileges.
Example
<?php use Posi\System; $path = '/tmp/posi-chmod-demo.txt'; $fd = System::open($path, 2 | 64 | 512, 0644); System::close($fd); if (System::chmod($path, 0600) !== 0) { throw new RuntimeException('chmod failed'); }
System::chown(string $path, int $owner, int $group): int
Sets owner and group for a path. Returns 0 on success and -1 on failure (same semantics as C chown(2)).
$owner— numeric user ID (uid_t).$group— numeric group ID (gid_t).
This call usually requires superuser privileges or capability CAP_CHOWN on Linux.
Example
<?php use Posi\System; $path = '/tmp/owned-by-me.txt'; $fd = System::open($path, 2 | 64 | 512, 0644); System::close($fd); $uid = (int) posix_getuid(); $gid = (int) posix_getgid(); if (System::chown($path, $uid, $gid) !== 0) { throw new RuntimeException('chown failed'); }
System::fchmod(int $fd, int $mode): int
Like chmod, but applies to an open file descriptor. Returns 0 on success and -1 on failure (same semantics as C fchmod(2)).
Example
<?php use Posi\System; $fd = System::open('/tmp/posi-fchmod.txt', 2 | 64 | 512, 0644); if (System::fchmod($fd, 0600) !== 0) { System::close($fd); throw new RuntimeException('fchmod failed'); } System::close($fd);
System::fchown(int $fd, int $owner, int $group): int
Like chown, but applies to the file referred to by an open descriptor. Returns 0 on success and -1 on failure (same semantics as C fchown(2)).
Example
<?php use Posi\System; $fd = System::open('/tmp/posi-fchown.txt', 2 | 64 | 512, 0644); $uid = (int) posix_getuid(); $gid = (int) posix_getgid(); if (System::fchown($fd, $uid, $gid) !== 0) { System::close($fd); throw new RuntimeException('fchown failed'); } System::close($fd);
System::write(int $fd, string $glob_of_bytes, int $num_bytes_to_write): int
Writes up to $num_bytes_to_write bytes from $glob_of_bytes to the descriptor. Returns the number of bytes written, or -1 on failure (same semantics as C write(2)).
Example — write a line to a file descriptor
<?php use Posi\System; $fd = System::open('/tmp/posi-demo.txt', 2 | 64 | 512, 0644); $payload = "ping\n"; $written = System::write($fd, $payload, strlen($payload)); // $written === 5 when all bytes were accepted System::close($fd);
Example — write to /dev/null
<?php use Posi\System; $fd = System::open('/dev/null', O_WRONLY); System::write($fd, str_repeat('x', 1024), 1024); System::close($fd);
System::read(int $fd, int $amt_of_bytes_to_read): string
Reads up to $amt_of_bytes_to_read bytes from the descriptor. Returns a binary string of the bytes read (length may be less than requested). Returns false if the underlying read(2) fails.
Example — read the first 512 bytes of a file
<?php use Posi\System; $fd = System::open('/etc/hosts', 0); $data = System::read($fd, 512); if ($data === false) { throw new RuntimeException('read failed'); } echo $data; System::close($fd);
Example — read from a pipe in a loop
<?php use Posi\System; $fd = /* descriptor from pipe, socket, or device */; while (true) { $chunk = System::read($fd, 4096); if ($chunk === false) { break; } if ($chunk === '') { break; // EOF } // process $chunk }
System::getuid(): int
Returns the real user ID of the calling process (same semantics as C getuid(2)). This call always succeeds from the extension’s perspective.
Example
<?php use Posi\System; $uid = System::getuid();
System::setuid(int $uid): int
Sets the real user ID of the calling process. Returns 0 on success and -1 on failure (same semantics as C setuid(2)). Changing UID typically requires appropriate privileges.
Example
<?php use Posi\System; if (System::setuid(65534) !== 0) { // not privileged or operation denied }
System::umask(int $mask): int
Sets the file mode creation mask. Returns the previous umask value (same semantics as C umask(2)). Values are numeric (for example octal 022 is the integer 18 on typical platforms).
Example
<?php use Posi\System; $previous = System::umask(0077); // restore prior mask System::umask($previous);
System::lseek(int $fd, int $offset, int $whence): int
Repositions the offset of the open descriptor. Returns the new offset measured in bytes from the beginning of the file, or -1 on failure (same semantics as C lseek(2)). Use SEEK_SET, SEEK_CUR, and SEEK_END from your platform.
Example
<?php use Posi\System; $fd = System::open('/etc/hosts', 0); $pos = System::lseek($fd, 0, SEEK_SET); System::close($fd);
System::recv(int $fd, int $len, int $flags = 0): string|false
Receives up to $len bytes from a socket descriptor (same semantics as C recv(2)). Returns a binary string (possibly shorter than $len), or false on failure. Optional $flags are the usual MSG_* constants for your platform (0 to block until data is available).
Example
<?php use Posi\System; // $sock must be a connected socket FD from System::socket() when implemented, // or another API that returns a socket descriptor. // $data = System::recv($sock, 4096, 0);
System::readv(int $fd, array $iovecs): array|false
Scatter-read into several buffers in one syscall (readv(2)). Each element of $iovecs is an associative array:
| Key | Meaning |
|---|---|
len |
Required. Maximum bytes to place in this segment. |
base |
Optional. If present as a string, up to min(strlen($base), len) bytes are copied into the segment before the read (usually you omit this for a fresh read buffer). |
On success, returns:
| Key | Meaning |
|---|---|
res |
Total bytes read (non-negative), or 0 at EOF. |
buffers |
List of binary strings, one per $iovecs entry, each truncated to the portion of the read that landed in that segment. |
Returns false if the syscall fails or if $iovecs is invalid.
Example — two segments from a regular file
<?php use Posi\System; $fd = System::open('/etc/hosts', 0); $out = System::readv($fd, [ ['len' => 4], ['len' => 8], ]); if ($out !== false) { // $out['res'] — total bytes read // $out['buffers'][0], $out['buffers'][1] — segment contents } System::close($fd);
System::fcntl(int $fd, int $command, mixed $arguments = null): array
Invokes fcntl(2) on the descriptor. Always returns an associative array:
| Key | Meaning |
|---|---|
res |
Return value from fcntl (non-negative on success, -1 on failure). |
val |
Command-specific value: flag integer for getters like F_GETFL, false on error, flock array for lock commands, or echo of the argument for setters. |
$arguments may be omitted, an integer, a boolean, or (for F_GETLK / F_SETLK / F_SETLKW) an array with keys: type, whence, start, len, pid.
Example — read open-file status flags (F_GETFL)
<?php use Posi\System; $fd = System::open('/tmp/posi-demo.txt', 0); $result = System::fcntl($fd, F_GETFL); // $result['res'] — fcntl status // $result['val'] — current O_* flags as integer $flags = $result['val']; System::close($fd);
Example — enable non-blocking I/O (F_SETFL)
<?php use Posi\System; $fd = System::open('/tmp/posi-demo.txt', 0); $get = System::fcntl($fd, F_GETFL); $newFlags = $get['val'] | O_NONBLOCK; System::fcntl($fd, F_SETFL, $newFlags); System::close($fd);
Example — advisory lock (F_SETLK)
<?php use Posi\System; $fd = System::open('/tmp/posi-demo.txt', 2); $result = System::fcntl($fd, F_SETLK, [ 'type' => F_WRLCK, 'whence' => SEEK_SET, 'start' => 0, 'len' => 0, 'pid' => 0, ]); // $result['val'] contains flock fields after the operation System::close($fd);
System::ioctl(int $fd, int $command, mixed $arguments = null): array
Invokes ioctl(2) on the descriptor. Returns the same shape as fcntl:
| Key | Meaning |
|---|---|
res |
Return value from ioctl. |
val |
Output value: integer, string buffer, false on failure, or argument echo depending on command and $arguments. |
$arguments may be omitted (null), an integer pointer value, a string buffer, or an array:
['value' => int]— read/write a single integer through the ioctl (value updated inval).['bytes' => string]or['data' => string]— binary buffer in/out.
Example — ioctl with no argument (device-specific command)
<?php use Posi\System; $fd = System::open('/dev/null', O_RDWR); // Replace SOME_IOCTL_CMD with your driver's command constant. $result = System::ioctl($fd, SOME_IOCTL_CMD); // $result['res'], $result['val'] System::close($fd);
Example — pass an integer argument
<?php use Posi\System; $fd = System::open('/dev/some-device', O_RDWR); $result = System::ioctl($fd, SOME_IOCTL_CMD, 0); // On success, $result['val'] may reflect the argument or driver output. System::close($fd);
Example — integer in/out via array
<?php use Posi\System; $fd = System::open('/dev/some-device', O_RDWR); $result = System::ioctl($fd, SOME_IOCTL_CMD, ['value' => 1]); // $result['val'] holds the updated integer after ioctl $out = $result['val']; System::close($fd);
Example — binary buffer in/out
<?php use Posi\System; $fd = System::open('/dev/some-device', O_RDWR); $buffer = str_repeat("\0", 32); $result = System::ioctl($fd, SOME_IOCTL_CMD, ['data' => $buffer]); // $result['val'] is the buffer returned from the driver $response = $result['val']; System::close($fd);
System::wait(?int &$status = null): int
Blocks until any child process exits (implemented with waitpid(-1, …, 0), same idea as wait(2)). Returns the child PID on success, or -1 on error (for example ECHILD when there are no children). If you pass $status by reference, it receives the raw wait status integer for use with pcntl_wifexited(), pcntl_wexitstatus(), and related helpers (or your own bit tests). If the call fails, $status is not updated.
Example
<?php use Posi\System; $pid = pcntl_fork(); if ($pid === 0) { exit(42); } $status = 0; $got = System::wait($status); // decode with pcntl_wexitstatus($status) when pcntl_wifexited($status)
System::waitpid(int $pid, ?int &$status = null, int $options = 0): int
Wraps waitpid(2). Use $pid = -1 to wait for any child (same as wait()). $options is typically 0 (block) or WNOHANG (do not block). Returns the child PID on success when a child was reaped, 0 when $options includes WNOHANG and no child was ready, or -1 on error. The raw status word is written to $status by reference only when the return value is greater than zero (a child was reaped).
Example
<?php use Posi\System; $status = 0; $pid = System::waitpid(-1, $status, WNOHANG);
System::hostname(): string|false
Returns the current host name via gethostname(2) (same underlying call as PHP’s gethostname()). Returns false if the syscall fails.
Example
<?php use Posi\System; $host = System::hostname();
System::lstat(string $path): array|false
Returns file status information via lstat(2). Unlike stat(2), lstat does not follow symbolic links — it reports metadata for the link itself.
On success, returns an associative array with the same keys as PHP’s built-in lstat():
| Key | Meaning |
|---|---|
dev |
Device number of the filesystem containing the file. |
ino |
Inode number. |
mode |
File type and permissions (st_mode). |
nlink |
Number of hard links. |
uid |
Owner user ID. |
gid |
Owner group ID. |
rdev |
Device type (for special files). |
size |
Total size in bytes. |
blksize |
Preferred I/O block size. |
blocks |
Number of 512-byte blocks allocated. |
atime |
Last access time (Unix timestamp). |
mtime |
Last modification time (Unix timestamp). |
ctime |
Last inode change time (Unix timestamp). |
Returns false if the syscall fails.
Example
<?php use Posi\System; $info = System::lstat('/tmp/my-symlink'); if ($info !== false) { // $info['size'], $info['mode'], etc. }
Quick reference
| Method | Signature |
|---|---|
open |
System::open(string $filepath, int $flags, int $mode = 0644): int |
chmod |
System::chmod(string $path, int $mode): int |
chown |
System::chown(string $path, int $owner, int $group): int |
fchmod |
System::fchmod(int $fd, int $mode): int |
fchown |
System::fchown(int $fd, int $owner, int $group): int |
close |
System::close(int $fd): int |
write |
System::write(int $fd, string $glob_of_bytes, int $num_bytes_to_write): int |
read |
System::read(int $fd, int $amt_of_bytes_to_read): string |
getuid |
System::getuid(): int |
setuid |
System::setuid(int $uid): int |
umask |
System::umask(int $mask): int |
lseek |
System::lseek(int $fd, int $offset, int $whence): int |
recv |
System::recv(int $fd, int $len, int $flags = 0): string|false |
readv |
System::readv(int $fd, array $iovecs): array|false |
fcntl |
System::fcntl(int $fd, int $command, mixed $arguments = null): array |
ioctl |
System::ioctl(int $fd, int $command, mixed $arguments = null): array |
wait |
System::wait(?int &$status = null): int |
waitpid |
System::waitpid(int $pid, ?int &$status = null, int $options = 0): int |
hostname |
System::hostname(): string|false |
lstat |
System::lstat(string $path): array|false |