Virtual file system - chung-leong/zigar GitHub Wiki

Zigar's virtual file system is a mechanism that redirects system calls to JavaScript handlers. Its main purpose is allowing you to use C libraries that uses stdio or POSIX functions for input/output. While desktop applications typically save their results to files, server applications generally their results to remote clients over the network. Persisting data temporarily on disk is inefficient and can create security issues.

When WebAssembly code runs in the web browser, it has no access to the file system at all--at least not without the user manually selecting a file first.

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 its functionality available, we import the C file into Zig and write a function that calls main() with the expected arguments:

const c = @cImport({
    @cInclude("./vfs-example-1.c");
});

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

On the JavaScript side, we capture the output by adding a listener for the 'open' event using __zigar.on():

import { __zigar, create } from './vfs-example-1.zig';

const array = [];
__zigar.on('open', ({ path }) => {
  if (path === 'vfs/file1') return array;
});
create('/vfs/file1', 'Ralph Wiggum');
console.log(array);
const blob = new Blob(array);
console.log(await blob.text());

A JavaScript array is ones of the objects that Zigar can use as a file. A virtual file descriptor gets assigned to it. When fprintf or fwrite is used on a FILE containing this descriptor, the bytes are sent as a Uint8Array to a JavaScript handler, which append it to the array.

In the browser, you can create a URL using URL.createObjectURL() pointing to the blob created from the array, allowing the user to "download" the file.

The above example relies C-to-Zig translation. This would often not work for more complicate code. To compile C code as C, use a build.extra.zig to add the C source files:

// build.extra.zig
const std = @import("std");

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

pub fn getCSourceFiles(_: *std.Build, args: anytype) []const []const u8 {
    args.library.addCSourceFile(.{
        .file = .{ .cwd_relative = cfg.module_dir ++ "main.c" },
        .flags = &.{"-Dmain=c_main"},
    });
    return &.{};
}
extern fn c_main(c_int, [*][*c]const u8) c_int;

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

We use a compiler flag to rename main() to c_main(), since it's illegal to call a C main function.

More examples

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().


Events | File interface | Directory interface

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