diff options
author | 2022-01-19 02:29:07 -0800 | |
---|---|---|
committer | 2022-01-19 02:29:07 -0800 | |
commit | d3a93d527336af73df838d69ca42ad1b18adebb8 (patch) | |
tree | 726dad460bf4ee2608ffa9557943df11da56f8c3 /src/javascript/jsc/node/syscall.zig | |
parent | ed9637de5056af4572ec5e0a75feee9ca858798e (diff) | |
download | bun-d3a93d527336af73df838d69ca42ad1b18adebb8.tar.gz bun-d3a93d527336af73df838d69ca42ad1b18adebb8.tar.zst bun-d3a93d527336af73df838d69ca42ad1b18adebb8.zip |
`fs.*Sync()`, `bun wiptest`, and More ™ (#106)
* very very wip
* almost ready to fix the errors
* Update identity_context.zig
* Update base.zig
* [bun test] It runs successfully
* Remove unnecessary call
* [Bun.js] Improve JS <> Zig unicode string interop
This fixes longstanding unicode bugs with `console.log` & `fetch`.
I believe @evanwashere reported this first awhile ago
* [Bun.js] Implement `Object.is()` binding and a way to set a timeout for script execution
* Update PLCrashReport.zig
* [Bun.js] Make `console.log` more closely match Node.js and Deno
* [Bun.js] Implement formatting specifier for console.*
* Implement `console.clear()`
* bug fix
* Support console.clear()
* Buffer stderr
* [bun test] Begin implementing Node.js `fs`
* Update darwin_c.zig
* Implement more of `fs`
* `mkdir`, `mkdir` recursive, `mkdtemp`
* `open`, `read` (and pread)
* Move some things into more files
* Implement readdir
* `readFile`, `readLink`, and `realpath`
* `writeFile`, `symlink`, `chown`, `rename`, `stat`, `unlink`, `truncate`
* `lutimes`
* Implement `SystemError` and begin wiring up the `fs` module
* `"fs"` - Most of the arguments / validation
* `fs` - Rest of the arguments / validations
* Begin wiring up the `fs` module
* Fix all the build errors
* support printing typed arrays in console.log
* It...works?
* Support `require("fs")`, `import fs from 'fs';`, `import * as fs from 'fs'`
* Fix a couple bugs
* get rid of the crash reporter for now
* Update fs.exports.js
* [bun.js] slight improvement to startup time
* [bun.js] Improve error message printing
* [Bun.js] Add `Bun.gc()` to run the garbage collector manually and report heap size
* [Bun.js] Add Bun.generateHeapSnapshot to return what JS types are using memory
* [Bun.js] Add `Bun.shrink()` to tell JSC to shrink the VM size
* Improve encoding reader
* [bun.js] Improve callback & microtask performance
* Update node_fs.zig
* Implement `console.assert`
* simple test
* [Bun.js] Prepare for multiple globals/realms to support testing
* Create callbacks-overhead.mjs
* Update http.zig
* [Bun.js] Implement `queueMicrotask`
* Add test for queueMicrotask
* :sleepy:
* [Bun.js] Implement `process.versions`, `process.pid`, `process.ppid`, `process.nextTick`, `process.versions`,
* Implement `process.env.toJSON()`
* [Bun.js] Improve performance of `fs.existsSync`
* :nail_care:
* [Bun.js] Implement `process.chdir(str)` and `process.cwd()`, support up to 4 args in `process.nextTick`
* Make creating Zig::Process lazy
* Split processi nto separte file
* [Bun.js] Node.js Streams - Part 1/?
* [Bun.js] Node.js streams 2/?
* WIP streams
* fix crash
* Reduce allocations in many places
* swap
* Make `bun` start 2ms faster
* Always use an apiLock()
* libBacktrace doesn't really work yet
* Fix crash in the upgrade checker
* Clean up code for importing the runtime when not bundling
* :camera:
* Update linker.zig
* 68!
* backtrace
* no, really backtrace
* Fix
* Linux fixes
* Fixes on Linux
* Update mimalloc
* [bun test] Automatically scan for {.test,_test,.spec,_spec}.{jsx,tsx,js,cts,mts,ts,cjs}
Diffstat (limited to 'src/javascript/jsc/node/syscall.zig')
-rw-r--r-- | src/javascript/jsc/node/syscall.zig | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/javascript/jsc/node/syscall.zig b/src/javascript/jsc/node/syscall.zig new file mode 100644 index 000000000..7dee85736 --- /dev/null +++ b/src/javascript/jsc/node/syscall.zig @@ -0,0 +1,465 @@ +// This file is entirely based on Zig's std.os +// The differences are in error handling +const std = @import("std"); +const os = std.os; +const builtin = @import("builtin"); + +const Syscall = @This(); +const Environment = @import("../../../global.zig").Environment; +const default_allocator = @import("../../../global.zig").default_allocator; +const JSC = @import("../../../jsc.zig"); +const SystemError = JSC.SystemError; +const darwin = os.darwin; +const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; +const fd_t = std.os.fd_t; +const C = @import("../../../global.zig").C; +const linux = os.linux; +const Maybe = JSC.Node.Maybe; + +pub const system = if (Environment.isLinux) linux else darwin; +const libc = std.os.system; + +pub const Tag = enum(u8) { + TODO, + + access, + chmod, + chown, + clonefile, + close, + copy_file_range, + copyfile, + fchmod, + fchown, + fcntl, + fdatasync, + fstat, + fsync, + ftruncate, + futimens, + getdents64, + getdirentries64, + lchmod, + lchown, + link, + lseek, + lstat, + lutimes, + mkdir, + mkdtemp, + open, + pread, + pwrite, + read, + readlink, + rename, + stat, + symlink, + unlink, + utimes, + write, + getcwd, + chdir, + fcopyfile, + + pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null); +}; +const PathString = @import("../../../global.zig").PathString; + +const mode_t = os.mode_t; + +const open_sym = system.open; + +const fstat_sym = if (builtin.os.tag == .linux and builtin.link_libc) + libc.fstat64 +else + libc.fstat; + +const mem = std.mem; + +pub fn getcwd(buf: *[std.fs.MAX_PATH_BYTES]u8) Maybe([]const u8) { + const Result = Maybe([]const u8); + buf[0] = 0; + const rc = std.c.getcwd(buf, std.fs.MAX_PATH_BYTES); + return if (rc != null) + Result{ .result = std.mem.sliceTo(rc.?[0..std.fs.MAX_PATH_BYTES], 0) } + else + Result.errnoSys(0, .getcwd).?; +} + +pub fn fchmod(fd: JSC.Node.FileDescriptor, mode: JSC.Node.Mode) Maybe(void) { + return Maybe(void).errnoSys(C.fchmod(fd, mode), .fchmod) orelse + Maybe(void).success; +} + +pub fn chdir(destination: [:0]const u8) Maybe(void) { + const rc = libc.chdir(destination); + return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; +} + +pub fn stat(path: [:0]const u8) Maybe(os.Stat) { + var stat_ = mem.zeroes(os.Stat); + if (Maybe(os.Stat).errnoSys(libc.stat(path, &stat_), .stat)) |err| return err; + return Maybe(os.Stat){ .result = stat_ }; +} + +pub fn lstat(path: [:0]const u8) Maybe(os.Stat) { + var stat_ = mem.zeroes(os.Stat); + if (Maybe(os.Stat).errnoSys(C.lstat(path, &stat_), .lstat)) |err| return err; + return Maybe(os.Stat){ .result = stat_ }; +} + +pub fn fstat(fd: JSC.Node.FileDescriptor) Maybe(os.Stat) { + var stat_ = mem.zeroes(os.Stat); + if (Maybe(os.Stat).errnoSys(fstat_sym(fd, &stat_), .fstat)) |err| return err; + return Maybe(os.Stat){ .result = stat_ }; +} + +pub fn mkdir(file_path: [:0]const u8, flags: JSC.Node.Mode) Maybe(void) { + if (comptime Environment.isMac) { + return Maybe(void).errnoSysP(darwin.mkdir(file_path, flags), .mkdir, file_path) orelse Maybe(void).success; + } + + if (comptime Environment.isLinux) { + return Maybe(void).errnoSysP(linux.mkdir(file_path, flags), .mkdir, file_path) orelse Maybe(void).success; + } +} + +pub fn getErrno(rc: anytype) std.os.E { + if (comptime Environment.isMac) return std.os.errno(rc); + const Type = @TypeOf(rc); + + return switch (Type) { + comptime_int, usize => std.os.linux.getErrno(@as(usize, rc)), + i32, c_int, isize => std.os.linux.getErrno(@bitCast(usize, @as(isize, rc))), + else => @compileError("Not implemented yet for type " ++ @typeName(Type)), + }; +} + +pub fn open(file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(JSC.Node.FileDescriptor) { + while (true) { + const rc = Syscall.system.open(file_path, flags, perm); + return switch (Syscall.getErrno(rc)) { + .SUCCESS => .{ .result = @intCast(JSC.Node.FileDescriptor, rc) }, + .INTR => continue, + else => |err| { + return Maybe(std.os.fd_t){ + .err = .{ + .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), + .syscall = .open, + }, + }; + }, + }; + } + + unreachable; +} + +// The zig standard library marks BADF as unreachable +// That error is not unreachable for us +pub fn close(fd: std.os.fd_t) ?Syscall.Error { + if (comptime Environment.isMac) { + // This avoids the EINTR problem. + return switch (darwin.getErrno(darwin.@"close$NOCANCEL"(fd))) { + .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close }, + else => null, + }; + } + + if (comptime Environment.isLinux) { + return switch (linux.getErrno(linux.close(fd))) { + .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close }, + else => null, + }; + } + + @compileError("Not implemented yet"); +} + +const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => std.math.maxInt(i32), + else => std.math.maxInt(isize), +}; + +pub fn write(fd: os.fd_t, bytes: []const u8) Maybe(usize) { + const adjusted_len = @minimum(max_count, bytes.len); + + while (true) { + const rc = libc.write(fd, bytes.ptr, adjusted_len); + if (Maybe(usize).errnoSys(rc, .write)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; +} + +const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc) + libc.pread64 +else + libc.pread; + +pub fn pread(fd: os.fd_t, buf: []u8, offset: i64) Maybe(usize) { + const adjusted_len = @minimum(buf.len, max_count); + + const ioffset = @bitCast(i64, offset); // the OS treats this as unsigned + while (true) { + const rc = pread_sym(fd, buf.ptr, adjusted_len, ioffset); + if (Maybe(usize).errnoSys(rc, .pread)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; +} + +const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc) + libc.pwrite64 +else + libc.pwrite; + +pub fn pwrite(fd: os.fd_t, bytes: []const u8, offset: i64) Maybe(usize) { + const adjusted_len = @minimum(bytes.len, max_count); + + const ioffset = @bitCast(i64, offset); // the OS treats this as unsigned + while (true) { + const rc = pwrite_sym(fd, bytes.ptr, adjusted_len, ioffset); + return if (Maybe(usize).errnoSys(rc, .pwrite)) |err| { + switch (err.getErrno()) { + .INTR => continue, + else => return err, + } + } else Maybe(usize){ .result = @intCast(usize, rc) }; + } + + unreachable; +} + +pub fn read(fd: os.fd_t, buf: []u8) Maybe(usize) { + const adjusted_len = @minimum(buf.len, max_count); + while (true) { + const rc = libc.read(fd, buf.ptr, adjusted_len); + if (Maybe(usize).errnoSys(rc, .read)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; +} + +pub fn readlink(in: [:0]const u8, buf: []u8) Maybe(usize) { + while (true) { + const rc = libc.readlink(in, buf.ptr, buf.len); + + if (Maybe(usize).errnoSys(rc, .readlink)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; +} + +pub fn rename(from: [:0]const u8, to: [:0]const u8) Maybe(void) { + while (true) { + if (Maybe(void).errnoSys(libc.rename(from, to), .rename)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn chown(path: [:0]const u8, uid: os.uid_t, gid: os.gid_t) Maybe(void) { + while (true) { + if (Maybe(void).errnoSys(C.chown(path, uid, gid), .chown)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn symlink(from: [:0]const u8, to: [:0]const u8) Maybe(void) { + while (true) { + if (Maybe(void).errnoSys(libc.symlink(from, to), .symlink)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn clonefile(from: [:0]const u8, to: [:0]const u8) Maybe(void) { + if (comptime !Environment.isMac) @compileError("macOS only"); + + while (true) { + if (Maybe(void).errnoSys(C.darwin.clonefile(from, to, 0), .clonefile)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn copyfile(from: [:0]const u8, to: [:0]const u8, flags: c_int) Maybe(void) { + if (comptime !Environment.isMac) @compileError("macOS only"); + + while (true) { + if (Maybe(void).errnoSys(C.darwin.copyfile(from, to, null, flags), .copyfile)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn fcopyfile(fd_in: std.os.fd_t, fd_out: std.os.fd_t, flags: c_int) Maybe(void) { + if (comptime !Environment.isMac) @compileError("macOS only"); + + while (true) { + if (Maybe(void).errnoSys(darwin.fcopyfile(fd_in, fd_out, null, flags), .fcopyfile)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn unlink(from: [:0]const u8) Maybe(void) { + while (true) { + if (Maybe(void).errno(libc.unlink(from), .unlink)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return Maybe(void).success; + } + unreachable; +} + +pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) { + switch (comptime builtin.os.tag) { + .windows => { + const windows = std.os.windows; + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]) catch { + return Maybe([]u8){ .err = .{ .errno = .EBADF } }; + }; + + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; + return .{ .result = out_buffer[0..end_index] }; + }, + .macos, .ios, .watchos, .tvos => { + // On macOS, we can use F.GETPATH fcntl command to query the OS for + // the path to the file descriptor. + @memset(out_buffer, 0, MAX_PATH_BYTES); + if (Maybe([]u8).errnoSys(darwin.fcntl(fd, os.F.GETPATH, out_buffer), .fcntl)) |err| { + return err; + } + const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES; + return .{ .result = out_buffer[0..len] }; + }, + .linux => { + var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; + const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}\x00", .{fd}) catch unreachable; + + return switch (readlink(proc_path, out_buffer)) { + .err => |err| return .{ .err = err }, + .result => |len| return .{ .result = out_buffer[0..len] }, + }; + }, + // .solaris => { + // var procfs_buf: ["/proc/self/path/-2147483648".len:0]u8 = undefined; + // const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable; + + // const target = readlinkZ(proc_path, out_buffer) catch |err| switch (err) { + // error.UnsupportedReparsePointType => unreachable, + // error.NotLink => unreachable, + // else => |e| return e, + // }; + // return target; + // }, + else => @compileError("querying for canonical path of a handle is unsupported on this host"), + } +} + +pub const Error = struct { + const max_errno_value = brk: { + const errno_values = std.enums.values(os.E); + var err = @enumToInt(os.E.SUCCESS); + for (errno_values) |errn| { + err = @maximum(err, @enumToInt(errn)); + } + break :brk err; + }; + pub const Int: type = std.math.IntFittingRange(0, max_errno_value + 5); + + errno: Int, + syscall: Syscall.Tag = @intToEnum(Syscall.Tag, 0), + path: []const u8 = "", + + pub inline fn getErrno(this: Error) os.E { + return @intToEnum(os.E, this.errno); + } + + pub inline fn withPath(this: Error, path: anytype) Error { + return Error{ + .errno = this.errno, + .syscall = this.syscall, + .path = std.mem.span(path), + }; + } + + pub inline fn withSyscall(this: Error, syscall: Syscall) Error { + return Error{ + .errno = this.errno, + .syscall = syscall, + .path = this.path, + }; + } + + pub const todo_errno = std.math.maxInt(Int) - 1; + pub const todo = Error{ .errno = todo_errno }; + + pub fn toSystemError(this: Error) SystemError { + var sys = SystemError{ + .errno = @as(c_int, this.errno) * -1, + .syscall = JSC.ZigString.init(@tagName(this.syscall)), + }; + + // errno label + if (this.errno > 0 and this.errno < C.SystemErrno.max) { + const system_errno = @intToEnum(C.SystemErrno, this.errno); + sys.code = JSC.ZigString.init(@tagName(system_errno)); + if (C.SystemErrno.labels.get(system_errno)) |label| { + sys.message = JSC.ZigString.init(label); + } + } + + if (this.path.len > 0) { + sys.path = JSC.ZigString.init(this.path); + } + + return sys; + } + + pub fn toJS(this: Error, ctx: JSC.C.JSContextRef) JSC.C.JSObjectRef { + return this.toSystemError().toErrorInstance(ctx.ptr()).asObjectRef(); + } + + pub fn toJSC(this: Error, ptr: *JSC.JSGlobalObject) JSC.JSValue { + return this.toSystemError().toErrorInstance(ptr); + } +}; |