Virtual file system ᴾᴴᴾ - chung-leong/zigar GitHub Wiki

JavaScript | PHP


Zigar allows you to access [PHP streams] as though they are regular files in the file system. You can use fread() from the standard C library to read a HTTP stream, for instance. You can use Win32's WriteFile() to write to PHP's output stream. This is actually what happens in many of the examples in this manual. By default, STDERR is redirected to php://output. That's where the text goes whenever we use std.debug.print.

The idea is that many existing libraries are designed with desktop applications in mind. When data is generated, it typically gets saved to a file afterward. A server-side application, on the other hand, would either send the data to the client or persist it in a database. Without an IO redirection mechanism, you would have to resort to saving the data in a temporary file. This is inefficient and carrys the risk of exposing the data of one user to another.

Basic operation

Suppose we have a C program that saves its output to a file:

#include <stdio.h>

int main(int argc, char* argv[]) {
    FILE* file = fopen(argv[1], "w");
    if (!file) return 1;
    fprintf(file, "Hello, %s!\n", argv[2]);
    fclose(file);
    return 0;
}

To make it usable in PHP, we have to first tell the Zig compiler to add it as a module to the build. We accomplish this by adding build.extra.zig in the same directory as our Zig file:

const std = @import("std");

const cfg = @import("build.cfg.zig");

pub fn getCSourceFiles(_: *std.Build, _: anytype) []const []const u8 {
    return &.{cfg.module_dir ++ "main.c"};
}

And the Zig module itself:

extern fn main(c_int, [*c][*c]u8) c_int;

pub fn save(path: [:0]const u8, name: [:0]const u8) !void {
    var args = [_][*c]const u8{ "", path, name };
    if (main(args.len + 1, @ptrCast(&args)) != 0) return error.Unexpected;
}

To use it in PHP, we simply import it as usual:

<?php

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');

$m->save("/php://output", "Ralph Wiggum");

?>
Hello, Ralph Wiggum!

Virtual file path

A virtual file path is simply a stream's URL prefixed by the directory separation character. The purpose of this prefix is to make C and Zig code interpret the path as absolute. A forward slash will work on Windows most of the times but not always. Zigar itself allows its use in lieu of a backslash. The C or Zig code you're trying to might fail to recognize such a path as absolute and procceeds to prepend the current directory. In the case of std.fs.openFileAbsolute(), you application would crash as the path would fail an assertion.

Use the constant DIRECTORY_SEPARATE if compatibility with Windows is important.

Using php://memory and php://temp

The following code saves the result to a memory stream and a temporary stream:

<?php

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');
$m->save("/php://memory", "Ralph Wiggum");
$m->save("/php://temp", "Sideshow Bob");

?>

It's basically useless, since you have no way of retrieving the captured data. To actually make use of these two stream types, you need to first create the stream using fopen(), then use the special export describe() to obtain a virtual file descriptor for the stream. This descriptor can then be used to form the path to a php://fd "file":

<?php

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');

$strm = fopen("php://memory", "w+");
$fd = $m->__zigar->describe($strm);
$m->save("/php://fd/$fd", "Ralph Wiggum");
$data = stream_get_contents($strm, null, 0);
debug_zval_dump($data);
string(21) "Hello, Ralph Wiggum!
" refcount(2)

Redirecting standard streams

Another way of making use of php://memory and php://temp is to redirect STDOUT to one, using the special export redirect():

<?php

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');

$strm = fopen("php://memory", "w+");
$fd = $m->__zigar->redirect('stdout', $strm);
$m->save("/php://stdout", "Ralph Wiggum");
$data = stream_get_contents($strm, null, 0);
debug_zval_dump($data);
Hello, Ralph Wiggum!
string(0) "" interned

Redirecting the file system root

Using redirect(), it's possible to completely override the file system:

<?php

require_once __DIR__ . '/VirtualFSStream.php';

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');

$root = new VirtualDir([
    'output' => new VirtualDir(),
]);
VirtualFSStream::add_root_node('hello', $root);
$dir = opendir("vfs://hello");
$fd = $m->__zigar->redirect('root', $dir);
$m->save("/output/ralph.txt", "Ralph Wiggum");
$data = file_get_contents("vfs://hello/output/ralph.txt");
debug_zval_dump($data);
string(21) "Hello, Ralph Wiggum!
" refcount(2)

VirtualFSStream is a minimalist stream wrapper created to aid the development of php-zigar. It's a good starting point if you choose to implement your own virtual file system.

Selective redirection

On Unix, everything is a file. A complete replacement of the file system can therefore lead to loss of functionalities. Functions in std.Random, for instance, would no longer work as the module wouldn't have access to /dev/random.

To exclude certain directories from redirection, pass a callback function to redirect() in lieu of the resource handle from opendir():

<?php

require_once __DIR__ . '/VirtualFSStream.php';

$m = zigar_use(__DIR__ . '/vfs-example-1/module.zig');

$root = new VirtualDir([
    'output' => new VirtualDir(),
]);
VirtualFSStream::add_root_node('hello', $root);
$dir = opendir("vfs://hello");
$m->__zigar->redirect('root', function($path) use($dir) {
    echo "path = $path\n";
    if (str_starts_with($path, '/tmp/')) return; 
    return $dir;
});
$m->save("/output/ralph.txt", "Ralph Wiggum");
$data = file_get_contents("vfs://hello/output/ralph.txt");
debug_zval_dump($data);
// saving to real file
$m->save("/tmp/lisa.txt", "Lisa Simpson");
$data = file_get_contents("/tmp/lisa.txt");
debug_zval_dump($data);
path = /output/ralph.txt
string(21) "Hello, Ralph Wiggum!
" refcount(2)
path = /tmp/lisa.txt
string(21) "Hello, Lisa Simpson!
" refcount(2)

Implementation notes

Writes to stderr by threads might not be synchronous. Because std.debug.print uses a mutex to ensure coherent writes, there's a possibility of a deadlock situation, where the main thread tries to get a lock while the thread holding it is itself waiting for the main thread to process the write request. To avoid this, a write to stderr is only given 50ms to complete. If that fails to occur, the thread would be informed that the operation has succeeded.

On Windows, paths received by non-wide functions (e.g. CreateFileA) are treated as UTF-8. No codepage to Unicode conversion is performed.

Limitations

There's currently no support for memory mapped files.

Many functions in the std.fs namespace make direct syscalls on Linux even when the module is linked against libc. To redirect these, Zigar uses syscall user dispatch. This feature is available starting from version 5.11 of the Linux kernel and is exclusive to x86.

Support for
_findfirst() on Windows is minimalist. It's only capable of emulating POSIX's readdir().


IO-redirection

⚠️ **GitHub.com Fallback** ⚠️