From 922d8378e724063063e709223e90e3a577e4566a Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 1 Mar 2022 10:41:38 -0700 Subject: stage2: Add limited WASI support for selfExePath and globalCacheDir This change adds support for locating the Zig executable and the library and global cache directories, based on looking in the fixed "/zig" and "/cache" directories. Since our argv[0] on WASI is just the basename (any absolute/relative path information is deleted by the runtime), there's very limited introspection we can do on WASI, so we rely on these fixed directories. These can be provided on the command-line using `--mapdir`, as follows: ``` wasmtime --mapdir=/cwd::. --mapdir=/cache::"$HOME/.cache/zig" --mapdir=/zig::./zig-out/ ./zig-out/bin/zig.wasm ``` --- lib/std/fs.zig | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/introspect.zig | 13 ++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 052343599e..a2873ef830 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2547,6 +2547,15 @@ pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathEr /// `selfExePath` except allocates the result on the heap. /// Caller owns returned memory. pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { + if (builtin.os.tag == .wasi) { + var args = try std.process.argsWithAllocator(allocator); + defer args.deinit(); + // On WASI, argv[0] is always just the basename of the current executable + const exe_name = args.next() orelse return error.FileNotFound; + + var buf: [MAX_PATH_BYTES]u8 = undefined; + return allocator.dupe(u8, try selfExePathWasi(&buf, exe_name)); + } // Use of MAX_PATH_BYTES here is justified as, at least on one tested Linux // system, readlink will completely fail to return a result larger than // PATH_MAX even if given a sufficiently large buffer. This makes it @@ -2643,10 +2652,45 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; return out_buffer[0..end_index]; }, + .wasi => @compileError("std.fs.selfExePath not supported for WASI. Use std.fs.selfExePathAlloc instead."), else => @compileError("std.fs.selfExePath not supported for this target"), } } +/// WASI-specific implementation of selfExePath +/// +/// On WASI argv0 is always just the executable basename, so this function relies +/// using a fixed executable directory path: "/zig" +/// +/// This path can be configured in wasmtime using `--mapdir=/zig::/path/to/zig/dir/` +fn selfExePathWasi(out_buffer: []u8, exe_name: []const u8) SelfExePathError![]const u8 { + var allocator = std.heap.FixedBufferAllocator.init(out_buffer); + var alloc = allocator.allocator(); + + // Check these paths: + // 1. "/zig/{exe_name}" + // 2. "/zig/bin/{exe_name}" + const base_paths_to_check = &[_][]const u8{ "/zig", "/zig/bin" }; + + for (base_paths_to_check) |base_path| { + const test_path = path.join(alloc, &.{ base_path, path.basename(exe_name) }) catch continue; + + // Make sure it's a file we're pointing to + const file = os.fstatat(os.wasi.AT.FDCWD, test_path, 0) catch continue; + if (file.filetype != .REGULAR_FILE) continue; + + // Path seems to be valid, let's try to turn it into an absolute path + var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; + if (os.realpath(test_path, &real_path_buf)) |real_path| { + if (real_path.len > out_buffer.len) + return error.NameTooLong; + mem.copy(u8, out_buffer, real_path); + return out_buffer[0..real_path.len]; + } else |_| continue; + } + return error.FileNotFound; +} + /// The result is UTF16LE-encoded. pub fn selfExePathW() [:0]const u16 { const image_path_name = &os.windows.peb().ProcessParameters.ImagePathName; diff --git a/src/introspect.zig b/src/introspect.zig index 562d6b04f4..c0de4dc7f5 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const mem = std.mem; +const os = std.os; const fs = std.fs; const Compilation = @import("Compilation.zig"); @@ -80,5 +81,15 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { } } - return fs.getAppDataDir(allocator, appname); + if (builtin.os.tag == .wasi) { + // On WASI, we have no way to get an App data dir, so we try to use a fixed + // Preopen path "/cache" as a last resort + const path = "/cache"; + + const file = os.fstatat(os.wasi.AT.FDCWD, path, 0) catch return error.CacheDirUnavailable; + if (file.filetype != .DIRECTORY) return error.CacheDirUnavailable; + return allocator.dupe(u8, path); + } else { + return fs.getAppDataDir(allocator, appname); + } } -- cgit v1.2.3 From 089651716c3d326f8540f4e415fc88443e42f5f2 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 1 Mar 2022 10:42:07 -0700 Subject: stage2: Bypass file locks in src/Cache.zig for WASI targets --- src/Cache.zig | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Cache.zig b/src/Cache.zig index 993114905e..37cd7a7529 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -762,18 +762,22 @@ pub const Manifest = struct { fn downgradeToSharedLock(self: *Manifest) !void { if (!self.have_exclusive_lock) return; - const manifest_file = self.manifest_file.?; - try manifest_file.downgradeLock(); + if (std.process.can_spawn or !builtin.single_threaded) { // Some targets (WASI) do not support flock + const manifest_file = self.manifest_file.?; + try manifest_file.downgradeLock(); + } self.have_exclusive_lock = false; } fn upgradeToExclusiveLock(self: *Manifest) !void { if (self.have_exclusive_lock) return; - const manifest_file = self.manifest_file.?; - // Here we intentionally have a period where the lock is released, in case there are - // other processes holding a shared lock. - manifest_file.unlock(); - try manifest_file.lock(.Exclusive); + if (std.process.can_spawn or !builtin.single_threaded) { // Some targets (WASI) do not support flock + const manifest_file = self.manifest_file.?; + // Here we intentionally have a period where the lock is released, in case there are + // other processes holding a shared lock. + manifest_file.unlock(); + try manifest_file.lock(.Exclusive); + } self.have_exclusive_lock = true; } -- cgit v1.2.3 From 692ccd01b4ebaa78f6702b15459437228e6a790d Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 1 Mar 2022 10:42:34 -0700 Subject: stage2: Initialize WASI preopens on startup --- src/main.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.zig b/src/main.zig index a647216b05..8696661017 100644 --- a/src/main.zig +++ b/src/main.zig @@ -162,6 +162,16 @@ pub fn main() anyerror!void { return mainArgs(gpa_tracy.allocator(), arena, args); } + // WASI: `--dir` instructs the WASM runtime to "preopen" a directory, making + // it available to the us, the guest program. This is the only way for us to + // access files/dirs on the host filesystem + if (builtin.os.tag == .wasi) { + // This sets our CWD to "/preopens/cwd" + // Dot-prefixed preopens like `--dir=.` are "mounted" at "/preopens/cwd" + // Other preopens like `--dir=lib` are "mounted" at "/" + try std.os.initPreopensWasi(std.heap.page_allocator, "/preopens/cwd"); + } + return mainArgs(gpa, arena, args); } -- cgit v1.2.3 From 3a63fa6b7f56a2f384ebd460e80c00e6bbd2efee Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 4 Mar 2022 10:23:27 -0700 Subject: stage2: Add 'zig.wasm' fallback for binary name --- lib/std/fs.zig | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index a2873ef830..f7a9249f1c 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2663,7 +2663,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { /// using a fixed executable directory path: "/zig" /// /// This path can be configured in wasmtime using `--mapdir=/zig::/path/to/zig/dir/` -fn selfExePathWasi(out_buffer: []u8, exe_name: []const u8) SelfExePathError![]const u8 { +fn selfExePathWasi(out_buffer: []u8, argv0: []const u8) SelfExePathError![]const u8 { var allocator = std.heap.FixedBufferAllocator.init(out_buffer); var alloc = allocator.allocator(); @@ -2671,22 +2671,25 @@ fn selfExePathWasi(out_buffer: []u8, exe_name: []const u8) SelfExePathError![]co // 1. "/zig/{exe_name}" // 2. "/zig/bin/{exe_name}" const base_paths_to_check = &[_][]const u8{ "/zig", "/zig/bin" }; + const exe_names_to_check = &[_][]const u8{ path.basename(argv0), "zig.wasm" }; for (base_paths_to_check) |base_path| { - const test_path = path.join(alloc, &.{ base_path, path.basename(exe_name) }) catch continue; + for (exe_names_to_check) |exe_name| { + const test_path = path.join(alloc, &.{ base_path, exe_name }) catch continue; - // Make sure it's a file we're pointing to - const file = os.fstatat(os.wasi.AT.FDCWD, test_path, 0) catch continue; - if (file.filetype != .REGULAR_FILE) continue; + // Make sure it's a file we're pointing to + const file = os.fstatat(os.wasi.AT.FDCWD, test_path, 0) catch continue; + if (file.filetype != .REGULAR_FILE) continue; - // Path seems to be valid, let's try to turn it into an absolute path - var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; - if (os.realpath(test_path, &real_path_buf)) |real_path| { - if (real_path.len > out_buffer.len) - return error.NameTooLong; - mem.copy(u8, out_buffer, real_path); - return out_buffer[0..real_path.len]; - } else |_| continue; + // Path seems to be valid, let's try to turn it into an absolute path + var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; + if (os.realpath(test_path, &real_path_buf)) |real_path| { + if (real_path.len > out_buffer.len) + return error.NameTooLong; + mem.copy(u8, out_buffer, real_path); + return out_buffer[0..real_path.len]; + } else |_| continue; + } } return error.FileNotFound; } -- cgit v1.2.3