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.
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.
- Using SQLite in a browser
- [Accessing a remote file through HTTP range requests](<Client-side-SQLite-with-remote-file)
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.
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().