Files
bun.sh/src/sys.zig
2025-11-18 10:49:21 -08:00

4314 lines
151 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Cross-platform "system call" abstractions. On linux, many of these functions
//! emit direct system calls directly (std.os.linux). Others call `libc` APIs.
//! Windows uses a mix of `libuv`, `kernel32` and `ntdll`. macOS uses `libc`.
//!
//! Sometimes this namespace is referred to as "Syscall", prefer "bun.sys"/"sys"
const This = @This();
//
// TODO: Split and organize this file. It is likely worth moving many functions
// into methods on `bun.FD`, and keeping this namespace to just overall stuff
// like `Error`, `Maybe`, `Tag`, and so on.
const platform_defs = switch (Environment.os) {
.windows => @import("./errno/windows_errno.zig"),
.linux => @import("./errno/linux_errno.zig"),
.mac => @import("./errno/darwin_errno.zig"),
.wasm => {},
};
pub const workaround_symbols = @import("./workaround_missing_symbols.zig").current;
/// Enum of `errno` values
pub const E = platform_defs.E;
/// Namespace of (potentially polyfilled) libuv `errno` values.
/// Polyfilled on posix, mirrors the real libuv definitions on Windows.
pub const UV_E = platform_defs.UV_E;
pub const S = platform_defs.S;
/// TODO: The way we do errors in Bun needs to get cleaned up. This enum is way
/// too complicated; It's duplicated three times, and inside of it it has tons
/// of re-listings of all errno codes. Why is SystemErrno different than `E`? ...etc!
///
/// The problem is because we use libc in some cases and we use zig's std lib in
/// other places and other times we go direct. So we end up with a lot of
/// redundant code.
pub const SystemErrno = platform_defs.SystemErrno;
pub const getErrno = platform_defs.getErrno;
comptime {
_ = &workaround_symbols; // execute comptime logic to export any needed symbols
}
const linux = syscall;
pub const sys_uv = if (Environment.isWindows) @import("./sys_uv.zig") else sys;
pub const F_OK = 0;
pub const X_OK = 1;
pub const W_OK = 2;
pub const R_OK = 4;
const log = bun.Output.scoped(.SYS, .visible);
pub const syslog = log;
pub const syscall = switch (Environment.os) {
.linux => std.os.linux,
// macOS requires using libc
.mac => std.c,
.windows, .wasm => @compileError("not implemented"),
};
fn toPackedO(number: anytype) std.posix.O {
return @bitCast(number);
}
pub const Mode = std.posix.mode_t;
pub const O = switch (Environment.os) {
.mac => struct {
pub const PATH = 0x0000;
pub const RDONLY = 0x0000;
pub const WRONLY = 0x0001;
pub const RDWR = 0x0002;
pub const NONBLOCK = 0x0004;
pub const APPEND = 0x0008;
pub const CREAT = 0x0200;
pub const TRUNC = 0x0400;
pub const EXCL = 0x0800;
pub const SHLOCK = 0x0010;
pub const EXLOCK = 0x0020;
pub const NOFOLLOW = 0x0100;
pub const SYMLINK = 0x200000;
pub const EVTONLY = 0x8000;
pub const CLOEXEC = 0x01000000;
pub const ACCMODE = 3;
pub const ALERT = 536870912;
pub const ASYNC = 64;
pub const DIRECTORY = 0x00100000;
pub const DP_GETRAWENCRYPTED = 1;
pub const DP_GETRAWUNENCRYPTED = 2;
pub const DSYNC = 4194304;
pub const FSYNC = SYNC;
pub const NOCTTY = 131072;
pub const POPUP = 2147483648;
pub const SYNC = 128;
pub const toPacked = toPackedO;
},
.linux, .wasm => switch (Environment.isX86) {
true => struct {
pub const RDONLY = 0x0000;
pub const WRONLY = 0x0001;
pub const RDWR = 0x0002;
pub const CREAT = 0o100;
pub const EXCL = 0o200;
pub const NOCTTY = 0o400;
pub const TRUNC = 0o1000;
pub const APPEND = 0o2000;
pub const NONBLOCK = 0o4000;
pub const DSYNC = 0o10000;
pub const SYNC = 0o4010000;
pub const RSYNC = 0o4010000;
pub const DIRECTORY = 0o200000;
pub const NOFOLLOW = 0o400000;
pub const CLOEXEC = 0o2000000;
pub const ASYNC = 0o20000;
pub const DIRECT = 0o40000;
pub const LARGEFILE = 0;
pub const NOATIME = 0o1000000;
pub const PATH = 0o10000000;
pub const TMPFILE = 0o20200000;
pub const NDELAY = NONBLOCK;
pub const toPacked = toPackedO;
},
false => struct {
pub const RDONLY = 0x0000;
pub const WRONLY = 0x0001;
pub const RDWR = 0x0002;
pub const CREAT = 0o100;
pub const EXCL = 0o200;
pub const NOCTTY = 0o400;
pub const TRUNC = 0o1000;
pub const APPEND = 0o2000;
pub const NONBLOCK = 0o4000;
pub const DSYNC = 0o10000;
pub const SYNC = 0o4010000;
pub const RSYNC = 0o4010000;
pub const DIRECTORY = 0o40000;
pub const NOFOLLOW = 0o100000;
pub const CLOEXEC = 0o2000000;
pub const ASYNC = 0o20000;
pub const DIRECT = 0o200000;
pub const LARGEFILE = 0o400000;
pub const NOATIME = 0o1000000;
pub const PATH = 0o10000000;
pub const TMPFILE = 0o20040000;
pub const NDELAY = NONBLOCK;
pub const SYMLINK = c.O_SYMLINK;
pub const toPacked = toPackedO;
},
},
.windows => struct {
pub const RDONLY = 0o0;
pub const WRONLY = 0o1;
pub const RDWR = 0o2;
pub const CREAT = 0o100;
pub const EXCL = 0o200;
pub const NOCTTY = 0;
pub const TRUNC = 0o1000;
pub const APPEND = 0o2000;
pub const NONBLOCK = 0o4000;
pub const DSYNC = 0o10000;
pub const SYNC = 0o4010000;
pub const RSYNC = 0o4010000;
pub const DIRECTORY = 0o200000;
pub const NOFOLLOW = 0o400000;
pub const CLOEXEC = 0o2000000;
pub const ASYNC = 0o20000;
pub const DIRECT = 0o40000;
pub const LARGEFILE = 0;
pub const NOATIME = 0o1000000;
pub const PATH = 0o10000000;
pub const TMPFILE = 0o20200000;
pub const NDELAY = NONBLOCK;
pub const toPacked = toPackedO;
},
};
pub const Tag = enum(u8) {
TODO,
dup,
access,
connect,
chmod,
chown,
clonefile,
clonefileat,
close,
copy_file_range,
copyfile,
fchmod,
fchmodat,
fchown,
fcntl,
fdatasync,
fstat,
fstatat,
fsync,
ftruncate,
futimens,
getdents64,
getdirentries64,
lchmod,
lchown,
link,
lseek,
lstat,
lutime,
mkdir,
mkdtemp,
fnctl,
memfd_create,
mmap,
munmap,
open,
pread,
pwrite,
read,
readlink,
rename,
stat,
statfs,
symlink,
symlinkat,
unlink,
utime,
utimensat,
write,
getcwd,
getenv,
chdir,
fcopyfile,
recv,
send,
sendfile,
sendmmsg,
splice,
rmdir,
truncate,
realpath,
futime,
pidfd_open,
poll,
ppoll,
watch,
scandir,
kevent,
kqueue,
epoll_ctl,
kill,
waitpid,
posix_spawn,
getaddrinfo,
writev,
pwritev,
readv,
preadv,
ioctl_ficlone,
accept,
bind2,
connect2,
listen,
pipe,
try_write,
socketpair,
setsockopt,
statx,
rm,
uv_spawn,
uv_pipe,
uv_tty_set_mode,
uv_open_osfhandle,
uv_os_homedir,
// Below this line are Windows API calls only.
WriteFile,
NtQueryDirectoryFile,
NtSetInformationFile,
GetFinalPathNameByHandle,
CloseHandle,
SetFilePointerEx,
SetEndOfFile,
pub fn isWindows(this: Tag) bool {
return @intFromEnum(this) > @intFromEnum(Tag.WriteFile);
}
pub var strings = std.EnumMap(Tag, jsc.C.JSStringRef).initFull(null);
};
pub const Error = @import("./sys/Error.zig");
pub const PosixStat = @import("./sys/PosixStat.zig").PosixStat;
pub fn Maybe(comptime ReturnTypeT: type) type {
return bun.api.node.Maybe(ReturnTypeT, Error);
}
pub fn getcwd(buf: *bun.PathBuffer) Maybe([]const u8) {
const Result = Maybe([]const u8);
return switch (getcwdZ(buf)) {
.err => |err| Result{ .err = err },
.result => |cwd| Result{ .result = cwd },
};
}
pub fn getcwdZ(buf: *bun.PathBuffer) Maybe([:0]const u8) {
const Result = Maybe([:0]const u8);
buf[0] = 0;
if (comptime Environment.isWindows) {
var wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
const len: windows.DWORD = kernel32.GetCurrentDirectoryW(wbuf.len, wbuf);
if (Result.errnoSysP(len, .getcwd, buf)) |err| return err;
return Result{ .result = bun.strings.fromWPath(buf, wbuf[0..len]) };
}
const rc: ?[*:0]u8 = @ptrCast(std.c.getcwd(buf, bun.MAX_PATH_BYTES));
return if (rc != null)
Result{ .result = rc.?[0..std.mem.len(rc.?) :0] }
else
Result.errnoSysP(@as(c_int, 0), .getcwd, buf).?;
}
const syscall_or_c = if (Environment.isLinux) syscall else bun.c;
pub fn fchown(fd: bun.FileDescriptor, uid: jsc.Node.uid_t, gid: jsc.Node.gid_t) Maybe(void) {
if (comptime Environment.isWindows) {
return sys_uv.fchown(fd, uid, gid);
}
while (true) {
const rc = syscall_or_c.fchown(fd.cast(), uid, gid);
if (Maybe(void).errnoSysFd(rc, .fchown, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
unreachable;
}
pub fn fchmod(fd: bun.FileDescriptor, mode: bun.Mode) Maybe(void) {
if (comptime Environment.isWindows) {
return sys_uv.fchmod(fd, mode);
}
while (true) {
const rc = syscall_or_c.fchmod(fd.cast(), mode);
if (Maybe(void).errnoSysFd(rc, .fchmod, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
unreachable;
}
pub fn fchmodat(fd: bun.FileDescriptor, path: [:0]const u8, mode: bun.Mode, flags: if (Environment.isLinux) u32 else i32) Maybe(void) {
if (comptime Environment.isWindows) @compileError("Use fchmod instead");
while (true) {
const rc = syscall_or_c.fchmodat(fd.cast(), path.ptr, mode, flags);
if (Maybe(void).errnoSysFd(rc, .fchmodat, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
unreachable;
}
pub fn chmod(path: [:0]const u8, mode: bun.Mode) Maybe(void) {
if (comptime Environment.isWindows) {
return sys_uv.chmod(path, mode);
}
while (true) {
const rc = syscall_or_c.chmod(path.ptr, mode);
if (Maybe(void).errnoSysP(rc, .chmod, path)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
unreachable;
}
pub fn chdirOSPath(
path: [:0]const u8,
destination: if (Environment.isPosix) [:0]const u8 else []const u8,
) Maybe(void) {
if (comptime Environment.isPosix) {
const rc = syscall.chdir(destination);
return Maybe(void).errnoSysPD(rc, .chdir, path, destination) orelse .success;
}
if (comptime Environment.isWindows) {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
if (c.SetCurrentDirectoryW(bun.strings.toWDirPath(wbuf, destination)) == windows.FALSE) {
log("SetCurrentDirectory({s}) = {d}", .{ destination, kernel32.GetLastError() });
return Maybe(void).errnoSysPD(0, .chdir, path, destination) orelse .success;
}
log("SetCurrentDirectory({s}) = {d}", .{ destination, 0 });
return .success;
}
@compileError("Not implemented yet");
}
pub fn chdir(path: anytype, destination: anytype) Maybe(void) {
const Type = @TypeOf(destination);
if (comptime Environment.isPosix) {
if (comptime Type == []u8 or Type == []const u8) {
return chdirOSPath(
&(std.posix.toPosixPath(path) catch return .{ .err = .{
.errno = @intFromEnum(SystemErrno.EINVAL),
.syscall = .chdir,
} }),
&(std.posix.toPosixPath(destination) catch return .{ .err = .{
.errno = @intFromEnum(SystemErrno.EINVAL),
.syscall = .chdir,
} }),
);
}
return chdirOSPath(path, destination);
}
if (comptime Environment.isWindows) {
if (comptime Type == *[*:0]u16) {
if (kernel32.SetCurrentDirectory(destination) != 0) {
return Maybe(void).errnoSysPD(0, .chdir, path, destination) orelse .success;
}
return .success;
}
if (comptime Type == bun.OSPathSliceZ or Type == [:0]u16) {
return chdirOSPath(path, @as(bun.OSPathSliceZ, destination));
}
return chdirOSPath(path, destination);
}
return Maybe(void).todo();
}
pub fn sendfile(src: bun.FileDescriptor, dest: bun.FileDescriptor, len: usize) Maybe(usize) {
while (true) {
const rc = std.os.linux.sendfile(
dest.cast(),
src.cast(),
null,
// we set a maximum to avoid EINVAL
@min(len, std.math.maxInt(i32) - 1),
);
if (Maybe(usize).errnoSysFd(rc, .sendfile, src)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .{ .result = rc };
}
}
pub fn stat(path: [:0]const u8) Maybe(bun.Stat) {
if (Environment.isWindows) {
return sys_uv.stat(path);
} else {
var stat_ = mem.zeroes(bun.Stat);
const rc = if (Environment.isLinux)
// aarch64 linux doesn't implement a "stat" syscall. It's all fstatat.
linux.fstatat(std.posix.AT.FDCWD, path, &stat_, 0)
else
workaround_symbols.stat(path, &stat_);
if (comptime Environment.allow_assert)
log("stat({s}) = {d}", .{ bun.asByteSlice(path), rc });
if (Maybe(bun.Stat).errnoSysP(rc, .stat, path)) |err| return err;
return Maybe(bun.Stat){ .result = stat_ };
}
}
pub fn statfs(path: [:0]const u8) Maybe(bun.StatFS) {
if (Environment.isWindows) {
return .{ .err = Error.fromCode(.ENOSYS, .statfs) };
} else {
var statfs_ = mem.zeroes(bun.StatFS);
const rc = if (Environment.isLinux)
c.statfs(path, &statfs_)
else if (Environment.isMac)
c.statfs(path, &statfs_)
else
@compileError("Unsupported platform");
if (comptime Environment.allow_assert)
log("statfs({s}) = {d}", .{ bun.asByteSlice(path), rc });
if (Maybe(bun.StatFS).errnoSysP(rc, .statfs, path)) |err| return err;
return Maybe(bun.StatFS){ .result = statfs_ };
}
}
pub fn lstat(path: [:0]const u8) Maybe(bun.Stat) {
if (Environment.isWindows) {
return sys_uv.lstat(path);
} else {
var stat_buf = mem.zeroes(bun.Stat);
if (Maybe(bun.Stat).errnoSysP(workaround_symbols.lstat(path, &stat_buf), .lstat, path)) |err| return err;
return Maybe(bun.Stat){ .result = stat_buf };
}
}
pub fn fstat(fd: bun.FileDescriptor) Maybe(bun.Stat) {
if (Environment.isWindows) {
// TODO: this is a bad usage of makeLibUVOwned
const uvfd = fd.makeLibUVOwned() catch
return .{ .err = Error.fromCode(.MFILE, .uv_open_osfhandle) };
return sys_uv.fstat(uvfd);
}
var stat_ = mem.zeroes(bun.Stat);
const rc = workaround_symbols.fstat(fd.cast(), &stat_);
if (comptime Environment.allow_assert)
log("fstat({f}) = {d}", .{ fd, rc });
if (Maybe(bun.Stat).errnoSysFd(rc, .fstat, fd)) |err| return err;
return Maybe(bun.Stat){ .result = stat_ };
}
pub const StatxField = enum(comptime_int) {
type = linux.STATX_TYPE,
mode = linux.STATX_MODE,
nlink = linux.STATX_NLINK,
uid = linux.STATX_UID,
gid = linux.STATX_GID,
atime = linux.STATX_ATIME,
mtime = linux.STATX_MTIME,
ctime = linux.STATX_CTIME,
btime = linux.STATX_BTIME,
ino = linux.STATX_INO,
size = linux.STATX_SIZE,
blocks = linux.STATX_BLOCKS,
};
// Linux Kernel v4.11
pub var supports_statx_on_linux = std.atomic.Value(bool).init(true);
/// Linux kernel makedev encoding for device numbers
/// From glibc sys/sysmacros.h and Linux kernel <linux/kdev_t.h>
/// dev_t layout (64 bits):
/// Bits 31-20: major high (12 bits)
/// Bits 19-8: minor high (12 bits)
/// Bits 7-0: minor low (8 bits)
inline fn makedev(major: u32, minor: u32) u64 {
const maj: u64 = major & 0xFFF;
const min: u64 = minor & 0xFFFFF;
return (maj << 8) | (min & 0xFF) | ((min & 0xFFF00) << 12);
}
fn statxImpl(fd: bun.FileDescriptor, path: ?[*:0]const u8, flags: u32, mask: u32) Maybe(PosixStat) {
if (comptime !Environment.isLinux) {
@compileError("statx is only supported on Linux");
}
var buf: linux.Statx = undefined;
while (true) {
const rc = linux.statx(@intCast(fd.cast()), if (path) |p| p else "", flags, mask, &buf);
if (Maybe(PosixStat).errnoSys(rc, .statx)) |err| {
// Retry on EINTR
if (err.getErrno() == .INTR) continue;
// Handle unsupported statx by setting flag and falling back
if (err.getErrno() == .NOSYS or err.getErrno() == .OPNOTSUPP) {
supports_statx_on_linux.store(false, .monotonic);
if (path) |p| {
const path_span = bun.span(p);
const fallback = if (flags & linux.AT.SYMLINK_NOFOLLOW != 0) lstat(path_span) else stat(path_span);
return switch (fallback) {
.result => |s| .{ .result = PosixStat.init(&s) },
.err => |e| .{ .err = e },
};
} else {
return switch (fstat(fd)) {
.result => |s| .{ .result = PosixStat.init(&s) },
.err => |e| .{ .err = e },
};
}
}
return err;
}
// Convert statx buffer to PosixStat structure
const stat_ = PosixStat{
.dev = makedev(buf.dev_major, buf.dev_minor),
.ino = buf.ino,
.mode = buf.mode,
.nlink = buf.nlink,
.uid = buf.uid,
.gid = buf.gid,
.rdev = makedev(buf.rdev_major, buf.rdev_minor),
.size = @bitCast(buf.size),
.blksize = @intCast(buf.blksize),
.blocks = @bitCast(buf.blocks),
.atim = .{ .sec = buf.atime.sec, .nsec = buf.atime.nsec },
.mtim = .{ .sec = buf.mtime.sec, .nsec = buf.mtime.nsec },
.ctim = .{ .sec = buf.ctime.sec, .nsec = buf.ctime.nsec },
.birthtim = if (buf.mask & linux.STATX_BTIME != 0)
.{ .sec = buf.btime.sec, .nsec = buf.btime.nsec }
else
.{ .sec = 0, .nsec = 0 },
};
return .{ .result = stat_ };
}
}
pub fn fstatx(fd: bun.FileDescriptor, comptime fields: []const StatxField) Maybe(PosixStat) {
const mask: u32 = comptime brk: {
var i: u32 = 0;
for (fields) |field| {
i |= @intFromEnum(field);
}
break :brk i;
};
return statxImpl(fd, null, linux.AT.EMPTY_PATH, mask);
}
pub fn statx(path: [*:0]const u8, comptime fields: []const StatxField) Maybe(PosixStat) {
const mask: u32 = comptime brk: {
var i: u32 = 0;
for (fields) |field| {
i |= @intFromEnum(field);
}
break :brk i;
};
return statxImpl(bun.FD.fromNative(std.posix.AT.FDCWD), path, 0, mask);
}
pub fn lstatx(path: [*:0]const u8, comptime fields: []const StatxField) Maybe(PosixStat) {
const mask: u32 = comptime brk: {
var i: u32 = 0;
for (fields) |field| {
i |= @intFromEnum(field);
}
break :brk i;
};
return statxImpl(bun.FD.fromNative(std.posix.AT.FDCWD), path, linux.AT.SYMLINK_NOFOLLOW, mask);
}
pub fn lutimes(path: [:0]const u8, atime: jsc.Node.TimeLike, mtime: jsc.Node.TimeLike) Maybe(void) {
if (comptime Environment.isWindows) {
return sys_uv.lutimes(path, atime, mtime);
}
return utimensWithFlags(path, atime, mtime, std.posix.AT.SYMLINK_NOFOLLOW);
}
pub fn mkdiratA(dir_fd: bun.FileDescriptor, file_path: []const u8) Maybe(void) {
const buf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(buf);
return mkdiratW(dir_fd, bun.strings.toWPathNormalized(buf, file_path));
}
pub fn mkdiratZ(dir_fd: bun.FileDescriptor, file_path: [*:0]const u8, mode: mode_t) Maybe(void) {
return switch (Environment.os) {
.mac => Maybe(void).errnoSysP(syscall.mkdirat(@intCast(dir_fd.cast()), file_path, mode), .mkdir, file_path) orelse .success,
.linux => Maybe(void).errnoSysP(linux.mkdirat(@intCast(dir_fd.cast()), file_path, mode), .mkdir, file_path) orelse .success,
.windows, .wasm => @compileError("mkdir is not implemented on this platform"),
};
}
fn mkdiratPosix(dir_fd: bun.FileDescriptor, file_path: []const u8, mode: mode_t) Maybe(void) {
return mkdiratZ(
dir_fd,
&(std.posix.toPosixPath(file_path) catch return .{ .err = Error.fromCode(.NAMETOOLONG, .mkdir) }),
mode,
);
}
pub const mkdirat = if (Environment.isWindows)
mkdiratW
else
mkdiratPosix;
pub fn mkdiratW(dir_fd: bun.FileDescriptor, file_path: [:0]const u16, _: i32) Maybe(void) {
const dir_to_make = openDirAtWindowsNtPath(dir_fd, file_path, .{ .iterable = false, .can_rename_or_delete = true, .op = .only_create });
if (dir_to_make == .err) {
return .{ .err = dir_to_make.err };
}
dir_to_make.result.close();
return .success;
}
pub fn fstatat(fd: bun.FileDescriptor, path: [:0]const u8) Maybe(bun.Stat) {
if (Environment.isWindows) {
return switch (openatWindowsA(fd, path, 0, 0)) {
.result => |file| {
defer file.close();
return fstat(file);
},
.err => |err| Maybe(bun.Stat){ .err = err },
};
}
var stat_buf = mem.zeroes(bun.Stat);
const fd_valid = if (fd == bun.invalid_fd) std.posix.AT.FDCWD else fd.native();
if (Maybe(bun.Stat).errnoSysFP(syscall.fstatat(fd_valid, path, &stat_buf, 0), .fstatat, fd, path)) |err| {
log("fstatat({f}, {s}) = {s}", .{ fd, path, @tagName(err.getErrno()) });
return err;
}
log("fstatat({f}, {s}) = 0", .{ fd, path });
return Maybe(bun.Stat){ .result = stat_buf };
}
pub fn mkdir(file_path: [:0]const u8, flags: mode_t) Maybe(void) {
return switch (Environment.os) {
.mac => Maybe(void).errnoSysP(syscall.mkdir(file_path, flags), .mkdir, file_path) orelse .success,
.linux => Maybe(void).errnoSysP(syscall.mkdir(file_path, flags), .mkdir, file_path) orelse .success,
.windows => {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
return Maybe(void).errnoSysP(
bun.windows.CreateDirectoryW(bun.strings.toKernel32Path(wbuf, file_path).ptr, null),
.mkdir,
file_path,
) orelse .success;
},
.wasm => @compileError("mkdir is not implemented on this platform"),
};
}
pub fn mkdirA(file_path: []const u8, flags: mode_t) Maybe(void) {
if (comptime Environment.isMac) {
return Maybe(void).errnoSysP(syscall.mkdir(&(std.posix.toPosixPath(file_path) catch return Maybe(void){
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .open,
},
}), flags), .mkdir, file_path) orelse .success;
}
if (comptime Environment.isLinux) {
return Maybe(void).errnoSysP(linux.mkdir(&(std.posix.toPosixPath(file_path) catch return Maybe(void){
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .open,
},
}), flags), .mkdir, file_path) orelse .success;
}
if (comptime Environment.isWindows) {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
const wpath = bun.strings.toKernel32Path(wbuf, file_path);
return Maybe(void).errnoSysP(
kernel32.CreateDirectoryW(wpath.ptr, null),
.mkdir,
file_path,
) orelse .success;
}
}
pub fn mkdirOSPath(file_path: bun.OSPathSliceZ, flags: mode_t) Maybe(void) {
return switch (Environment.os) {
else => mkdir(file_path, flags),
.windows => {
const rc = c.CreateDirectoryW(file_path, null);
if (Maybe(void).errnoSys(
rc,
.mkdir,
)) |err| {
log("CreateDirectoryW({f}) = {s}", .{ bun.fmt.fmtOSPath(file_path, .{}), err.err.name() });
return err;
}
log("CreateDirectoryW({f}) = 0", .{bun.fmt.fmtOSPath(file_path, .{})});
return .success;
},
};
}
const fnctl_int = if (Environment.isLinux) usize else c_int;
pub fn fcntl(fd: bun.FileDescriptor, cmd: i32, arg: anytype) Maybe(fnctl_int) {
while (true) {
const result = switch (@TypeOf(arg)) {
i32, comptime_int, c_int => fcntl_symbol(fd.native(), cmd, @as(c_int, arg)),
i64 => fcntl_symbol(fd.cast(), cmd, @as(c_long, @bitCast(arg))),
*const anyopaque, *anyopaque, usize => fcntl_symbol(fd.cast(), cmd, arg),
else => @compileError("Unsupported argument type for fcntl"),
};
if (Maybe(fnctl_int).errnoSysFd(result, .fcntl, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .{ .result = @intCast(result) };
}
unreachable;
}
/// Normalizes for ntdll.dll APIs. Replaces long-path prefixes with nt object
/// prefixes, which may not function properly in kernel32 APIs.
// TODO: Rename to normalizePathWindowsForNtdll
pub fn normalizePathWindows(
comptime T: type,
dir_fd: bun.FileDescriptor,
path_: []const T,
buf: *bun.WPathBuffer,
comptime opts: struct { add_nt_prefix: bool = true },
) Maybe([:0]const u16) {
if (comptime T != u8 and T != u16) {
@compileError("normalizePathWindows only supports u8 and u16 character types");
}
const wbuf = if (T != u16) bun.w_path_buffer_pool.get();
defer if (T != u16) bun.w_path_buffer_pool.put(wbuf);
var path = if (T == u16) path_ else bun.strings.convertUTF8toUTF16InBuffer(wbuf, path_);
if (std.fs.path.isAbsoluteWindowsWTF16(path)) {
if (path_.len >= 4) {
if ((bun.strings.eqlComptimeT(T, path_[path_.len - "\\nul".len ..], "\\nul") or
bun.strings.eqlComptimeT(T, path_[path_.len - "\\NUL".len ..], "\\NUL")))
{
@memcpy(buf[0..bun.strings.w("\\??\\NUL").len], bun.strings.w("\\??\\NUL"));
buf[bun.strings.w("\\??\\NUL").len] = 0;
return .{ .result = buf[0..bun.strings.w("\\??\\NUL").len :0] };
}
if ((path[1] == '/' or path[1] == '\\') and
(path[3] == '/' or path[3] == '\\'))
{
// Preserve the device path, instead of resolving '.' as a relative
// path. This prevents simplifying the path '\\.\pipe' into '\pipe'
if (path[2] == '.') {
buf[0..4].* = .{ '\\', '\\', '.', '\\' };
const rest = path[4..];
@memcpy(buf[4..][0..rest.len], rest);
buf[path.len] = 0;
return .{ .result = buf[0..path.len :0] };
}
// For long paths and nt object paths, conver the prefix into an nt object, then resolve.
// TODO: NT object paths technically mean they are already resolved. Will that break?
if (path[2] == '?' and (path[1] == '?' or path[1] == '/' or path[1] == '\\')) {
path = path[4..];
}
}
}
const norm = bun.path.normalizeStringGenericTZ(u16, path, buf, .{ .add_nt_prefix = opts.add_nt_prefix, .zero_terminate = true });
return .{ .result = norm };
}
if (bun.strings.indexOfAnyT(T, path_, &.{ '\\', '/', '.' }) == null) {
if (buf.len < path.len) {
return .{
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .open,
},
};
}
// Skip the system call to get the final path name if it doesn't have any of the above characters.
@memcpy(buf[0..path.len], path);
buf[path.len] = 0;
return .{
.result = buf[0..path.len :0],
};
}
const base_fd = if (dir_fd == bun.invalid_fd)
std.fs.cwd().fd
else
dir_fd.cast();
const base_path = bun.windows.GetFinalPathNameByHandle(base_fd, w.GetFinalPathNameByHandleFormat{}, buf) catch {
return .{ .err = .{
.errno = @intFromEnum(E.BADFD),
.syscall = .open,
} };
};
if (path.len >= 2 and bun.path.isDriveLetterT(u16, path[0]) and path[1] == ':') {
path = path[2..];
}
const buf1 = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(buf1);
@memcpy(buf1[0..base_path.len], base_path);
buf1[base_path.len] = '\\';
@memcpy(buf1[base_path.len + 1 .. base_path.len + 1 + path.len], path);
const norm = bun.path.normalizeStringGenericTZ(u16, buf1[0 .. base_path.len + 1 + path.len], buf, .{ .add_nt_prefix = true, .zero_terminate = true });
return .{
.result = norm,
};
}
fn openDirAtWindowsNtPath(
dirFd: bun.FileDescriptor,
path: [:0]const u16,
options: WindowsOpenDirOptions,
) Maybe(bun.FileDescriptor) {
const iterable = options.iterable;
const no_follow = options.no_follow;
const can_rename_or_delete = options.can_rename_or_delete;
const read_only = options.read_only;
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
w.SYNCHRONIZE | w.FILE_TRAVERSE;
const iterable_flag: u32 = if (iterable) w.FILE_LIST_DIRECTORY else 0;
const rename_flag: u32 = if (can_rename_or_delete) w.DELETE else 0;
const read_only_flag: u32 = if (read_only) 0 else w.FILE_ADD_FILE | w.FILE_ADD_SUBDIRECTORY;
const flags: u32 = iterable_flag | base_flags | rename_flag | read_only_flag;
const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
// NtCreateFile seems to not function on device paths.
// Since it is absolute, it can just use CreateFileW
if (bun.strings.hasPrefixComptimeUTF16(path, "\\\\.\\"))
return openWindowsDevicePath(
path,
flags,
if (options.op != .only_open) w.FILE_OPEN_IF else w.FILE_OPEN,
w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
);
const path_len_bytes: u16 = @truncate(path.len * 2);
var nt_name = w.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(path.ptr),
};
var attr = w.OBJECT_ATTRIBUTES{
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(path))
null
else if (dirFd == bun.invalid_fd)
std.fs.cwd().fd
else
dirFd.cast(),
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var fd: w.HANDLE = w.INVALID_HANDLE_VALUE;
var io: w.IO_STATUS_BLOCK = undefined;
const rc = w.ntdll.NtCreateFile(
&fd,
flags,
&attr,
&io,
null,
0,
FILE_SHARE,
switch (options.op) {
.only_open => w.FILE_OPEN,
.only_create => w.FILE_CREATE,
.open_or_create => w.FILE_OPEN_IF,
},
w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
null,
0,
);
if (comptime Environment.allow_assert) {
if (rc == .INVALID_PARAMETER) {
// Double check what flags you are passing to this
//
// - access_mask probably needs w.SYNCHRONIZE,
// - options probably needs w.FILE_SYNCHRONOUS_IO_NONALERT
// - disposition probably needs w.FILE_OPEN
bun.Output.debugWarn("NtCreateFile({f}, {f}) = {s} (dir) = {d}\nYou are calling this function with the wrong flags!!!", .{ dirFd, bun.fmt.utf16(path), @tagName(rc), @intFromPtr(fd) });
} else if (rc == .OBJECT_PATH_SYNTAX_BAD or rc == .OBJECT_NAME_INVALID) {
bun.Output.debugWarn("NtCreateFile({f}, {f}) = {s} (dir) = {d}\nYou are calling this function without normalizing the path correctly!!!", .{ dirFd, bun.fmt.utf16(path), @tagName(rc), @intFromPtr(fd) });
} else {
log("NtCreateFile({f}, {f}) = {s} (dir) = {d}", .{ dirFd, bun.fmt.utf16(path), @tagName(rc), @intFromPtr(fd) });
}
}
switch (windows.Win32Error.fromNTStatus(rc)) {
.SUCCESS => {
return .{ .result = .fromNative(fd) };
},
else => |code| {
if (code.toSystemErrno()) |sys_err| {
return .{
.err = .{
.errno = @intFromEnum(sys_err),
.syscall = .open,
},
};
}
return .{
.err = .{
.errno = @intFromEnum(E.UNKNOWN),
.syscall = .open,
},
};
},
}
}
fn openWindowsDevicePath(
path: [:0]const u16,
dwDesiredAccess: u32,
dwCreationDisposition: u32,
dwFlagsAndAttributes: u32,
) Maybe(bun.FileDescriptor) {
const rc = std.os.windows.kernel32.CreateFileW(
path,
dwDesiredAccess,
FILE_SHARE,
null,
dwCreationDisposition,
dwFlagsAndAttributes,
null,
);
if (rc == w.INVALID_HANDLE_VALUE) {
return .{ .err = .{
.errno = if (windows.Win32Error.get().toSystemErrno()) |e|
@intFromEnum(e)
else
@intFromEnum(E.UNKNOWN),
.syscall = .open,
} };
}
return .{ .result = .fromNative(rc) };
}
pub const WindowsOpenDirOptions = struct {
iterable: bool = false,
no_follow: bool = false,
can_rename_or_delete: bool = false,
op: Op = .only_open,
read_only: bool = false,
const Op = enum {
only_open,
only_create,
open_or_create,
};
};
fn openDirAtWindowsT(
comptime T: type,
dirFd: bun.FileDescriptor,
path: []const T,
options: WindowsOpenDirOptions,
) Maybe(bun.FileDescriptor) {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
const norm = switch (normalizePathWindows(T, dirFd, path, wbuf, .{})) {
.err => |err| return .{ .err = err },
.result => |norm| norm,
};
if (comptime T == u8) {
log("openDirAtWindows({s}) = {f}", .{ path, bun.fmt.utf16(norm) });
} else {
log("openDirAtWindowsT({f}) = {f}", .{ bun.fmt.utf16(path), bun.fmt.utf16(norm) });
}
return openDirAtWindowsNtPath(dirFd, norm, options);
}
pub fn openDirAtWindows(
dirFd: bun.FileDescriptor,
path: []const u16,
options: WindowsOpenDirOptions,
) Maybe(bun.FileDescriptor) {
return openDirAtWindowsT(u16, dirFd, path, options);
}
pub noinline fn openDirAtWindowsA(
dirFd: bun.FileDescriptor,
path: []const u8,
options: WindowsOpenDirOptions,
) Maybe(bun.FileDescriptor) {
return openDirAtWindowsT(u8, dirFd, path, options);
}
const NtCreateFileOptions = struct {
access_mask: w.ULONG,
disposition: w.ULONG,
options: w.ULONG,
attributes: w.ULONG = w.FILE_ATTRIBUTE_NORMAL,
sharing_mode: w.ULONG = FILE_SHARE,
};
/// For this function to open an absolute path, it must start with "\??\". Otherwise
/// you need a reference file descriptor the "invalid_fd" file descriptor is used
/// to signify that the current working directory should be used.
///
/// When using this function I highly recommend reading this first:
/// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile
///
/// It is very very very easy to mess up flags here. Please review existing
/// examples to this call and the above function that maps unix flags to
/// the windows ones.
///
/// It is very easy to waste HOURS on the subtle semantics of this function.
///
/// In the zig standard library, messing up the input to their equivalent
/// will trigger `unreachable`. Here there will be a debug log with the path.
pub fn openFileAtWindowsNtPath(
dir: bun.FileDescriptor,
path: []const u16,
options: NtCreateFileOptions,
) Maybe(bun.FileDescriptor) {
// Another problem re: normalization is that you can use relative paths, but no leading '.\' or './''
// this path is probably already backslash normalized so we're only going to check for '.\'
// const path = if (bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, ".\\")) path_maybe_leading_dot[2..] else path_maybe_leading_dot;
// bun.assert(!bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, "./"));
var result: windows.HANDLE = undefined;
const path_len_bytes = std.math.cast(u16, path.len * 2) orelse return .{
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .open,
},
};
var nt_name = windows.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(path.ptr),
};
var attr = windows.OBJECT_ATTRIBUTES{
.Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
// From the Windows Documentation:
//
// [ObjectName] must be a fully qualified file specification or the name of a device object,
// unless it is the name of a file relative to the directory specified by RootDirectory.
// For example, \Device\Floppy1\myfile.dat or \??\B:\myfile.dat could be the fully qualified
// file specification, provided that the floppy driver and overlying file system are already
// loaded. For more information, see File Names, Paths, and Namespaces.
.ObjectName = &nt_name,
.RootDirectory = if (bun.strings.hasPrefixComptimeType(u16, path, windows.nt_object_prefix))
null
else if (dir == bun.invalid_fd)
std.fs.cwd().fd
else
dir.cast(),
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: windows.IO_STATUS_BLOCK = undefined;
var attributes = options.attributes;
while (true) {
const rc = windows.ntdll.NtCreateFile(
&result,
options.access_mask,
&attr,
&io,
null,
attributes,
options.sharing_mode,
options.disposition,
options.options,
null,
0,
);
if (Environment.allow_assert and Environment.enable_logs) {
if (rc == .INVALID_PARAMETER) {
// Double check what flags you are passing to this
//
// - access_mask probably needs w.SYNCHRONIZE,
// - options probably needs w.FILE_SYNCHRONOUS_IO_NONALERT
// - disposition probably needs w.FILE_OPEN
bun.Output.debugWarn("NtCreateFile({f}, {f}) = {s} (file) = {d}\nYou are calling this function with the wrong flags!!!", .{ dir, bun.fmt.utf16(path), @tagName(rc), @intFromPtr(result) });
} else if (rc == .OBJECT_PATH_SYNTAX_BAD or rc == .OBJECT_NAME_INVALID) {
// See above comment. For absolute paths you must have \??\ at the start.
bun.Output.debugWarn("NtCreateFile({f}, {f}) = {s} (file) = {d}\nYou are calling this function without normalizing the path correctly!!!", .{ dir, bun.fmt.utf16(path), @tagName(rc), @intFromPtr(result) });
} else {
if (rc == .SUCCESS) {
log("NtCreateFile({f}, {f}) = {s} (file) = {f}", .{ dir, bun.fmt.utf16(path), @tagName(rc), bun.FD.fromNative(result) });
} else {
log("NtCreateFile({f}, {f}) = {s} (file) = {}", .{ dir, bun.fmt.utf16(path), @tagName(rc), rc });
}
}
}
if (rc == .ACCESS_DENIED and
attributes == w.FILE_ATTRIBUTE_NORMAL and
(options.access_mask & (w.GENERIC_READ | w.GENERIC_WRITE)) == w.GENERIC_WRITE)
{
// > If CREATE_ALWAYS and FILE_ATTRIBUTE_NORMAL are specified,
// > CreateFile fails and sets the last error to ERROR_ACCESS_DENIED
// > if the file exists and has the FILE_ATTRIBUTE_HIDDEN or
// > FILE_ATTRIBUTE_SYSTEM attribute. To avoid the error, specify the
// > same attributes as the existing file.
//
// The above also applies to NtCreateFile. In order to make this work,
// we retry but only in the case that the file was opened for writing.
//
// See https://github.com/oven-sh/bun/issues/6820
// https://github.com/libuv/libuv/pull/3380
attributes = w.FILE_ATTRIBUTE_HIDDEN;
continue;
}
switch (windows.Win32Error.fromNTStatus(rc)) {
.SUCCESS => {
if (options.access_mask & w.FILE_APPEND_DATA != 0) {
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointerex
const FILE_END = 2;
if (kernel32.SetFilePointerEx(result, 0, null, FILE_END) == 0) {
return .{
.err = .{
.errno = @intFromEnum(E.UNKNOWN),
.syscall = .SetFilePointerEx,
},
};
}
}
return .{ .result = .fromNative(result) };
},
else => |code| {
if (code.toSystemErrno()) |sys_err| {
return .{
.err = .{
.errno = @intFromEnum(sys_err),
.syscall = .open,
},
};
}
return .{
.err = .{
.errno = @intFromEnum(E.UNKNOWN),
.syscall = .open,
},
};
},
}
}
}
// Delete: this doesnt apply to NtCreateFile :(
// pub const WindowsOpenFlags = struct {
// access: w.DWORD,
// share: w.DWORD,
// disposition: w.DWORD,
// attributes: w.DWORD,
// pub fn fromLibUV(flags_in: c_int) error{EINVAL}!WindowsOpenFlags {
// const uv = bun.windows.libuv;
// var flags = flags_in;
// // Adjust flags to be compatible with the memory file mapping. Save the
// // original flags to emulate the correct behavior
// if (flags & uv.UV_FS_O_FILEMAP != 0) {
// if (flags & (O.RDONLY | O.WRONLY | O.RDWR) != 0) {
// flags = (flags & ~@as(c_int, O.WRONLY)) | O.RDWR;
// }
// if (flags & O.APPEND != 0) {
// flags &= ~@as(c_int, O.APPEND);
// flags &= ~@as(c_int, O.RDONLY | O.WRONLY | O.RDWR);
// flags |= O.RDWR;
// }
// }
// var access_flag: w.DWORD = switch (flags & (uv.UV_FS_O_RDONLY | uv.UV_FS_O_WRONLY | uv.UV_FS_O_RDWR)) {
// uv.UV_FS_O_RDONLY => w.FILE_GENERIC_READ,
// uv.UV_FS_O_WRONLY => w.FILE_GENERIC_WRITE,
// uv.UV_FS_O_RDWR => w.FILE_GENERIC_READ | w.FILE_GENERIC_WRITE,
// else => return error.EINVAL,
// };
// if (flags & O.APPEND != 0) {
// access_flag &= ~@as(u32, w.FILE_WRITE_DATA);
// access_flag |= w.FILE_APPEND_DATA;
// }
// access_flag |= w.SYNCHRONIZE;
// const share: w.DWORD = if (flags & uv.UV_FS_O_EXLOCK != 0) 0 else FILE_SHARE;
// const disposition: w.DWORD = switch (flags & uv.UV_FS_O_CREAT | uv.UV_FS_O_EXCL | uv.UV_FS_O_TRUNC) {
// 0,
// uv.UV_FS_O_EXCL,
// => w.OPEN_EXISTING,
// uv.UV_FS_O_CREAT,
// => w.OPEN_ALWAYS,
// uv.UV_FS_O_CREAT | uv.UV_FS_O_EXCL,
// uv.UV_FS_O_CREAT | uv.UV_FS_O_EXCL | uv.UV_FS_O_TRUNC,
// => w.CREATE_NEW,
// uv.UV_FS_O_TRUNC,
// uv.UV_FS_O_TRUNC | uv.UV_FS_O_EXCL,
// => w.TRUNCATE_EXISTING,
// uv.UV_FS_O_CREAT | uv.UV_FS_O_TRUNC,
// => w.TRUNCATE_EXISTING,
// else => return error.EINVAL,
// };
// var attributes: w.DWORD = w.FILE_ATTRIBUTE_NORMAL;
// if (flags & uv.UV_FS_O_CREAT != 0) {
// // if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) {
// }
// if (flags & uv.UV_FS_O_TEMPORARY != 0) {
// attributes |= w.FILE_DELETE_ON_CLOSE;
// access_flag |= w.DELETE;
// }
// if (flags & uv.UV_FS_O_SHORT_LIVED != 0) {
// attributes |= w.FILE_ATTRIBUTE_TEMPORARY;
// }
// switch (flags & (uv.UV_FS_O_SEQUENTIAL | uv.UV_FS_O_RANDOM)) {
// 0 => {},
// uv.UV_FS_O_SEQUENTIAL => attributes |= w.FILE_FLAG_SEQUENTIAL_SCAN,
// uv.UV_FS_O_RANDOM => attributes |= w.FILE_FLAG_SEQUENTIAL_SCAN,
// else => return error.EINVAL,
// }
// if (flags & uv.UV_FS_O_DIRECT != 0) {
// // FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually exclusive.
// // Windows returns 87, ERROR_INVALID_PARAMETER if these are combined.
// //
// // FILE_APPEND_DATA is included in FILE_GENERIC_WRITE:
// //
// // FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE |
// // FILE_WRITE_DATA |
// // FILE_WRITE_ATTRIBUTES |
// // FILE_WRITE_EA |
// // FILE_APPEND_DATA |
// // SYNCHRONIZE
// //
// // Note: Appends are also permitted by FILE_WRITE_DATA.
// //
// // In order for direct writes and direct appends to succeed, we therefore
// // exclude FILE_APPEND_DATA if FILE_WRITE_DATA is specified, and otherwise
// // fail if the user's sole permission is a direct append, since this
// // particular combination is invalid.
// if (access_flag & w.FILE_APPEND_DATA != 0) {
// if (access_flag & w.FILE_WRITE_DATA != 0) {
// access_flag &= @as(u32, w.FILE_APPEND_DATA);
// } else {
// return error.EINVAL;
// }
// }
// attributes |= w.FILE_FLAG_NO_BUFFERING;
// }
// switch (flags & uv.UV_FS_O_DSYNC | uv.UV_FS_O_SYNC) {
// 0 => {},
// else => attributes |= w.FILE_FLAG_WRITE_THROUGH,
// }
// // Setting this flag makes it possible to open a directory.
// attributes |= w.FILE_FLAG_BACKUP_SEMANTICS;
// return .{
// .access = access_flag,
// .share = share,
// .disposition = disposition,
// .attributes = attributes,
// };
// }
// };
pub fn openFileAtWindowsT(
comptime T: type,
dirFd: bun.FileDescriptor,
path: []const T,
options: NtCreateFileOptions,
) Maybe(bun.FileDescriptor) {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
const norm = switch (normalizePathWindows(T, dirFd, path, wbuf, .{})) {
.err => |err| return .{ .err = err },
.result => |norm| norm,
};
return openFileAtWindowsNtPath(dirFd, norm, options);
}
pub fn openFileAtWindows(
dirFd: bun.FileDescriptor,
path: []const u16,
opts: NtCreateFileOptions,
) Maybe(bun.FileDescriptor) {
return openFileAtWindowsT(u16, dirFd, path, opts);
}
pub noinline fn openFileAtWindowsA(
dirFd: bun.FileDescriptor,
path: []const u8,
opts: NtCreateFileOptions,
) Maybe(bun.FileDescriptor) {
return openFileAtWindowsT(u8, dirFd, path, opts);
}
pub fn openatWindowsT(comptime T: type, dir: bun.FileDescriptor, path: []const T, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
return openatWindowsTMaybeNormalize(T, dir, path, flags, perm, true);
}
pub fn openatWindowsTMaybeNormalize(comptime T: type, dir: bun.FileDescriptor, path: []const T, flags: i32, perm: bun.Mode, comptime normalize: bool) Maybe(bun.FileDescriptor) {
if (flags & O.DIRECTORY != 0) {
const windows_options: WindowsOpenDirOptions = .{
.iterable = flags & O.PATH == 0,
.no_follow = flags & O.NOFOLLOW != 0,
.can_rename_or_delete = false,
};
if (comptime !normalize and T == u16) {
return openDirAtWindowsNtPath(dir, path, windows_options);
}
// we interpret O_PATH as meaning that we don't want iteration
return openDirAtWindowsT(
T,
dir,
path,
windows_options,
);
}
const nonblock = flags & O.NONBLOCK != 0;
const overwrite = flags & O.WRONLY != 0 and flags & O.APPEND == 0;
var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
if (flags & O.RDWR != 0) {
access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
} else if (flags & O.APPEND != 0) {
access_mask |= w.GENERIC_WRITE | w.FILE_APPEND_DATA;
} else if (flags & O.WRONLY != 0) {
access_mask |= w.GENERIC_WRITE;
} else {
access_mask |= w.GENERIC_READ;
}
const disposition: w.ULONG = blk: {
if (flags & O.CREAT != 0) {
if (flags & O.EXCL != 0) {
break :blk w.FILE_CREATE;
}
break :blk if (overwrite) w.FILE_OVERWRITE_IF else w.FILE_OPEN_IF;
}
break :blk if (overwrite) w.FILE_OVERWRITE else w.FILE_OPEN;
};
const blocking_flag: windows.ULONG = if (!nonblock) windows.FILE_SYNCHRONOUS_IO_NONALERT else 0;
const file_or_dir_flag: windows.ULONG = switch (flags & O.DIRECTORY != 0) {
// .file_only => windows.FILE_NON_DIRECTORY_FILE,
true => windows.FILE_DIRECTORY_FILE,
false => 0,
};
const follow_symlinks = flags & O.NOFOLLOW == 0;
const options: windows.ULONG = if (follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | windows.FILE_OPEN_REPARSE_POINT;
var attributes: w.DWORD = windows.FILE_ATTRIBUTE_NORMAL;
if (flags & O.CREAT != 0 and perm & 0x80 == 0 and perm != 0) {
attributes |= windows.FILE_ATTRIBUTE_READONLY;
}
const open_options: NtCreateFileOptions = .{
.access_mask = access_mask,
.disposition = disposition,
.options = options,
.attributes = attributes,
};
if (comptime !normalize and T == u16) {
return openFileAtWindowsNtPath(dir, path, open_options);
}
return openFileAtWindowsT(T, dir, path, open_options);
}
pub fn openatWindows(
dir: bun.FileDescriptor,
path: []const u16,
flags: i32,
perm: bun.Mode,
) Maybe(bun.FileDescriptor) {
return openatWindowsT(u16, dir, path, flags, perm);
}
pub fn openatWindowsA(
dir: bun.FileDescriptor,
path: []const u8,
flags: i32,
perm: bun.Mode,
) Maybe(bun.FileDescriptor) {
return openatWindowsT(u8, dir, path, flags, perm);
}
pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSliceZ, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
if (comptime Environment.isMac) {
// https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/open-base.c
const rc = darwin_nocancel.@"openat$NOCANCEL"(dirfd.cast(), file_path.ptr, @bitCast(bun.O.toPacked(flags)), perm);
if (comptime Environment.allow_assert)
log("openat({f}, {s}, {d}) = {d}", .{ dirfd, bun.sliceTo(file_path, 0), flags, rc });
return Maybe(bun.FileDescriptor).errnoSysFP(rc, .open, dirfd, file_path) orelse .{ .result = .fromNative(rc) };
} else if (comptime Environment.isWindows) {
return openatWindowsT(bun.OSPathChar, dirfd, file_path, flags, perm);
}
while (true) {
const rc = syscall.openat(dirfd.cast(), file_path, bun.O.toPacked(flags), perm);
if (comptime Environment.allow_assert)
log("openat({f}, {s}, {d}) = {d}", .{ dirfd, bun.sliceTo(file_path, 0), flags, rc });
return switch (sys.getErrno(rc)) {
.SUCCESS => .{ .result = .fromNative(@intCast(rc)) },
.INTR => continue,
else => |err| {
return .{
.err = .{
.errno = @truncate(@intFromEnum(err)),
.syscall = .open,
},
};
},
};
}
}
pub fn access(path: bun.OSPathSliceZ, mode: i32) Maybe(void) {
if (Environment.isWindows) {
const attrs = getFileAttributes(path) orelse {
return .{ .err = .{
.errno = @intFromEnum(bun.windows.getLastErrno()),
.syscall = .access,
} };
};
if (!((mode & W_OK) > 0) or
!(attrs.is_readonly) or
(attrs.is_directory))
{
return .success;
} else {
return .{ .err = .{
.errno = @intFromEnum(E.PERM),
.syscall = .access,
} };
}
}
// TODO: fix that bun's std library fork has a different parameter type.
return Maybe(void).errnoSysP(syscall.access(path, @bitCast(mode)), .access, path) orelse .success;
}
pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
if (comptime Environment.isWindows) {
return openatWindowsT(u8, dirfd, file_path, flags, perm);
} else {
return openatOSPath(dirfd, file_path, flags, perm);
}
}
pub fn openatFileWithLibuvFlags(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: bun.jsc.Node.FileSystemFlags, perm: bun.Mode) Maybe(bun.FileDescriptor) {
if (comptime Environment.isWindows) {
const f = flags.toWindows() catch return .{ .err = .{
.errno = @intFromEnum(E.INVAL),
.syscall = .open,
.path = file_path,
} };
// TODO: pass f.share
return openFileAtWindowsT(u8, dirfd, file_path, f.access, f.disposition, f.attributes);
} else {
return openatOSPath(dirfd, file_path, flags.asPosix(), perm);
}
}
pub fn openatA(dirfd: bun.FileDescriptor, file_path: []const u8, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
if (comptime Environment.isWindows) {
return openatWindowsT(u8, dirfd, file_path, flags, perm);
}
const pathZ = std.posix.toPosixPath(file_path) catch return Maybe(bun.FileDescriptor){
.err = .{
.errno = @intFromEnum(E.NAMETOOLONG),
.syscall = .open,
},
};
return openatOSPath(
dirfd,
&pathZ,
flags,
perm,
);
}
pub fn openA(file_path: []const u8, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
// this is what open() does anyway.
return openatA(.cwd(), file_path, flags, perm);
}
pub fn open(file_path: [:0]const u8, flags: i32, perm: bun.Mode) Maybe(bun.FileDescriptor) {
// TODO(@paperclover): this should not use libuv; when the libuv path is
// removed here, the call sites in node_fs.zig should make sure they parse
// the libuv specific file flags using the WindowsOpenFlags structure.
if (comptime Environment.isWindows) {
return sys_uv.open(file_path, flags, perm);
}
// this is what open() does anyway.
return openat(.cwd(), file_path, flags, perm);
}
pub const max_count = switch (builtin.os.tag) {
.linux => 0x7ffff000,
.macos, .ios, .watchos, .tvos => std.math.maxInt(i32),
.windows => std.math.maxInt(u32),
else => std.math.maxInt(isize),
};
pub fn write(fd: bun.FileDescriptor, bytes: []const u8) Maybe(usize) {
const adjusted_len = @min(max_count, bytes.len);
var debug_timer = bun.Output.DebugTimer.start();
defer {
if (Environment.isDebug) {
if (debug_timer.timer.read() > std.time.ns_per_ms) {
log("write({f}, {d}) blocked for {f}", .{ fd, bytes.len, debug_timer });
}
}
}
return switch (Environment.os) {
.mac => {
const rc = darwin_nocancel.@"write$NOCANCEL"(fd.cast(), bytes.ptr, adjusted_len);
log("write({f}, {d}) = {d} ({f})", .{ fd, adjusted_len, rc, debug_timer });
if (Maybe(usize).errnoSysFd(rc, .write, fd)) |err| {
return err;
}
return Maybe(usize){ .result = @intCast(rc) };
},
.linux => {
while (true) {
const rc = syscall.write(fd.cast(), bytes.ptr, adjusted_len);
log("write({f}, {d}) = {d} {f}", .{ fd, adjusted_len, rc, debug_timer });
if (Maybe(usize).errnoSysFd(rc, .write, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @intCast(rc) };
}
},
.windows => {
// "WriteFile sets this value to zero before doing any work or error checking."
var bytes_written: u32 = undefined;
bun.assert(bytes.len > 0);
const rc = kernel32.WriteFile(
fd.cast(),
bytes.ptr,
@as(u32, @truncate(adjusted_len)),
&bytes_written,
null,
);
if (rc == 0) {
log("WriteFile({f}, {d}) = {s}", .{ fd, adjusted_len, @tagName(bun.windows.getLastErrno()) });
const er = std.os.windows.kernel32.GetLastError();
if (er == .ACCESS_DENIED) {
// file is not writable
return .{ .err = .{
.errno = @intFromEnum(SystemErrno.EBADF),
.syscall = .write,
.fd = fd,
} };
}
const errno = (SystemErrno.init(bun.windows.kernel32.GetLastError()) orelse SystemErrno.EUNKNOWN).toE();
return .{
.err = sys.Error{
.errno = @intFromEnum(errno),
.syscall = .write,
.fd = fd,
},
};
}
log("WriteFile({f}, {d}) = {d}", .{ fd, adjusted_len, bytes_written });
return Maybe(usize){ .result = bytes_written };
},
.wasm => @compileError("Not implemented yet"),
};
}
fn veclen(buffers: anytype) usize {
var len: usize = 0;
for (buffers) |buffer| {
len += buffer.len;
}
return len;
}
pub fn writev(fd: bun.FileDescriptor, buffers: []std.posix.iovec) Maybe(usize) {
if (comptime Environment.isMac) {
const rc = writev_sym(fd.cast(), @as([*]std.posix.iovec_const, @ptrCast(buffers.ptr)), @as(i32, @intCast(buffers.len)));
if (comptime Environment.allow_assert)
log("writev({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| {
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
while (true) {
const rc = writev_sym(fd.cast(), @as([*]std.posix.iovec_const, @ptrCast(buffers.ptr)), buffers.len);
if (comptime Environment.allow_assert)
log("writev({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
unreachable;
}
}
pub fn pwritev(fd: bun.FileDescriptor, buffers: []const bun.PlatformIOVecConst, position: isize) Maybe(usize) {
if (comptime Environment.isWindows) {
return sys_uv.pwritev(fd, buffers, position);
}
if (comptime Environment.isMac) {
const rc = pwritev_sym(fd.cast(), buffers.ptr, @as(i32, @intCast(buffers.len)), position);
if (comptime Environment.allow_assert)
log("pwritev({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| {
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
while (true) {
const rc = pwritev_sym(fd.cast(), buffers.ptr, buffers.len, position);
if (comptime Environment.allow_assert)
log("pwritev({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
unreachable;
}
}
pub fn readv(fd: bun.FileDescriptor, buffers: []std.posix.iovec) Maybe(usize) {
if (comptime Environment.allow_assert) {
if (buffers.len == 0) {
bun.Output.debugWarn("readv() called with 0 length buffer", .{});
}
}
if (comptime Environment.isMac) {
const rc = readv_sym(fd.cast(), buffers.ptr, @as(i32, @intCast(buffers.len)));
if (comptime Environment.allow_assert)
log("readv({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| {
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
while (true) {
const rc = readv_sym(fd.cast(), buffers.ptr, buffers.len);
if (comptime Environment.allow_assert)
log("readv({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
unreachable;
}
}
pub fn preadv(fd: bun.FileDescriptor, buffers: []std.posix.iovec, position: isize) Maybe(usize) {
if (comptime Environment.allow_assert) {
if (buffers.len == 0) {
bun.Output.debugWarn("preadv() called with 0 length buffer", .{});
}
}
if (comptime Environment.isMac) {
const rc = preadv_sym(fd.cast(), buffers.ptr, @as(i32, @intCast(buffers.len)), position);
if (comptime Environment.allow_assert)
log("preadv({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| {
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
while (true) {
const rc = preadv_sym(fd.cast(), buffers.ptr, buffers.len, position);
if (comptime Environment.allow_assert)
log("preadv({f}, {d}) = {d}", .{ fd, veclen(buffers), rc });
if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
unreachable;
}
}
const preadv_sym = if (builtin.os.tag == .linux and builtin.link_libc)
std.os.linux.preadv
else if (builtin.os.tag.isDarwin())
darwin_nocancel.@"preadv$NOCANCEL"
else
syscall.preadv;
const readv_sym = if (builtin.os.tag == .linux and builtin.link_libc)
std.os.linux.readv
else if (builtin.os.tag.isDarwin())
darwin_nocancel.@"readv$NOCANCEL"
else
syscall.readv;
const pwritev_sym = if (builtin.os.tag == .linux and builtin.link_libc)
std.os.linux.pwritev
else if (builtin.os.tag.isDarwin())
darwin_nocancel.@"pwritev$NOCANCEL"
else
syscall.pwritev;
const writev_sym = if (builtin.os.tag.isDarwin())
darwin_nocancel.@"writev$NOCANCEL"
else
syscall.writev;
const pread_sym = if (builtin.os.tag.isDarwin())
darwin_nocancel.@"pread$NOCANCEL"
else
syscall.pread;
const fcntl_symbol = syscall.fcntl;
pub fn pread(fd: bun.FileDescriptor, buf: []u8, offset: i64) Maybe(usize) {
const adjusted_len = @min(buf.len, max_count);
if (comptime Environment.allow_assert) {
if (adjusted_len == 0) {
bun.Output.debugWarn("pread() called with 0 length buffer", .{});
}
}
const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned
while (true) {
const rc = pread_sym(fd.cast(), buf.ptr, adjusted_len, ioffset);
if (Maybe(usize).errnoSysFd(rc, .pread, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
}
const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc and !bun.Environment.isMusl)
libc.pwrite64
else
syscall.pwrite;
pub fn pwrite(fd: bun.FileDescriptor, bytes: []const u8, offset: i64) Maybe(usize) {
if (comptime Environment.allow_assert) {
if (bytes.len == 0) {
bun.Output.debugWarn("pwrite() called with 0 length buffer", .{});
}
}
const adjusted_len = @min(bytes.len, max_count);
const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned
while (true) {
const rc = pwrite_sym(fd.cast(), bytes.ptr, adjusted_len, ioffset);
return if (Maybe(usize).errnoSysFd(rc, .pwrite, fd)) |err| {
switch (err.getErrno()) {
.INTR => continue,
else => return err,
}
} else Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
}
pub fn read(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
if (comptime Environment.allow_assert) {
if (buf.len == 0) {
bun.Output.debugWarn("read() called with 0 length buffer", .{});
}
}
const debug_timer = bun.Output.DebugTimer.start();
const adjusted_len = @min(buf.len, max_count);
return switch (Environment.os) {
.mac => {
const rc = darwin_nocancel.@"read$NOCANCEL"(fd.cast(), buf.ptr, adjusted_len);
if (Maybe(usize).errnoSysFd(rc, .read, fd)) |err| {
log("read({f}, {d}) = {s} ({f})", .{ fd, adjusted_len, err.err.name(), debug_timer });
return err;
}
log("read({f}, {d}) = {d} ({f})", .{ fd, adjusted_len, rc, debug_timer });
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
},
.linux => {
while (true) {
const rc = syscall.read(fd.cast(), buf.ptr, adjusted_len);
log("read({f}, {d}) = {d} ({f})", .{ fd, adjusted_len, rc, debug_timer });
if (Maybe(usize).errnoSysFd(rc, .read, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
},
.windows => if (fd.kind == .uv)
sys_uv.read(fd, buf)
else {
var amount_read: u32 = 0;
const rc = kernel32.ReadFile(fd.native(), buf.ptr, @as(u32, @truncate(adjusted_len)), &amount_read, null);
if (rc == windows.FALSE) {
const ret: Maybe(usize) = .{
.err = sys.Error{
.errno = @intFromEnum(bun.windows.getLastErrno()),
.syscall = .read,
.fd = fd,
},
};
if (comptime Environment.isDebug) {
log("ReadFile({f}, {d}) = {s} ({f})", .{ fd, adjusted_len, ret.err.name(), debug_timer });
}
return ret;
}
log("ReadFile({f}, {d}) = {d} ({f})", .{ fd, adjusted_len, amount_read, debug_timer });
return Maybe(usize){ .result = amount_read };
},
.wasm => @compileError("read is not implemented on this platform"),
};
}
pub fn readAll(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
var rest = buf;
var total_read: usize = 0;
while (rest.len > 0) {
switch (read(fd, rest)) {
.result => |len| {
if (len == 0) break;
rest = rest[len..];
total_read += len;
},
.err => |err| return .{ .err = err },
}
}
return .{ .result = total_read };
}
const socket_flags_nonblock = c.MSG_DONTWAIT | c.MSG_NOSIGNAL;
pub fn recvNonBlock(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
return recv(fd, buf, socket_flags_nonblock);
}
pub fn poll(fds: []std.posix.pollfd, timeout: i32) Maybe(usize) {
while (true) {
const rc = switch (Environment.os) {
.mac => darwin_nocancel.@"poll$NOCANCEL"(fds.ptr, fds.len, timeout),
.linux => linux.poll(fds.ptr, fds.len, timeout),
.wasm => @compileError("poll is not implemented on this platform"),
};
if (Maybe(usize).errnoSys(rc, .poll)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .{ .result = @as(usize, @intCast(rc)) };
}
}
pub fn ppoll(fds: []std.posix.pollfd, timeout: ?*std.posix.timespec, sigmask: ?*const std.posix.sigset_t) Maybe(usize) {
while (true) {
const rc = switch (Environment.os) {
.mac => darwin_nocancel.@"ppoll$NOCANCEL"(fds.ptr, fds.len, timeout, sigmask),
.linux => linux.ppoll(fds.ptr, fds.len, timeout, sigmask),
.wasm => @compileError("ppoll is not implemented on this platform"),
};
if (Maybe(usize).errnoSys(rc, .ppoll)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .{ .result = @as(usize, @intCast(rc)) };
}
}
pub fn recv(fd: bun.FileDescriptor, buf: []u8, flag: u32) Maybe(usize) {
const adjusted_len = @min(buf.len, max_count);
const debug_timer = bun.Output.DebugTimer.start();
if (comptime Environment.allow_assert) {
if (adjusted_len == 0) {
bun.Output.debugWarn("recv() called with 0 length buffer", .{});
}
}
if (comptime Environment.isMac) {
const rc = darwin_nocancel.@"recvfrom$NOCANCEL"(fd.cast(), buf.ptr, adjusted_len, flag, null, null);
if (Maybe(usize).errnoSysFd(rc, .recv, fd)) |err| {
log("recv({f}, {d}) = {s} {f}", .{ fd, adjusted_len, err.err.name(), debug_timer });
return err;
}
log("recv({f}, {d}) = {d} {f}", .{ fd, adjusted_len, rc, debug_timer });
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
while (true) {
const rc = linux.recvfrom(fd.cast(), buf.ptr, adjusted_len, flag, null, null);
if (Maybe(usize).errnoSysFd(rc, .recv, fd)) |err| {
if (err.getErrno() == .INTR) continue;
log("recv({f}, {d}) = {s} {f}", .{ fd, adjusted_len, err.err.name(), debug_timer });
return err;
}
log("recv({f}, {d}) = {d} {f}", .{ fd, adjusted_len, rc, debug_timer });
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
}
}
pub fn kevent(fd: bun.FileDescriptor, changelist: []const std.c.Kevent, eventlist: []std.c.Kevent, timeout: ?*std.posix.timespec) Maybe(usize) {
while (true) {
const rc = std.c.kevent(fd.cast(), changelist.ptr, @intCast(changelist.len), eventlist.ptr, @intCast(eventlist.len), timeout);
if (Maybe(usize).errnoSysFd(rc, .kevent, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .{ .result = @as(usize, @intCast(rc)) };
}
unreachable;
}
pub fn sendNonBlock(fd: bun.FileDescriptor, buf: []const u8) Maybe(usize) {
return send(fd, buf, socket_flags_nonblock);
}
pub fn send(fd: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) {
if (comptime Environment.isMac) {
const debug_timer = bun.Output.DebugTimer.start();
const rc = darwin_nocancel.@"sendto$NOCANCEL"(fd.cast(), buf.ptr, buf.len, flag, null, 0);
if (Maybe(usize).errnoSysFd(rc, .send, fd)) |err| {
syslog("send({f}, {d}) = {s} ({f})", .{ fd, buf.len, err.err.name(), debug_timer });
return err;
}
syslog("send({f}, {d}) = {d} ({f})", .{ fd, buf.len, rc, debug_timer });
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
} else {
const debug_timer = bun.Output.DebugTimer.start();
while (true) {
const rc = linux.sendto(fd.cast(), buf.ptr, buf.len, flag, null, 0);
if (Maybe(usize).errnoSysFd(rc, .send, fd)) |err| {
if (err.getErrno() == .INTR) continue;
syslog("send({f}, {d}) = {s} ({f})", .{ fd, buf.len, err.err.name(), debug_timer });
return err;
}
syslog("send({f}, {d}) = {d} ({f})", .{ fd, buf.len, rc, debug_timer });
return Maybe(usize){ .result = @as(usize, @intCast(rc)) };
}
}
}
pub fn pidfd_open(pid: std.os.linux.pid_t, flags: u32) Maybe(i32) {
while (true) {
const rc = linux.pidfd_open(pid, flags);
if (Maybe(i32).errnoSys(rc, .pidfd_open)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(i32){ .result = @intCast(rc) };
}
unreachable;
}
pub fn lseek(fd: bun.FileDescriptor, offset: i64, whence: usize) Maybe(usize) {
while (true) {
const rc = syscall.lseek(fd.cast(), offset, whence);
if (Maybe(usize).errnoSysFd(rc, .lseek, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return Maybe(usize){ .result = rc };
}
}
pub fn readlink(in: [:0]const u8, buf: []u8) Maybe([:0]u8) {
if (comptime Environment.isWindows) {
return sys_uv.readlink(in, buf);
}
while (true) {
const rc = syscall.readlink(in, buf.ptr, buf.len);
if (Maybe([:0]u8).errnoSysP(rc, .readlink, in)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
buf[@intCast(rc)] = 0;
return .{ .result = buf[0..@intCast(rc) :0] };
}
}
pub fn readlinkat(fd: bun.FileDescriptor, in: [:0]const u8, buf: []u8) Maybe([:0]u8) {
while (true) {
const rc = syscall.readlinkat(fd.cast(), in, buf.ptr, buf.len);
if (Maybe([:0]u8).errnoSysFP(rc, .readlink, fd, in)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
buf[@intCast(rc)] = 0;
return Maybe([:0]u8){ .result = buf[0..@intCast(rc) :0] };
}
}
pub fn ftruncate(fd: bun.FileDescriptor, size: isize) Maybe(void) {
if (comptime Environment.isWindows) {
var io_status_block: std.os.windows.IO_STATUS_BLOCK = undefined;
var eof_info = std.os.windows.FILE_END_OF_FILE_INFORMATION{
.EndOfFile = @bitCast(size),
};
const rc = windows.ntdll.NtSetInformationFile(
fd.cast(),
&io_status_block,
&eof_info,
@sizeOf(std.os.windows.FILE_END_OF_FILE_INFORMATION),
.FileEndOfFileInformation,
);
return Maybe(void).errnoSysFd(rc, .ftruncate, fd) orelse .success;
}
return while (true) {
if (Maybe(void).errnoSysFd(syscall.ftruncate(fd.cast(), size), .ftruncate, fd)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
};
}
pub fn rename(from: [:0]const u8, to: [:0]const u8) Maybe(void) {
while (true) {
if (Maybe(void).errnoSys(syscall.rename(from, to), .rename)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
}
pub const RenameAt2Flags = packed struct {
exchange: bool = false,
exclude: bool = false,
nofollow: bool = false,
pub fn int(self: RenameAt2Flags) u32 {
var flags: u32 = 0;
if (comptime Environment.isMac) {
if (self.exchange) flags |= c.RENAME_SWAP;
if (self.exclude) flags |= c.RENAME_EXCL;
if (self.nofollow) flags |= c.RENAME_NOFOLLOW_ANY;
} else {
if (self.exchange) flags |= c.RENAME_EXCHANGE;
if (self.exclude) flags |= c.RENAME_NOREPLACE;
}
return flags;
}
};
pub fn renameatConcurrently(
from_dir_fd: bun.FileDescriptor,
from: [:0]const u8,
to_dir_fd: bun.FileDescriptor,
to: [:0]const u8,
comptime opts: struct { move_fallback: bool = false },
) Maybe(void) {
switch (renameatConcurrentlyWithoutFallback(from_dir_fd, from, to_dir_fd, to)) {
.result => return .success,
.err => |e| {
if (opts.move_fallback and e.getErrno() == E.XDEV) {
bun.Output.debugWarn("renameatConcurrently() failed with E.XDEV, falling back to moveFileZSlowMaybe()", .{});
return moveFileZSlowMaybe(from_dir_fd, from, to_dir_fd, to);
}
return .{ .err = e };
},
}
}
pub fn renameatConcurrentlyWithoutFallback(
from_dir_fd: bun.FileDescriptor,
from: [:0]const u8,
to_dir_fd: bun.FileDescriptor,
to: [:0]const u8,
) Maybe(void) {
var did_atomically_replace = false;
attempt_atomic_rename_and_fallback_to_racy_delete: {
{
// Happy path: the folder doesn't exist in the cache dir, so we can
// just rename it. We don't need to delete anything.
var err = switch (renameat2(from_dir_fd, from, to_dir_fd, to, .{
.exclude = true,
})) {
// if ENOENT don't retry
.err => |err| if (err.getErrno() == .NOENT) return .{ .err = err } else err,
.result => break :attempt_atomic_rename_and_fallback_to_racy_delete,
};
// Windows doesn't have any equivalent with renameat with swap
if (!bun.Environment.isWindows) {
// Fallback path: the folder exists in the cache dir, it might be in a strange state
// let's attempt to atomically replace it with the temporary folder's version
if (switch (err.getErrno()) {
.EXIST, .NOTEMPTY, .OPNOTSUPP => true,
else => false,
}) {
did_atomically_replace = true;
switch (renameat2(from_dir_fd, from, to_dir_fd, to, .{
.exchange = true,
})) {
.err => {},
.result => break :attempt_atomic_rename_and_fallback_to_racy_delete,
}
did_atomically_replace = false;
}
}
}
// sad path: let's try to delete the folder and then rename it
if (to_dir_fd.isValid()) {
var to_dir = to_dir_fd.stdDir();
to_dir.deleteTree(to) catch {};
} else {
std.fs.deleteTreeAbsolute(to) catch {};
}
switch (renameat(from_dir_fd, from, to_dir_fd, to)) {
.err => |err| {
return .{ .err = err };
},
.result => {},
}
}
return .success;
}
pub fn renameat2(from_dir: bun.FileDescriptor, from: [:0]const u8, to_dir: bun.FileDescriptor, to: [:0]const u8, flags: RenameAt2Flags) Maybe(void) {
if (Environment.isWindows) {
return renameat(from_dir, from, to_dir, to);
}
while (true) {
const rc = switch (comptime Environment.os) {
.linux => std.os.linux.renameat2(@intCast(from_dir.cast()), from.ptr, @intCast(to_dir.cast()), to.ptr, flags.int()),
.mac => bun.c.renameatx_np(@intCast(from_dir.cast()), from.ptr, @intCast(to_dir.cast()), to.ptr, flags.int()),
.windows, .wasm => @compileError("renameat2() is not implemented on this platform"),
};
if (Maybe(void).errnoSys(rc, .rename)) |err| {
if (err.getErrno() == .INTR) continue;
if (comptime Environment.allow_assert)
log("renameat2({f}, {s}, {f}, {s}) = {d}", .{ from_dir, from, to_dir, to, @intFromEnum(err.getErrno()) });
return err;
}
if (comptime Environment.allow_assert)
log("renameat2({f}, {s}, {f}, {s}) = {d}", .{ from_dir, from, to_dir, to, 0 });
return .success;
}
}
pub fn renameat(from_dir: bun.FileDescriptor, from: [:0]const u8, to_dir: bun.FileDescriptor, to: [:0]const u8) Maybe(void) {
if (Environment.isWindows) {
const w_buf_from = bun.w_path_buffer_pool.get();
const w_buf_to = bun.w_path_buffer_pool.get();
defer {
bun.w_path_buffer_pool.put(w_buf_from);
bun.w_path_buffer_pool.put(w_buf_to);
}
const rc = bun.windows.renameAtW(
from_dir,
bun.strings.toNTPath(w_buf_from, from),
to_dir,
bun.strings.toNTPath(w_buf_to, to),
true,
);
return rc;
}
while (true) {
if (Maybe(void).errnoSys(syscall.renameat(from_dir.cast(), from, to_dir.cast(), to), .rename)) |err| {
if (err.getErrno() == .INTR) continue;
if (comptime Environment.allow_assert)
log("renameat({f}, {s}, {f}, {s}) = {d}", .{ from_dir, from, to_dir, to, @intFromEnum(err.getErrno()) });
return err;
}
if (comptime Environment.allow_assert)
log("renameat({f}, {s}, {f}, {s}) = {d}", .{ from_dir, from, to_dir, to, 0 });
return .success;
}
}
pub fn chown(path: [:0]const u8, uid: posix.uid_t, gid: posix.gid_t) Maybe(void) {
while (true) {
if (Maybe(void).errnoSysP(c.chown(path, uid, gid), .chown, path)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
}
/// Same as symlink, except it handles ETXTBUSY by unlinking and retrying.
pub fn symlinkRunningExecutable(target: [:0]const u8, dest: [:0]const u8) Maybe(void) {
return switch (symlink(target, dest)) {
.err => |err| switch (err.getErrno()) {
// If we get ETXTBUSY or BUSY, try deleting it and then symlinking.
.BUSY, .TXTBSY => {
_ = unlink(dest);
return symlink(target, dest);
},
else => .{ .err = err },
},
.result => .{ .result = {} },
};
}
pub fn symlink(target: [:0]const u8, dest: [:0]const u8) Maybe(void) {
while (true) {
if (Maybe(void).errnoSys(syscall.symlink(target, dest), .symlink)) |err| {
if (err.getErrno() == .INTR) continue;
log("symlink({s}, {s}) = {s}", .{ target, dest, @tagName(err.getErrno()) });
return err;
}
log("symlink({s}, {s}) = 0", .{ target, dest });
return .success;
}
}
pub fn symlinkat(target: [:0]const u8, dirfd: bun.FileDescriptor, dest: [:0]const u8) Maybe(void) {
while (true) {
if (Maybe(void).errnoSys(syscall.symlinkat(target, dirfd.cast(), dest), .symlinkat)) |err| {
if (err.getErrno() == .INTR) continue;
log("symlinkat({s}, {f}, {s}) = {s}", .{ target, dirfd, dest, @tagName(err.getErrno()) });
return err;
}
log("symlinkat({s}, {f}, {s}) = 0", .{ target, dirfd, dest });
return .success;
}
}
pub const WindowsSymlinkOptions = packed struct {
directory: bool = false,
var symlink_flags: u32 = w.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
pub fn flags(this: WindowsSymlinkOptions) u32 {
if (this.directory) {
symlink_flags |= w.SYMBOLIC_LINK_FLAG_DIRECTORY;
}
return symlink_flags;
}
pub fn denied() void {
symlink_flags = 0;
}
pub var has_failed_to_create_symlink = false;
};
/// Symlinks on Windows can be relative or absolute, and junctions can
/// only be absolute. Passing `null` for `abs_fallback_junction_target`
/// is saying `target` is already absolute.
pub fn symlinkOrJunction(dest: [:0]const u8, target: [:0]const u8, abs_fallback_junction_target: ?[:0]const u8) Maybe(void) {
if (comptime !Environment.isWindows) {
// return symlink(target, dest);
@compileError("windows only plz!!");
}
if (!WindowsSymlinkOptions.has_failed_to_create_symlink) {
const sym16 = bun.w_path_buffer_pool.get();
const target16 = bun.w_path_buffer_pool.get();
defer {
bun.w_path_buffer_pool.put(sym16);
bun.w_path_buffer_pool.put(target16);
}
const sym_path = bun.strings.toWPathNormalizeAutoExtend(sym16, dest);
const target_path = bun.strings.toWPathNormalizeAutoExtend(target16, target);
switch (symlinkW(sym_path, target_path, .{ .directory = true })) {
.result => {
return .success;
},
.err => |err| {
switch (err.getErrno()) {
.EXIST, .NOENT => {
// if the destination already exists, or a component
// of the destination doesn't exist, return the error
// without trying junctions.
return .{ .err = err };
},
else => {
// fallthrough to junction
},
}
},
}
}
return sys_uv.symlinkUV(
abs_fallback_junction_target orelse target,
dest,
bun.windows.libuv.UV_FS_SYMLINK_JUNCTION,
);
}
pub fn symlinkW(dest: [:0]const u16, target: [:0]const u16, options: WindowsSymlinkOptions) Maybe(void) {
while (true) {
const flags = options.flags();
if (windows.CreateSymbolicLinkW(dest, target, flags) == 0) {
const errno = bun.windows.Win32Error.get();
log("CreateSymbolicLinkW({f}, {f}, {}) = {s}", .{
bun.fmt.fmtPath(u16, dest, .{}),
bun.fmt.fmtPath(u16, target, .{}),
flags,
@tagName(errno),
});
switch (errno) {
.INVALID_PARAMETER => {
if ((flags & w.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0) {
WindowsSymlinkOptions.denied();
continue;
}
},
else => {},
}
if (errno.toSystemErrno()) |err| {
switch (err) {
.ENOENT,
.EEXIST,
=> {
return .{
.err = .{
.errno = @intFromEnum(err),
.syscall = .symlink,
},
};
},
else => {},
}
WindowsSymlinkOptions.has_failed_to_create_symlink = true;
return .{
.err = .{
.errno = @intFromEnum(err),
.syscall = .symlink,
},
};
}
}
log("CreateSymbolicLinkW({f}, {f}, {}) = 0", .{
bun.fmt.fmtPath(u16, dest, .{}),
bun.fmt.fmtPath(u16, target, .{}),
flags,
});
return .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.clonefile(from, to, 0), .clonefile)) |err| {
if (err.getErrno() == .INTR) continue;
log("clonefile({s}, {s}) = {s}", .{ from, to, @tagName(err.getErrno()) });
return err;
}
log("clonefile({s}, {s}) = 0", .{ from, to });
return .success;
}
}
pub fn clonefileat(from: FD, from_path: [:0]const u8, to: FD, to_path: [:0]const u8) Maybe(void) {
if (comptime !Environment.isMac) {
@compileError("macOS only");
}
while (true) {
if (Maybe(void).errnoSys(c.clonefileat(from.cast(), from_path, to.cast(), to_path, 0), .clonefileat)) |err| {
if (err.getErrno() == .INTR) continue;
log(
\\clonefileat(
\\ {f},
\\ {s},
\\ {f},
\\ {s},
\\) = {s}
\\
, .{ from, from_path, to, to_path, @tagName(err.getErrno()) });
return err;
}
log(
\\clonefileat(
\\ {f},
\\ {s},
\\ {f},
\\ {s},
\\) = 0
\\
, .{ from, from_path, to, to_path });
return .success;
}
}
pub fn copyfile(from: [:0]const u8, to: [:0]const u8, flags: posix.system.COPYFILE) Maybe(void) {
if (comptime !Environment.isMac) @compileError("macOS only");
while (true) {
if (Maybe(void).errnoSys(c.copyfile(from, to, null, flags), .copyfile)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
}
pub fn fcopyfile(fd_in: bun.FileDescriptor, fd_out: bun.FileDescriptor, flags: posix.system.COPYFILE) Maybe(void) {
if (comptime !Environment.isMac) @compileError("macOS only");
while (true) {
if (Maybe(void).errnoSys(syscall.fcopyfile(fd_in.cast(), fd_out.cast(), null, flags), .fcopyfile)) |err| {
if (err.getErrno() == .INTR) continue;
return err;
}
return .success;
}
}
pub fn unlinkW(from: [:0]const u16) Maybe(void) {
const ret = windows.DeleteFileW(from);
if (Maybe(void).errnoSys(ret, .unlink)) |err| {
log("DeleteFileW({f}) = {s}", .{ bun.fmt.fmtPath(u16, from, .{}), @tagName(err.getErrno()) });
return err;
}
log("DeleteFileW({f}) = 0", .{bun.fmt.fmtPath(u16, from, .{})});
return .success;
}
pub fn unlink(from: [:0]const u8) Maybe(void) {
if (comptime Environment.isWindows) {
const w_buf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(w_buf);
return unlinkW(bun.strings.toWPathNormalizeAutoExtend(w_buf, from));
}
while (true) {
if (Maybe(void).errnoSysP(syscall.unlink(from), .unlink, from)) |err| {
if (err.getErrno() == .INTR) continue;
log("unlink({s}) = {s}", .{ from, @tagName(err.getErrno()) });
return err;
}
log("unlink({s}) = 0", .{from});
return .success;
}
}
pub fn rmdir(to: anytype) Maybe(void) {
return rmdirat(FD.cwd(), to);
}
pub fn rmdirat(dirfd: bun.FileDescriptor, to: anytype) Maybe(void) {
return unlinkatWithFlags(dirfd, to, std.posix.AT.REMOVEDIR);
}
pub fn unlinkatWithFlags(dirfd: bun.FileDescriptor, to: anytype, flags: c_uint) Maybe(void) {
if (Environment.isWindows) {
if (comptime std.meta.Elem(@TypeOf(to)) == u8) {
const w_buf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(w_buf);
return unlinkatWithFlags(dirfd, bun.strings.toNTPath(w_buf, bun.span(to)), flags);
}
return bun.windows.DeleteFileBun(to, .{
.dir = if (dirfd != bun.invalid_fd) dirfd.cast() else null,
.remove_dir = flags & std.posix.AT.REMOVEDIR != 0,
});
}
while (true) {
if (Maybe(void).errnoSysFP(syscall.unlinkat(dirfd.cast(), to, flags), .unlink, dirfd, to)) |err| {
if (err.getErrno() == .INTR) continue;
if (comptime Environment.allow_assert)
log("unlinkat({f}, {s}) = {s}", .{ dirfd, bun.sliceTo(to, 0), @tagName(err.getErrno()) });
return err;
}
if (comptime Environment.allow_assert)
log("unlinkat({f}, {s}) = 0", .{ dirfd, bun.sliceTo(to, 0) });
return .success;
}
unreachable;
}
pub fn unlinkat(dirfd: bun.FileDescriptor, to: anytype) Maybe(void) {
if (Environment.isWindows) {
return unlinkatWithFlags(dirfd, to, 0);
}
while (true) {
if (Maybe(void).errnoSysFP(syscall.unlinkat(dirfd.cast(), to, 0), .unlink, dirfd, to)) |err| {
if (err.getErrno() == .INTR) continue;
if (comptime Environment.allow_assert)
log("unlinkat({f}, {s}) = {s}", .{ dirfd, bun.sliceTo(to, 0), @tagName(err.getErrno()) });
return err;
}
if (comptime Environment.allow_assert)
log("unlinkat({f}, {s}) = 0", .{ dirfd, bun.sliceTo(to, 0) });
return .success;
}
}
pub fn getFdPath(fd: bun.FileDescriptor, out_buffer: *bun.PathBuffer) Maybe([]u8) {
switch (Environment.os) {
.windows => {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = bun.windows.GetFinalPathNameByHandle(fd.cast(), .{}, wide_buf[0..]) catch {
return Maybe([]u8){ .err = .{ .errno = @intFromEnum(SystemErrno.EBADF), .syscall = .GetFinalPathNameByHandle } };
};
// Trust that Windows gives us valid UTF-16LE.
return .{ .result = @constCast(bun.strings.fromWPath(out_buffer, wide_slice)) };
},
.mac => {
// On macOS, we can use F.GETPATH fcntl command to query the OS for
// the path to the file descriptor.
@memset(out_buffer[0..out_buffer.*.len], 0);
switch (fcntl(fd, posix.F.GETPATH, @intFromPtr(out_buffer))) {
.err => |err| return .{ .err = err },
.result => {},
}
return .{ .result = bun.sliceTo(out_buffer, 0) };
},
.linux => {
// TODO: alpine linux may not have /proc/self
var procfs_buf: ["/proc/self/fd/-2147483648".len + 1:0]u8 = undefined;
const proc_path = std.fmt.bufPrintZ(&procfs_buf, "/proc/self/fd/{d}", .{fd.cast()}) catch unreachable;
return switch (readlink(proc_path, out_buffer)) {
.err => |err| return .{ .err = err },
.result => |result| .{ .result = result },
};
},
.wasm => @compileError("querying for canonical path of a handle is unsupported on this host"),
}
}
/// Use of a mapped region can result in these signals:
/// * SIGSEGV - Attempted write into a region mapped as read-only.
/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
pub fn mmap(
ptr: ?[*]align(page_size_min) u8,
length: usize,
prot: u32,
flags: std.posix.MAP,
fd: bun.FileDescriptor,
offset: u64,
) Maybe([]align(page_size_min) u8) {
const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned
const rc = std.c.mmap(ptr, length, prot, flags, fd.cast(), ioffset);
const fail = std.c.MAP_FAILED;
if (rc == fail) {
return .initErr(.{
.errno = @as(sys.Error.Int, @truncate(@intFromEnum(getErrno(@as(i64, @bitCast(@intFromPtr(fail))))))),
.syscall = .mmap,
});
}
return .initResult(@as([*]align(page_size_min) u8, @ptrCast(@alignCast(rc)))[0..length]);
}
pub fn mmapFile(path: [:0]const u8, flags: std.c.MAP, wanted_size: ?usize, offset: usize) Maybe([]align(page_size_min) u8) {
const fd = switch (open(path, bun.O.RDWR, 0)) {
.result => |fd| fd,
.err => |err| return .{ .err = err },
};
defer fd.close();
var size = std.math.sub(usize, @as(usize, @intCast(switch (fstat(fd)) {
.result => |result| result.size,
.err => |err| {
return .{ .err = err };
},
})), offset) catch 0;
if (wanted_size) |size_| size = @min(size, size_);
const map = switch (mmap(null, size, posix.PROT.READ | posix.PROT.WRITE, flags, fd, offset)) {
.result => |map| map,
.err => |err| {
return .{ .err = err };
},
};
return .{ .result = map };
}
pub fn setCloseOnExec(fd: bun.FileDescriptor) Maybe(void) {
switch (fcntl(fd, std.posix.F.GETFD, 0)) {
.result => |fl| {
switch (fcntl(fd, std.posix.F.SETFD, fl | std.posix.FD_CLOEXEC)) {
.result => {},
.err => |err| return .{ .err = err },
}
},
.err => |err| return .{ .err = err },
}
return .success;
}
pub fn setsockopt(fd: bun.FileDescriptor, level: c_int, optname: u32, value: i32) Maybe(i32) {
while (true) {
const rc = syscall.setsockopt(fd.cast(), level, optname, std.mem.asBytes(&value), @sizeOf(i32));
if (Maybe(i32).errnoSysFd(rc, .setsockopt, fd)) |err| {
if (err.getErrno() == .INTR) continue;
log("setsockopt() = {d} {s}", .{ err.err.errno, err.err.name() });
return err;
}
log("setsockopt({d}, {d}, {d}) = {d}", .{ fd.cast(), level, optname, rc });
return .{ .result = @intCast(rc) };
}
unreachable;
}
pub fn setNoSigpipe(fd: bun.FileDescriptor) Maybe(void) {
if (comptime Environment.isMac) {
log("setNoSigpipe({f})", .{fd});
return switch (setsockopt(fd, std.posix.SOL.SOCKET, std.posix.SO.NOSIGPIPE, 1)) {
.result => .success,
.err => |err| .{ .err = err },
};
}
return .success;
}
const socketpair_t = if (Environment.isLinux) i32 else c_uint;
const NonblockingStatus = enum { blocking, nonblocking };
/// libc socketpair() except it defaults to:
/// - SOCK_CLOEXEC on Linux
/// - SO_NOSIGPIPE on macOS
///
/// On POSIX it otherwise makes it do O_CLOEXEC.
pub fn socketpair(domain: socketpair_t, socktype: socketpair_t, protocol: socketpair_t, nonblocking_status: NonblockingStatus) Maybe([2]bun.FileDescriptor) {
return socketpairImpl(domain, socktype, protocol, nonblocking_status, false);
}
/// We can't actually use SO_NOSIGPIPE for the stdout of a
/// subprocess we don't control because they have different
/// semantics.
///
/// For example, when running the shell script:
/// `grep hi src/js_parser/zig | echo hi`,
///
/// The `echo hi` command will terminate first and close its
/// end of the socketpair.
///
/// With SO_NOSIGPIPE, when `grep` continues and tries to write to
/// stdout, `ESIGPIPE` is returned and then `grep` handles this
/// and prints `grep: stdout: Broken pipe`
///
/// So the solution is to NOT set SO_NOGSIGPIPE in that scenario.
///
/// I think this only applies to stdout/stderr, not stdin. `read(...)`
/// and `recv(...)` do not return EPIPE as error codes.
pub fn socketpairForShell(domain: socketpair_t, socktype: socketpair_t, protocol: socketpair_t, nonblocking_status: NonblockingStatus) Maybe([2]bun.FileDescriptor) {
return socketpairImpl(domain, socktype, protocol, nonblocking_status, true);
}
pub const ShellSigpipeConfig = enum {
/// Only SO_NOSIGPIPE for the socket in the pair
/// that *we're* going to use, don't touch the one
/// we hand off to the subprocess
spawn,
/// off completely
pipeline,
};
pub fn socketpairImpl(domain: socketpair_t, socktype: socketpair_t, protocol: socketpair_t, nonblocking_status: NonblockingStatus, for_shell: bool) Maybe([2]bun.FileDescriptor) {
if (comptime !Environment.isPosix) @compileError("linux only!");
var fds_i: [2]syscall.fd_t = .{ 0, 0 };
if (comptime Environment.isLinux) {
while (true) {
const nonblock_flag: i32 = if (nonblocking_status == .nonblocking) linux.SOCK.NONBLOCK else 0;
const rc = std.os.linux.socketpair(domain, socktype | linux.SOCK.CLOEXEC | nonblock_flag, protocol, &fds_i);
if (Maybe([2]bun.FileDescriptor).errnoSys(rc, .socketpair)) |err| {
if (err.getErrno() == .INTR) continue;
log("socketpair() = {d} {s}", .{ err.err.errno, err.err.name() });
return err;
}
break;
}
} else {
while (true) {
const err = libc.socketpair(domain, socktype, protocol, &fds_i);
if (Maybe([2]bun.FileDescriptor).errnoSys(err, .socketpair)) |err2| {
if (err2.getErrno() == .INTR) continue;
log("socketpair() = {d} {s}", .{ err2.err.errno, err2.err.name() });
return err2;
}
break;
}
const err: ?sys.Error = err: {
// Set O_CLOEXEC first.
inline for (0..2) |i| {
switch (setCloseOnExec(.fromNative(fds_i[i]))) {
.err => |err| break :err err,
.result => {},
}
}
if (comptime Environment.isMac) {
if (for_shell) {
// see the comment on `socketpairForShell` for why we don't
// set SO_NOSIGPIPE here.
// macOS seems to default to around 8
// KB for the buffer size this is comically small. for
// processes normally, we do about 512 KB. for this we do
// 128 KB since you might have a lot of them at once.
const so_recvbuf: c_int = 1024 * 128;
const so_sendbuf: c_int = 1024 * 128;
_ = std.c.setsockopt(fds_i[1], std.posix.SOL.SOCKET, std.posix.SO.RCVBUF, &so_recvbuf, @sizeOf(c_int));
_ = std.c.setsockopt(fds_i[0], std.posix.SOL.SOCKET, std.posix.SO.SNDBUF, &so_sendbuf, @sizeOf(c_int));
} else {
inline for (0..2) |i| {
switch (setNoSigpipe(.fromNative(fds_i[i]))) {
.err => |err| break :err err,
else => {},
}
}
}
}
if (nonblocking_status == .nonblocking) {
inline for (0..2) |i| {
switch (setNonblocking(.fromNative(fds_i[i]))) {
.err => |err| break :err err,
.result => {},
}
}
}
break :err null;
};
// On any error after socketpair(), we need to close it.
if (err) |errr| {
inline for (0..2) |i| {
bun.FD.fromNative(fds_i[i]).close();
}
log("socketpair() = {d} {s}", .{ errr.errno, errr.name() });
return .{ .err = errr };
}
}
log("socketpair() = [{d} {d}]", .{ fds_i[0], fds_i[1] });
return Maybe([2]bun.FileDescriptor){ .result = .{ .fromNative(fds_i[0]), .fromNative(fds_i[1]) } };
}
pub fn munmap(memory: []align(page_size_min) const u8) Maybe(void) {
if (Maybe(void).errnoSys(syscall.munmap(memory.ptr, memory.len), .munmap)) |err| {
return err;
} else return .success;
}
pub fn memfd_create(name: [:0]const u8, flags: u32) Maybe(bun.FileDescriptor) {
if (comptime !Environment.isLinux) @compileError("linux only!");
const rc = std.os.linux.memfd_create(name, flags);
log("memfd_create({s}, {d}) = {d}", .{ name, flags, rc });
return Maybe(bun.FileDescriptor).errnoSys(rc, .memfd_create) orelse
.{ .result = .fromNative(@intCast(rc)) };
}
pub fn setPipeCapacityOnLinux(fd: bun.FileDescriptor, capacity: usize) Maybe(usize) {
if (comptime !Environment.isLinux) @compileError("Linux-only");
bun.assert(capacity > 0);
// In Linux versions before 2.6.11, the capacity of a
// pipe was the same as the system page size (e.g., 4096
// bytes on i386). Since Linux 2.6.11, the pipe
// capacity is 16 pages (i.e., 65,536 bytes in a system
// with a page size of 4096 bytes). Since Linux 2.6.35,
// the default pipe capacity is 16 pages, but the
// capacity can be queried and set using the
// fcntl(2) F_GETPIPE_SZ and F_SETPIPE_SZ operations.
// See fcntl(2) for more information.
//:# define F_SETPIPE_SZ 1031 /* Set pipe page size array.
const F_SETPIPE_SZ = 1031;
const F_GETPIPE_SZ = 1032;
// We don't use glibc here
// It didn't work. Always returned 0.
const pipe_len = switch (fcntl(fd, F_GETPIPE_SZ, 0)) {
.result => |result| result,
.err => |err| return err,
};
if (pipe_len == 0) return Maybe(usize){ .result = 0 };
if (pipe_len >= capacity) return Maybe(usize){ .result = pipe_len };
const new_pipe_len = switch (fcntl(fd, F_SETPIPE_SZ, capacity)) {
.result => |result| result,
.err => |err| return err,
};
return Maybe(usize){ .result = new_pipe_len };
}
pub fn getMaxPipeSizeOnLinux() usize {
return @as(
usize,
@intCast(bun.once(struct {
fn once() c_int {
const strings = bun.strings;
const default_out_size = 512 * 1024;
const pipe_max_size_fd = switch (open("/proc/sys/fs/pipe-max-size", bun.O.RDONLY, 0)) {
.result => |fd2| fd2,
.err => |err| {
log("Failed to open /proc/sys/fs/pipe-max-size: {d}\n", .{err.errno});
return default_out_size;
},
};
defer pipe_max_size_fd.close();
var max_pipe_size_buf: [128]u8 = undefined;
const max_pipe_size = switch (read(pipe_max_size_fd, max_pipe_size_buf[0..])) {
.result => |bytes_read| std.fmt.parseInt(i64, strings.trim(max_pipe_size_buf[0..bytes_read], "\n"), 10) catch |err| {
log("Failed to parse /proc/sys/fs/pipe-max-size: {s}\n", .{@errorName(err)});
return default_out_size;
},
.err => |err| {
log("Failed to read /proc/sys/fs/pipe-max-size: {d}\n", .{err.errno});
return default_out_size;
},
};
// we set the absolute max to 8 MB because honestly that's a huge pipe
// my current linux machine only goes up to 1 MB, so that's very unlikely to be hit
return @min(@as(c_int, @truncate(max_pipe_size -| 32)), 1024 * 1024 * 8);
}
}.once, c_int)),
);
}
pub const WindowsFileAttributes = packed struct(windows.DWORD) {
//1 0x00000001 FILE_ATTRIBUTE_READONLY
is_readonly: bool,
//2 0x00000002 FILE_ATTRIBUTE_HIDDEN
is_hidden: bool,
//4 0x00000004 FILE_ATTRIBUTE_SYSTEM
is_system: bool,
//8
_03: bool,
//1 0x00000010 FILE_ATTRIBUTE_DIRECTORY
is_directory: bool,
//2 0x00000020 FILE_ATTRIBUTE_ARCHIVE
is_archive: bool,
//4 0x00000040 FILE_ATTRIBUTE_DEVICE
is_device: bool,
//8 0x00000080 FILE_ATTRIBUTE_NORMAL
is_normal: bool,
//1 0x00000100 FILE_ATTRIBUTE_TEMPORARY
is_temporary: bool,
//2 0x00000200 FILE_ATTRIBUTE_SPARSE_FILE
is_sparse_file: bool,
//4 0x00000400 FILE_ATTRIBUTE_REPARSE_POINT
is_reparse_point: bool,
//8 0x00000800 FILE_ATTRIBUTE_COMPRESSED
is_compressed: bool,
//1 0x00001000 FILE_ATTRIBUTE_OFFLINE
is_offline: bool,
//2 0x00002000 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
is_not_content_indexed: bool,
//4 0x00004000 FILE_ATTRIBUTE_ENCRYPTED
is_encrypted: bool,
//8 0x00008000 FILE_ATTRIBUTE_INTEGRITY_STREAM
is_integrity_stream: bool,
//1 0x00010000 FILE_ATTRIBUTE_VIRTUAL
is_virtual: bool,
//2 0x00020000 FILE_ATTRIBUTE_NO_SCRUB_DATA
is_no_scrub_data: bool,
//4 0x00040000 FILE_ATTRIBUTE_EA
is_ea: bool,
//8 0x00080000 FILE_ATTRIBUTE_PINNED
is_pinned: bool,
//1 0x00100000 FILE_ATTRIBUTE_UNPINNED
is_unpinned: bool,
//2
_21: bool,
//4 0x00040000 FILE_ATTRIBUTE_RECALL_ON_OPEN
is_recall_on_open: bool,
//8
_23: bool,
//1
_24: bool,
//2
_25: bool,
//4 0x00400000 FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS
is_recall_on_data_access: bool,
//
__: u5,
};
pub fn getFileAttributes(path: anytype) ?WindowsFileAttributes {
if (comptime !Environment.isWindows) @compileError("Windows only");
const T = std.meta.Child(@TypeOf(path));
if (T == u16) {
// Win32 API does file path normalization, so we do not need the valid path assertion here.
const dword = kernel32.GetFileAttributesW(path.ptr);
if (comptime Environment.isDebug) {
log("GetFileAttributesW({f}) = {d}", .{ bun.fmt.utf16(path), dword });
}
if (dword == windows.INVALID_FILE_ATTRIBUTES) {
return null;
}
const attributes: WindowsFileAttributes = @bitCast(dword);
return attributes;
} else {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
const path_to_use = bun.strings.toKernel32Path(wbuf, path);
return getFileAttributes(path_to_use);
}
}
pub fn existsOSPath(path: bun.OSPathSliceZ, file_only: bool) bool {
if (Environment.isPosix) {
// access() may not work correctly on NFS file systems with UID
// mapping enabled, because UID mapping is done on the server and
// hidden from the client, which checks permissions. Similar
// problems can occur to FUSE mounts.
return syscall.access(path, 0) == 0;
}
if (Environment.isWindows) {
const attributes = getFileAttributes(path) orelse return false;
if (file_only and attributes.is_directory) {
return false;
}
if (attributes.is_reparse_point) {
// Check if the underlying file exists by opening it.
const rc = std.os.windows.kernel32.CreateFileW(
path,
0,
0,
null,
w.OPEN_EXISTING,
w.FILE_FLAG_BACKUP_SEMANTICS,
null,
);
if (rc == w.INVALID_HANDLE_VALUE) return false;
defer _ = bun.windows.CloseHandle(rc);
return true;
}
return true;
}
@compileError("TODO: existsOSPath");
}
pub fn exists(path: []const u8) bool {
if (comptime Environment.isPosix) {
return syscall.access(&(std.posix.toPosixPath(path) catch return false), 0) == 0;
}
if (comptime Environment.isWindows) {
return getFileAttributes(path) != null;
}
@compileError("TODO: existsOSPath");
}
pub fn existsZ(path: [:0]const u8) bool {
if (comptime Environment.isPosix) {
return syscall.access(path, 0) == 0;
}
if (comptime Environment.isWindows) {
return getFileAttributes(path) != null;
}
}
pub fn faccessat(dir_fd: bun.FileDescriptor, subpath: anytype) bun.sys.Maybe(bool) {
const has_sentinel = std.meta.sentinel(@TypeOf(subpath)) != null;
if (comptime !has_sentinel) {
const path = std.posix.toPosixPath(subpath) catch return bun.sys.Maybe(bool){ .err = Error.fromCode(.NAMETOOLONG, .access) };
return faccessat(dir_fd, &path);
}
if (comptime Environment.isLinux) {
// avoid loading the libc symbol for this to reduce chances of GLIBC minimum version requirements
const rc = linux.faccessat(dir_fd.cast(), subpath, linux.F_OK, 0);
syslog("faccessat({f}, {f}, O_RDONLY, 0) = {d}", .{ dir_fd, bun.fmt.fmtOSPath(subpath, .{}), if (rc == 0) 0 else @intFromEnum(getErrno(rc)) });
if (rc == 0) {
return bun.sys.Maybe(bool){ .result = true };
}
return bun.sys.Maybe(bool){ .result = false };
}
// on other platforms use faccessat from libc
const rc = std.c.faccessat(dir_fd.cast(), subpath, std.posix.F_OK, 0);
syslog("faccessat({f}, {f}, O_RDONLY, 0) = {d}", .{ dir_fd, bun.fmt.fmtOSPath(subpath, .{}), if (rc == 0) 0 else @intFromEnum(getErrno(rc)) });
if (rc == 0) {
return bun.sys.Maybe(bool){ .result = true };
}
return bun.sys.Maybe(bool){ .result = false };
}
pub fn directoryExistsAt(dir: bun.FileDescriptor, subpath: anytype) bun.sys.Maybe(bool) {
return switch (existsAtType(dir, subpath)) {
//
.err => |err| if (err.getErrno() == .NOENT)
.{ .result = false }
else
.{ .err = err },
.result => |result| .{ .result = result == .directory },
};
}
pub fn futimens(fd: bun.FileDescriptor, atime: jsc.Node.TimeLike, mtime: jsc.Node.TimeLike) Maybe(void) {
if (comptime Environment.isWindows) @compileError("TODO: futimes");
while (true) {
const rc = syscall.futimens(fd.cast(), &[2]syscall.timespec{
.{ .sec = @intCast(atime.sec), .nsec = atime.nsec },
.{ .sec = @intCast(mtime.sec), .nsec = mtime.nsec },
});
log("futimens({f}, accessed=({d}, {d}), modified=({d}, {d})) = {d}", .{ fd, atime.sec, atime.nsec, mtime.sec, mtime.nsec, rc });
if (rc == 0) {
return .success;
}
switch (getErrno(rc)) {
.INTR => continue,
else => return Maybe(void).errnoSysFd(rc, .futimens, fd).?,
}
}
unreachable;
}
fn utimensWithFlags(path: bun.OSPathSliceZ, atime: jsc.Node.TimeLike, mtime: jsc.Node.TimeLike, flags: u32) Maybe(void) {
if (comptime Environment.isWindows) @compileError("TODO: utimens");
while (true) {
var times: [2]syscall.timespec = .{
.{ .sec = @intCast(atime.sec), .nsec = atime.nsec },
.{ .sec = @intCast(mtime.sec), .nsec = mtime.nsec },
};
const rc = syscall.utimensat(
std.fs.cwd().fd,
path,
// this var should be a const, the zig type definition is wrong.
&times,
flags,
);
log("utimensat({d}, atime=({d}, {d}), mtime=({d}, {d})) = {d}", .{ std.fs.cwd().fd, atime.sec, atime.nsec, mtime.sec, mtime.nsec, rc });
if (rc == 0) {
return .success;
}
switch (getErrno(rc)) {
.INTR => continue,
else => return Maybe(void).errnoSysP(rc, .utimensat, path).?,
}
}
unreachable;
}
pub fn getFcntlFlags(fd: bun.FileDescriptor) Maybe(fnctl_int) {
return switch (fcntl(
fd,
std.posix.F.GETFL,
0,
)) {
.result => |f| .{ .result = f },
.err => |err| .{ .err = err },
};
}
pub fn utimens(path: bun.OSPathSliceZ, atime: jsc.Node.TimeLike, mtime: jsc.Node.TimeLike) Maybe(void) {
return utimensWithFlags(path, atime, mtime, 0);
}
pub fn setNonblocking(fd: bun.FileDescriptor) Maybe(void) {
return updateNonblocking(fd, true);
}
pub fn updateNonblocking(fd: bun.FileDescriptor, nonblocking: bool) Maybe(void) {
const current_flags: i32 = switch (getFcntlFlags(fd)) {
.result => |f| @intCast(f),
.err => |err| return .{ .err = err },
};
const new_flags: i32 = if (nonblocking) current_flags | @as(i32, bun.O.NONBLOCK) else current_flags & ~@as(i32, bun.O.NONBLOCK);
if (new_flags != current_flags) {
switch (fcntl(fd, std.posix.F.SETFL, @as(fnctl_int, @intCast(new_flags)))) {
.err => |err| return .{ .err = err },
.result => {},
}
}
return .success;
}
pub const ExistsAtType = enum {
file,
directory,
};
pub fn existsAtType(fd: bun.FileDescriptor, subpath: anytype) Maybe(ExistsAtType) {
if (comptime Environment.isWindows) {
const wbuf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(wbuf);
var path = if (std.meta.Child(@TypeOf(subpath)) == u16)
bun.strings.toNTPath16(wbuf, subpath)
else
bun.strings.toNTPath(wbuf, subpath);
// trim leading .\
// NtQueryAttributesFile expects relative paths to not start with .\
if (path.len > 2 and path[0] == '.' and path[1] == '\\') {
path = path[2..];
}
const path_len_bytes: u16 = @truncate(path.len * 2);
var nt_name = w.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(path.ptr),
};
var attr = w.OBJECT_ATTRIBUTES{
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(path))
null
else if (fd == bun.invalid_fd)
std.fs.cwd().fd
else
fd.cast(),
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var basic_info: w.FILE_BASIC_INFORMATION = undefined;
const rc = ntdll.NtQueryAttributesFile(&attr, &basic_info);
if (bun.sys.Maybe(bool).errnoSys(rc, .access)) |err| {
syslog("NtQueryAttributesFile({f}, O_RDONLY, 0) = {f}", .{ bun.fmt.fmtOSPath(path, .{}), err });
return .{ .err = err.err };
}
const is_regular_file = basic_info.FileAttributes != c.INVALID_FILE_ATTRIBUTES and
// from libuv: directories cannot be read-only
// https://github.com/libuv/libuv/blob/eb5af8e3c0ea19a6b0196d5db3212dae1785739b/src/win/fs.c#L2144-L2146
(basic_info.FileAttributes & c.FILE_ATTRIBUTE_DIRECTORY == 0 or
basic_info.FileAttributes & c.FILE_ATTRIBUTE_READONLY == 0);
const is_dir = basic_info.FileAttributes != c.INVALID_FILE_ATTRIBUTES and
basic_info.FileAttributes & c.FILE_ATTRIBUTE_DIRECTORY != 0 and
basic_info.FileAttributes & c.FILE_ATTRIBUTE_READONLY == 0;
return if (is_dir) {
syslog("NtQueryAttributesFile({f}, O_RDONLY, 0) = directory", .{bun.fmt.fmtOSPath(path, .{})});
return .{ .result = .directory };
} else if (is_regular_file) {
syslog("NtQueryAttributesFile({f}, O_RDONLY, 0) = file", .{bun.fmt.fmtOSPath(path, .{})});
return .{ .result = .file };
} else {
syslog("NtQueryAttributesFile({f}, O_RDONLY, 0) = {d}", .{ bun.fmt.fmtOSPath(path, .{}), basic_info.FileAttributes });
return .{ .err = Error.fromCode(.UNKNOWN, .access) };
};
}
if (std.meta.sentinel(@TypeOf(subpath)) == null) {
const path_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(path_buf);
@memcpy(path_buf[0..subpath.len], subpath);
path_buf[subpath.len] = 0;
const slice: [:0]const u8 = @ptrCast(path_buf);
return existsAtType(fd, slice);
}
return switch (fstatat(fd, subpath)) {
.err => |err| .{ .err = err },
.result => |result| if (S.ISDIR(result.mode)) .{ .result = .directory } else .{ .result = .file },
};
}
pub fn existsAt(fd: bun.FileDescriptor, subpath: [:0]const u8) bool {
if (comptime Environment.isPosix) {
return switch (faccessat(fd, subpath)) {
.err => false,
.result => |r| r,
};
}
if (comptime Environment.isWindows) {
if (existsAtType(fd, subpath).asValue()) |exists_at_type| {
return exists_at_type == .file;
}
return false;
}
@compileError("TODO: existsAtOSPath");
}
pub extern "c" fn is_executable_file(path: [*:0]const u8) bool;
pub fn isExecutableFileOSPath(path: bun.OSPathSliceZ) bool {
if (comptime Environment.isPosix) {
return is_executable_file(path);
}
if (comptime Environment.isWindows) {
// Rationale: `GetBinaryTypeW` does not work on .cmd files.
// SaferiIsExecutableFileType works on .cmd files.
// The following file name extensions are examples of executable file types. This is not a complete list.
// .bat
// .cmd
// .com
// .exe
// .js
// .lnk
// .pif
// .pl
// .shs
// .url
// .vbs
// The security policy Microsoft Management Console (MMC) snap-in (Secpol.msc) controls which extensions are considered executable file types.
// we pass false to include .exe files (see https://learn.microsoft.com/en-us/windows/win32/api/winsafer/nf-winsafer-saferiisexecutablefiletype)
return bun.windows.SaferiIsExecutableFileType(path, w.FALSE) != w.FALSE;
}
@compileError("TODO: isExecutablePath");
}
pub fn isExecutableFilePath(path: anytype) bool {
const Type = @TypeOf(path);
if (comptime Environment.isPosix) {
switch (Type) {
*[*:0]const u8, *[*:0]u8, [*:0]const u8, [*:0]u8 => return is_executable_file(path),
[:0]const u8, [:0]u8 => return is_executable_file(path.ptr),
[]const u8, []u8 => return is_executable_file(
&(std.posix.toPosixPath(path) catch return false),
),
else => @compileError("TODO: isExecutableFilePath"),
}
}
if (comptime Environment.isWindows) {
var buf: [(bun.MAX_PATH_BYTES / 2) + 1]u16 = undefined;
return isExecutableFileOSPath(bun.strings.toWPath(&buf, path));
}
@compileError("TODO: isExecutablePath");
}
pub fn setFileOffset(fd: bun.FileDescriptor, offset: usize) Maybe(void) {
if (comptime Environment.isLinux) {
return Maybe(void).errnoSysFd(
linux.lseek(fd.cast(), @intCast(offset), posix.SEEK.SET),
.lseek,
fd,
) orelse .success;
}
if (comptime Environment.isMac) {
return Maybe(void).errnoSysFd(
std.c.lseek(fd.cast(), @intCast(offset), posix.SEEK.SET),
.lseek,
fd,
) orelse .success;
}
if (comptime Environment.isWindows) {
const offset_high: u64 = @as(u32, @intCast(offset >> 32));
const offset_low: u64 = @as(u32, @intCast(offset & 0xFFFFFFFF));
var plarge_integer: i64 = @bitCast(offset_high);
const rc = kernel32.SetFilePointerEx(
fd.cast(),
@as(windows.LARGE_INTEGER, @bitCast(offset_low)),
&plarge_integer,
windows.FILE_BEGIN,
);
if (rc == windows.FALSE) {
return Maybe(void).errnoSysFd(0, .lseek, fd) orelse .success;
}
return .success;
}
}
pub fn setFileOffsetToEndWindows(fd: bun.FileDescriptor) Maybe(usize) {
if (comptime Environment.isWindows) {
var new_ptr: std.os.windows.LARGE_INTEGER = undefined;
const rc = kernel32.SetFilePointerEx(fd.cast(), 0, &new_ptr, windows.FILE_END);
if (rc == windows.FALSE) {
return Maybe(usize).errnoSysFd(0, .lseek, fd) orelse Maybe(usize){ .result = 0 };
}
return Maybe(usize){ .result = @intCast(new_ptr) };
}
@compileError("Not Implemented");
}
extern fn Bun__disableSOLinger(fd: if (Environment.isWindows) windows.HANDLE else i32) void;
pub fn disableLinger(fd: bun.FileDescriptor) void {
Bun__disableSOLinger(fd.cast());
}
pub fn pipe() Maybe([2]bun.FileDescriptor) {
if (comptime Environment.isWindows) {
const uv = bun.windows.libuv;
var out: [2]bun.FileDescriptor = undefined;
var fds: [2]uv.uv_file = undefined;
if (uv.uv_pipe(&fds, 0, 0).errEnum()) |e| {
const err = Error.fromCode(e, .pipe);
log("pipe() = {f}", .{err});
return .{ .err = err };
}
out[0] = .fromUV(fds[0]);
out[1] = .fromUV(fds[1]);
log("pipe() = [{f}, {f}]", .{ out[0], out[1] });
return .{ .result = out };
}
var fds: [2]i32 = undefined;
const rc = syscall.pipe(&fds);
if (Maybe([2]bun.FileDescriptor).errnoSys(rc, .pipe)) |err| {
log("pipe() = {f}", .{err});
return err;
}
log("pipe() = [{d}, {d}]", .{ fds[0], fds[1] });
return .{ .result = .{ .fromNative(fds[0]), .fromNative(fds[1]) } };
}
pub fn openNullDevice() Maybe(bun.FileDescriptor) {
if (comptime Environment.isWindows) {
return sys_uv.open("nul", 0, 0);
}
return open("/dev/null", bun.O.RDWR, 0);
}
pub fn dupWithFlags(fd: bun.FileDescriptor, _: i32) Maybe(bun.FileDescriptor) {
if (comptime Environment.isWindows) {
var target: windows.HANDLE = undefined;
const process = kernel32.GetCurrentProcess();
const out = kernel32.DuplicateHandle(
process,
fd.cast(),
process,
&target,
0,
w.TRUE,
w.DUPLICATE_SAME_ACCESS,
);
if (out == 0) {
if (Maybe(bun.FileDescriptor).errnoSysFd(0, .dup, fd)) |err| {
log("dup({f}) = {f}", .{ fd, err });
return err;
}
}
const duplicated_fd = bun.FD.fromNative(target);
log("dup({f}) = {f}", .{ fd, duplicated_fd });
return Maybe(bun.FileDescriptor){ .result = duplicated_fd };
}
const ArgType = if (comptime Environment.isLinux) usize else c_int;
const out = switch (fcntl(fd, @as(i32, bun.c.F_DUPFD_CLOEXEC), @as(ArgType, 0))) {
.result => |result| result,
.err => |err| {
log("dup({f}) = {f}", .{ fd, err });
return .{ .err = err };
},
};
log("dup({f}) = {f}", .{ fd, bun.FileDescriptor.fromNative(@intCast(out)) });
return .initResult(.fromNative(@intCast(out)));
}
pub fn dup(fd: bun.FileDescriptor) Maybe(bun.FileDescriptor) {
return dupWithFlags(fd, 0);
}
pub fn link(comptime T: type, src: [:0]const T, dest: [:0]const T) Maybe(void) {
if (comptime Environment.isWindows) {
if (T == u8) {
return sys_uv.link(src, dest);
}
if (bun.windows.CreateHardLinkW(dest, src, null) == 0) {
return Maybe(void).errno(bun.windows.getLastErrno(), .link);
}
log("CreateHardLinkW({f}, {f}) = 0", .{
bun.fmt.fmtPath(T, dest, .{}),
bun.fmt.fmtPath(T, src, .{}),
});
return .success;
}
if (T == u16) {
@compileError("unexpected path type");
}
const ret = std.c.link(src, dest);
if (Maybe(void).errnoSysPD(ret, .link, src, dest)) |err| {
log("link({s}, {s}) = {s}", .{ src, dest, @tagName(err.getErrno()) });
return err;
}
log("link({s}, {s}) = 0", .{ src, dest });
return .success;
}
pub fn linkat(src: bun.FileDescriptor, src_path: []const u8, dest: bun.FileDescriptor, dest_path: []const u8) Maybe(void) {
return linkatZ(
src,
&(std.posix.toPosixPath(src_path) catch return .{
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .link,
},
}),
dest,
&(std.posix.toPosixPath(dest_path) catch return .{
.err = .{
.errno = @intFromEnum(E.NOMEM),
.syscall = .link,
},
}),
);
}
pub fn linkatZ(src: FD, src_path: [:0]const u8, dest: FD, dest_path: [:0]const u8) Maybe(void) {
const ret = std.c.linkat(src.cast(), src_path, dest.cast(), dest_path, 0);
if (Maybe(void).errnoSysP(ret, .link, src_path)) |err| {
log("linkat({f}, {s}, {f}, {s}) = {s}", .{ src, src_path, dest, dest_path, @tagName(err.getErrno()) });
return err;
}
log("linkat({f}, {s}, {f}, {s}) = 0", .{ src, src_path, dest, dest_path });
return .success;
}
pub fn linkatTmpfile(tmpfd: bun.FileDescriptor, dirfd: bun.FileDescriptor, name: [:0]const u8) Maybe(void) {
if (comptime !Environment.isLinux) {
@compileError("Linux only.");
}
const CAP_DAC_READ_SEARCH = struct {
pub var status = std.atomic.Value(i32).init(0);
};
while (true) {
// This is racy but it's fine if we call linkat() with an empty path multiple times.
const current_status = CAP_DAC_READ_SEARCH.status.load(.monotonic);
const rc = if (current_status != -1) std.os.linux.linkat(
tmpfd.cast(),
"",
dirfd.cast(),
name,
posix.AT.EMPTY_PATH,
) else brk: {
//
// snprintf(path, PATH_MAX, "/proc/self/fd/%d", fd);
// linkat(AT_FDCWD, path, AT_FDCWD, "/path/for/file",
// AT_SYMLINK_FOLLOW);
//
var procfs_buf: ["/proc/self/fd/-2147483648".len + 1:0]u8 = undefined;
const path = std.fmt.bufPrintZ(&procfs_buf, "/proc/self/fd/{d}", .{tmpfd.cast()}) catch unreachable;
break :brk std.os.linux.linkat(
posix.AT.FDCWD,
path,
dirfd.cast(),
name,
posix.AT.SYMLINK_FOLLOW,
);
};
if (Maybe(void).errnoSysFd(rc, .link, tmpfd)) |err| {
switch (err.getErrno()) {
.INTR => continue,
.ISDIR, .NOENT, .OPNOTSUPP, .PERM, .INVAL => {
// CAP_DAC_READ_SEARCH is required to linkat with an empty path.
if (current_status == 0) {
CAP_DAC_READ_SEARCH.status.store(-1, .monotonic);
continue;
}
},
else => {},
}
return err;
}
if (current_status == 0) {
CAP_DAC_READ_SEARCH.status.store(1, .monotonic);
}
return .success;
}
}
/// c-bindings.cpp
extern "c" fn sys_preadv2(
fd: c_int,
iov: [*]const std.posix.iovec,
iovcnt: c_int,
offset: std.posix.off_t,
flags: c_uint,
) isize;
/// On Linux, this `preadv2(2)` to attempt to read a blocking file descriptor without blocking.
///
/// On other platforms, this is just a wrapper around `read(2)`.
pub fn readNonblocking(fd: bun.FileDescriptor, buf: []u8) Maybe(usize) {
if (Environment.isLinux) {
while (bun.linux.RWFFlagSupport.isMaybeSupported()) {
const iovec = [1]std.posix.iovec{.{
.base = buf.ptr,
.len = buf.len,
}};
var debug_timer = bun.Output.DebugTimer.start();
// Note that there is a bug on Linux Kernel 5
const rc = sys_preadv2(fd.native(), &iovec, 1, -1, std.os.linux.RWF.NOWAIT);
if (comptime Environment.isDebug) {
log("preadv2({f}, {d}) = {d} ({f})", .{ fd, buf.len, rc, debug_timer });
if (debug_timer.timer.read() > std.time.ns_per_ms) {
bun.Output.debugWarn("preadv2({f}, {d}) blocked for {f}", .{ fd, buf.len, debug_timer });
}
}
if (Maybe(usize).errnoSysFd(rc, .read, fd)) |err| {
switch (err.getErrno()) {
.OPNOTSUPP, .NOSYS => {
bun.linux.RWFFlagSupport.disable();
switch (bun.isReadable(fd)) {
.hup, .ready => return read(fd, buf),
else => return .{ .err = Error.retry },
}
},
.INTR => continue,
else => return err,
}
}
return .{ .result = @as(usize, @intCast(rc)) };
}
}
return read(fd, buf);
}
/// c-bindings.cpp
pub extern "c" fn sys_pwritev2(
fd: c_int,
iov: [*]const std.posix.iovec_const,
iovcnt: c_int,
offset: std.posix.off_t,
flags: c_uint,
) isize;
/// On Linux, this `pwritev(2)` to attempt to read a blocking file descriptor without blocking.
///
/// On other platforms, this is just a wrapper around `read(2)`.
pub fn writeNonblocking(fd: bun.FileDescriptor, buf: []const u8) Maybe(usize) {
if (Environment.isLinux) {
while (bun.linux.RWFFlagSupport.isMaybeSupported()) {
const iovec = [1]std.posix.iovec_const{.{
.base = buf.ptr,
.len = buf.len,
}};
var debug_timer = bun.Output.DebugTimer.start();
const rc = sys_pwritev2(fd.native(), &iovec, 1, -1, std.os.linux.RWF.NOWAIT);
if (comptime Environment.isDebug) {
log("pwritev2({f}, {d}) = {d} ({f})", .{ fd, buf.len, rc, debug_timer });
if (debug_timer.timer.read() > std.time.ns_per_ms) {
bun.Output.debugWarn("pwritev2({f}, {d}) blocked for {f}", .{ fd, buf.len, debug_timer });
}
}
if (Maybe(usize).errnoSysFd(rc, .write, fd)) |err| {
switch (err.getErrno()) {
.OPNOTSUPP, .NOSYS => {
bun.linux.RWFFlagSupport.disable();
switch (bun.isWritable(fd)) {
.hup, .ready => return write(fd, buf),
else => return .{ .err = Error.retry },
}
},
.INTR => continue,
else => return err,
}
}
return .{ .result = @as(usize, @intCast(rc)) };
}
}
return write(fd, buf);
}
pub fn getFileSize(fd: bun.FileDescriptor) Maybe(usize) {
if (Environment.isWindows) {
var size: windows.LARGE_INTEGER = undefined;
if (windows.kernel32.GetFileSizeEx(fd.cast(), &size) == windows.FALSE) {
const err = Error.fromCode(windows.getLastErrno(), .fstat);
log("GetFileSizeEx({f}) = {s}", .{ fd, err.name() });
return .{ .err = err };
}
log("GetFileSizeEx({f}) = {d}", .{ fd, size });
return .{ .result = @intCast(@max(size, 0)) };
}
switch (fstat(fd)) {
.result => |*stat_| {
return .{ .result = @intCast(@max(stat_.size, 0)) };
},
.err => |err| {
return .{ .err = err };
},
}
}
pub fn isPollable(mode: mode_t) bool {
if (comptime bun.Environment.isWindows) return false;
return posix.S.ISFIFO(mode) or posix.S.ISSOCK(mode);
}
pub const Dir = @import("./dir.zig");
const FILE_SHARE = w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE;
pub const libuv_error_map = @import("./sys/libuv_error_map.zig").libuv_error_map;
pub const coreutils_error_map = @import("./sys/coreutils_error_map.zig").coreutils_error_map;
extern fn getRSS(rss: *usize) c_int;
pub fn selfProcessMemoryUsage() ?usize {
var rss: usize = undefined;
if (getRSS(&rss) != 0) {
return null;
}
return rss;
}
export fn Bun__errnoName(err: c_int) ?[*:0]const u8 {
return @tagName(SystemErrno.init(err) orelse return null);
}
/// Small "fire and forget" wrapper around unlink for c usage that handles EINTR, windows path conversion, etc.
export fn Bun__unlink(ptr: [*:0]const u8, len: usize) void {
_ = unlink(ptr[0..len :0]);
}
// TODO: this is wrong on Windows
pub fn lstat_absolute(path: [:0]const u8) !Stat {
if (builtin.os.tag == .windows) {
@compileError("Not implemented yet, consider using lstat()");
}
var st = std.mem.zeroes(libc_stat);
switch (std.posix.errno(workaround_symbols.lstat(path.ptr, &st))) {
.SUCCESS => {},
.NOENT => return error.FileNotFound,
// .EINVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
else => |err| return posix.unexpectedErrno(err),
}
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
const Kind = std.fs.File.Kind;
return Stat{
.inode = st.ino,
.size = @as(u64, @bitCast(st.size)),
.mode = st.mode,
.kind = switch (builtin.os.tag) {
.wasi => switch (st.filetype) {
posix.FILETYPE_BLOCK_DEVICE => Kind.block_device,
posix.FILETYPE_CHARACTER_DEVICE => Kind.character_device,
posix.FILETYPE_DIRECTORY => Kind.directory,
posix.FILETYPE_SYMBOLIC_LINK => Kind.sym_link,
posix.FILETYPE_REGULAR_FILE => Kind.file,
posix.FILETYPE_SOCKET_STREAM, posix.FILETYPE_SOCKET_DGRAM => Kind.unix_domain_socket,
else => Kind.unknown,
},
else => switch (st.mode & posix.S.IFMT) {
posix.S.IFBLK => Kind.block_device,
posix.S.IFCHR => Kind.character_device,
posix.S.IFDIR => Kind.directory,
posix.S.IFIFO => Kind.named_pipe,
posix.S.IFLNK => Kind.sym_link,
posix.S.IFREG => Kind.file,
posix.S.IFSOCK => Kind.unix_domain_socket,
else => Kind.unknown,
},
},
.atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
.mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
.ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
};
}
// renameatZ fails when renaming across mount points
// we assume that this is relatively uncommon
pub fn moveFileZ(from_dir: bun.FileDescriptor, filename: [:0]const u8, to_dir: bun.FileDescriptor, destination: [:0]const u8) !void {
switch (renameatConcurrentlyWithoutFallback(from_dir, filename, to_dir, destination)) {
.err => |err| {
// allow over-writing an empty directory
if (err.getErrno() == .ISDIR) {
_ = rmdirat(to_dir, destination.ptr);
try renameat(from_dir, filename, to_dir, destination).unwrap();
return;
}
if (err.getErrno() == .XDEV) {
try moveFileZSlow(from_dir, filename, to_dir, destination);
} else {
return bun.errnoToZigErr(err.errno);
}
},
.result => {},
}
}
pub fn moveFileZWithHandle(from_handle: bun.FileDescriptor, from_dir: bun.FileDescriptor, filename: [:0]const u8, to_dir: bun.FileDescriptor, destination: [:0]const u8) !void {
switch (renameat(from_dir, filename, to_dir, destination)) {
.err => |err| {
// allow over-writing an empty directory
if (err.getErrno() == .ISDIR) {
_ = rmdirat(to_dir, destination.ptr);
try (renameat(from_dir, filename, to_dir, destination).unwrap());
return;
}
if (err.getErrno() == .XDEV) {
try copyFileZSlowWithHandle(from_handle, to_dir, destination).unwrap();
_ = unlinkat(from_dir, filename);
return;
}
return bun.errnoToZigErr(err.errno);
},
.result => {},
}
}
// On Linux, this will be fast because sendfile() supports copying between two file descriptors on disk
// macOS & BSDs will be slow because
pub fn moveFileZSlow(from_dir: bun.FileDescriptor, filename: [:0]const u8, to_dir: bun.FileDescriptor, destination: [:0]const u8) !void {
return try moveFileZSlowMaybe(from_dir, filename, to_dir, destination).unwrap();
}
pub fn moveFileZSlowMaybe(from_dir: bun.FileDescriptor, filename: [:0]const u8, to_dir: bun.FileDescriptor, destination: [:0]const u8) Maybe(void) {
const in_handle = switch (openat(from_dir, filename, bun.O.RDONLY | bun.O.CLOEXEC, if (Environment.isWindows) 0 else 0o644)) {
.result => |f| f,
.err => |e| return .{ .err = e },
};
defer in_handle.close();
_ = from_dir.unlinkat(filename);
return copyFileZSlowWithHandle(in_handle, to_dir, destination);
}
pub fn copyFileZSlowWithHandle(in_handle: bun.FileDescriptor, to_dir: bun.FileDescriptor, destination: [:0]const u8) Maybe(void) {
if (comptime Environment.isWindows) {
var buf0: bun.WPathBuffer = undefined;
var buf1: bun.WPathBuffer = undefined;
const dest = switch (normalizePathWindows(u8, to_dir, destination, &buf0, .{})) {
.result => |x| x,
.err => |e| return .{ .err = e },
};
const src_len = bun.windows.GetFinalPathNameByHandleW(in_handle.cast(), &buf1, buf1.len, 0);
if (src_len == 0) {
return Maybe(void).errno(bun.sys.E.BUSY, .GetFinalPathNameByHandle);
} else if (src_len >= buf1.len) {
return Maybe(void).errno(bun.sys.E.NAMETOOLONG, .GetFinalPathNameByHandle);
}
const src = buf1[0..src_len :0];
return bun.copyFile(src, dest);
} else {
const stat_ = switch (fstat(in_handle)) {
.result => |s| s,
.err => |e| return .{ .err = e },
};
// Attempt to delete incase it already existed.
// This fixes ETXTBUSY on Linux
_ = unlinkat(to_dir, destination);
const out_handle = switch (openat(
to_dir,
destination,
bun.O.WRONLY | bun.O.CREAT | bun.O.CLOEXEC | bun.O.TRUNC,
if (comptime Environment.isPosix) 0o644 else 0,
)) {
.result => |fd| fd,
.err => |e| return .{ .err = e },
};
defer out_handle.close();
if (comptime Environment.isLinux) {
_ = std.os.linux.fallocate(out_handle.cast(), 0, 0, @intCast(stat_.size));
}
switch (bun.copyFile(in_handle, out_handle)) {
.err => |e| return .{ .err = e },
.result => {},
}
if (comptime Environment.isPosix) {
_ = bun.c.fchmod(out_handle.cast(), stat_.mode);
_ = bun.c.fchown(out_handle.cast(), stat_.uid, stat_.gid);
}
return .success;
}
}
pub fn kindFromMode(mode: mode_t) std.fs.File.Kind {
return switch (mode & bun.S.IFMT) {
bun.S.IFBLK => std.fs.File.Kind.block_device,
bun.S.IFCHR => std.fs.File.Kind.character_device,
bun.S.IFDIR => std.fs.File.Kind.directory,
bun.S.IFIFO => std.fs.File.Kind.named_pipe,
bun.S.IFLNK => std.fs.File.Kind.sym_link,
bun.S.IFREG => std.fs.File.Kind.file,
bun.S.IFSOCK => std.fs.File.Kind.unix_domain_socket,
else => .unknown,
};
}
pub fn getSelfExeSharedLibPaths(allocator: std.mem.Allocator) error{OutOfMemory}![][:0]u8 {
const List = std.array_list.Managed([:0]u8);
switch (builtin.os.tag) {
.linux,
.freebsd,
.netbsd,
.dragonfly,
.openbsd,
.solaris,
=> {
var paths = List.init(allocator);
errdefer {
const slice = paths.toOwnedSlice() catch &.{};
for (slice) |item| {
allocator.free(item);
}
allocator.free(slice);
}
try posix.dl_iterate_phdr(&paths, error{OutOfMemory}, struct {
fn callback(info: *posix.dl_phdr_info, size: usize, list: *List) !void {
_ = size;
const name = info.dlpi_name orelse return;
if (name[0] == '/') {
const item = try list.allocator.dupeZ(u8, mem.sliceTo(name, 0));
errdefer list.allocator.free(item);
try list.append(item);
}
}
}.callback);
return try paths.toOwnedSlice();
},
.macos, .ios, .watchos, .tvos => {
var paths = List.init(allocator);
errdefer {
const slice = paths.toOwnedSlice() catch &.{};
for (slice) |item| {
allocator.free(item);
}
allocator.free(slice);
}
const img_count = std.c._dyld_image_count();
for (0..img_count) |i| {
const name = std.c._dyld_get_image_name(i);
const item = try allocator.dupeZ(u8, mem.sliceTo(name, 0));
errdefer allocator.free(item);
try paths.append(item);
}
return try paths.toOwnedSlice();
},
// revisit if Haiku implements dl_iterat_phdr (https://dev.haiku-os.org/ticket/15743)
.haiku => {
var paths = List.init(allocator);
errdefer {
const slice = paths.toOwnedSlice() catch &.{};
for (slice) |item| {
allocator.free(item);
}
allocator.free(slice);
}
const b = "/boot/system/runtime_loader";
const item = try allocator.dupeZ(u8, mem.sliceTo(b, 0));
errdefer allocator.free(item);
try paths.append(item);
return try paths.toOwnedSlice();
},
else => @compileError("getSelfExeSharedLibPaths unimplemented for this target"),
}
}
pub const preallocate_length = switch (bun.Environment.os) {
.linux => 2048 * 1024,
else => {},
};
pub const preallocate_supported = @TypeOf(preallocate_length) != void;
// https://gist.github.com/Jarred-Sumner/b37b93399b63cbfd86e908c59a0a37df
// ext4 NVME Linux kernel 5.17.0-1016-oem x86_64
//
// hyperfine "./micro 1024 temp" "./micro 1024 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free"
// Benchmark 1: ./micro 1024 temp
// Time (mean ± σ): 1.8 ms ± 0.2 ms [User: 0.6 ms, System: 0.1 ms]
// Range (min … max): 1.2 ms … 2.3 ms 67 runs
// Benchmark 2: ./micro 1024 temp --preallocate
// Time (mean ± σ): 1.8 ms ± 0.1 ms [User: 0.6 ms, System: 0.1 ms]
// Range (min … max): 1.4 ms … 2.2 ms 121 runs
// Summary
// './micro 1024 temp --preallocate' ran
// 1.01 ± 0.13 times faster than './micro 1024 temp'
//
// hyperfine "./micro 65432 temp" "./micro 65432 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free"
// Benchmark 1: ./micro 65432 temp
// Time (mean ± σ): 1.8 ms ± 0.2 ms [User: 0.7 ms, System: 0.1 ms]
// Range (min … max): 1.2 ms … 2.3 ms 94 runs
// Benchmark 2: ./micro 65432 temp --preallocate
// Time (mean ± σ): 2.0 ms ± 0.1 ms [User: 0.6 ms, System: 0.1 ms]
// Range (min … max): 1.7 ms … 2.3 ms 108 runs
// Summary
// './micro 65432 temp' ran
// 1.08 ± 0.12 times faster than './micro 65432 temp --preallocate'
//
// hyperfine "./micro 654320 temp" "./micro 654320 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free"
// Benchmark 1: ./micro 654320 temp
// Time (mean ± σ): 2.3 ms ± 0.2 ms [User: 0.9 ms, System: 0.3 ms]
// Range (min … max): 1.9 ms … 2.9 ms 96 runs
//
// Benchmark 2: ./micro 654320 temp --preallocate
// Time (mean ± σ): 2.2 ms ± 0.1 ms [User: 0.9 ms, System: 0.2 ms]
// Range (min … max): 1.9 ms … 2.7 ms 115 runs
//
// Warning: Command took less than 5 ms to complete. Results might be inaccurate.
//
// Summary
// './micro 654320 temp --preallocate' ran
// 1.04 ± 0.10 times faster than './micro 654320 temp'
//
// hyperfine "./micro 6543200 temp" "./micro 6543200 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free"
// Benchmark 1: ./micro 6543200 temp
// Time (mean ± σ): 6.3 ms ± 0.4 ms [User: 0.4 ms, System: 4.9 ms]
// Range (min … max): 5.8 ms … 8.6 ms 84 runs
//
// Benchmark 2: ./micro 6543200 temp --preallocate
// Time (mean ± σ): 5.5 ms ± 0.3 ms [User: 0.5 ms, System: 3.9 ms]
// Range (min … max): 5.1 ms … 7.1 ms 93 runs
//
// Summary
// './micro 6543200 temp --preallocate' ran
// 1.14 ± 0.09 times faster than './micro 6543200 temp'
//
// hyperfine "./micro 65432000 temp" "./micro 65432000 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free"
// Benchmark 1: ./micro 65432000 temp
// Time (mean ± σ): 52.9 ms ± 0.4 ms [User: 3.1 ms, System: 48.7 ms]
// Range (min … max): 52.4 ms … 54.4 ms 36 runs
//
// Benchmark 2: ./micro 65432000 temp --preallocate
// Time (mean ± σ): 44.6 ms ± 0.8 ms [User: 2.3 ms, System: 41.2 ms]
// Range (min … max): 44.0 ms … 47.3 ms 37 runs
//
// Summary
// './micro 65432000 temp --preallocate' ran
// 1.19 ± 0.02 times faster than './micro 65432000 temp'
//
// hyperfine "./micro 65432000 temp" "./micro 65432000 temp --preallocate" --prepare="rm -rf temp"
// Benchmark 1: ./micro 65432000 temp
// Time (mean ± σ): 51.7 ms ± 0.9 ms [User: 2.1 ms, System: 49.6 ms]
// Range (min … max): 50.7 ms … 54.1 ms 49 runs
//
// Benchmark 2: ./micro 65432000 temp --preallocate
// Time (mean ± σ): 43.8 ms ± 2.3 ms [User: 2.2 ms, System: 41.4 ms]
// Range (min … max): 42.7 ms … 54.7 ms 56 runs
//
// Summary
// './micro 65432000 temp --preallocate' ran
// 1.18 ± 0.06 times faster than './micro 65432000 temp'
//
pub fn preallocate_file(fd: std.posix.fd_t, offset: std.posix.off_t, len: std.posix.off_t) anyerror!void {
switch (Environment.os) {
.linux => {
_ = std.os.linux.fallocate(fd, 0, @as(i64, @intCast(offset)), len);
},
.mac => {
// benchmarking this did nothing on macOS
// i verified it wasn't returning -1
// Based on https://api.kde.org/frameworks/kcoreaddons/html/posix__fallocate__mac_8h_source.html
// var fstore = zeroes(fstore_t);
// fstore.fst_flags = F_ALLOCATECONTIG;
// fstore.fst_posmode = F_PEOFPOSMODE;
// fstore.fst_offset = 0;
// fstore.fst_length = len + offset;
// var rc = os.system.fcntl(fd, os.F.PREALLOCATE, &fstore);
},
else => {}, // not tested,
}
}
pub fn dlopen(filename: [:0]const u8, flags: std.c.RTLD) ?*anyopaque {
if (comptime Environment.isWindows) {
return bun.windows.LoadLibraryA(filename);
}
return std.c.dlopen(filename, flags);
}
pub fn dlsymImpl(handle: ?*anyopaque, name: [:0]const u8) ?*anyopaque {
if (comptime Environment.isWindows) {
return bun.windows.GetProcAddressA(handle, name);
} else if (comptime Environment.isMac or Environment.isLinux) {
return std.c.dlsym(handle, name.ptr);
}
@compileError("dlsym unimplemented for this target");
}
pub fn dlsymWithHandle(comptime Type: type, comptime name: [:0]const u8, comptime handle_getter: fn () ?*anyopaque) ?Type {
if (comptime @typeInfo(Type) != .pointer) {
@compileError("dlsym must be a pointer type (e.g. ?const *fn()). Received " ++ @typeName(Type) ++ ".");
}
const Wrapper = struct {
pub var function: Type = undefined;
var failed = false;
pub var once = std.once(loadOnce);
fn loadOnce() void {
function = bun.cast(Type, dlsymImpl(@call(bun.callmod_inline, handle_getter, .{}), name) orelse {
failed = true;
return;
});
}
};
Wrapper.once.call();
if (Wrapper.failed) {
return null;
}
return Wrapper.function;
}
pub const umask = switch (Environment.os) {
else => bun.c.umask,
// Using the same typedef and define for `mode_t` and `umask` as node on windows.
// https://github.com/nodejs/node/blob/ad5e2dab4c8306183685973387829c2f69e793da/src/node_process_methods.cc#L29
.windows => @extern(*const fn (mode: u16) callconv(.c) u16, .{ .name = "_umask" }),
};
pub const File = @import("./sys/File.zig");
const builtin = @import("builtin");
const sys = @This(); // to avoid ambiguous references.
const bun = @import("bun");
const Environment = bun.Environment;
const FD = bun.FD;
const MAX_PATH_BYTES = bun.MAX_PATH_BYTES;
const c = bun.c; // translated c headers
const jsc = bun.jsc;
const libc_stat = bun.Stat;
const darwin_nocancel = bun.darwin.nocancel;
const windows = bun.windows;
const kernel32 = bun.windows.kernel32;
const ntdll = bun.windows.ntdll;
const std = @import("std");
const mem = std.mem;
const page_size_min = std.heap.page_size_min;
const w = std.os.windows;
const Stat = std.fs.File.Stat;
const posix = std.posix;
const libc = std.posix.system;
const mode_t = posix.mode_t;