const std = #import("std"); const Allocator = std.Allocator; const assert = std.assert; const builtin = #import("builtin"); const current = builtin.os; const link_libc = builtin.link_libc; const linux = #import("os/linux.nat"); const macos = #import("os/macos.nat"); const windows = #import("os/windows.nat"); const c = std.c; const system = switch (link_libc) { true => c, false => switch (current) { .linux => linux, .macos => c, .windows => windows, }, }; const unwrap_syscall = system.unwrap_syscall; const exit = fn(exit_code: s32) noreturn { switch (current) { .linux => _ = #syscall(#cast(linux.Syscall.exit_group), #cast(exit_code)), .macos => system.exit(exit_code), .windows => windows.ExitProcess(#cast(exit_code)), } } const max_file_operation_byte_count = switch (current) { .linux => 0x7ffff000, .macos => 0x7fffffff, else => #error("OS not supported"), }; const FileDescriptor = struct{ handle: system.FileDescriptor, const ReadError = error{ }; const read = fn(file_descriptor: FileDescriptor, bytes: []u8) ReadError!usize { if (bytes.length > 0) { switch (current) { .linux => { const len: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.read(file_descriptor, bytes); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; return byte_count; }, .macos => { const len: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.read(file_descriptor, bytes); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; return byte_count; }, else => #error("OS not supported"), } } else { return 0; } } const WriteError = error{ write_failed, }; const write = fn (file_descriptor: FileDescriptor, bytes: []const u8) WriteError!usize { switch (current) { .linux => { const length: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.write(file_descriptor.handle, bytes[0..length]); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => return WriteError.write_failed, }; return byte_count; }, .macos => { const length: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.write(file_descriptor.handle, bytes.pointer, length); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => return WriteError.write_failed, }; return byte_count; }, else => #error("OS not supported"), } } }; const StdFileDescriptor = enum { stdin = 0, stdout = 1, stderr = 2, const get = fn(descriptor: StdFileDescriptor) FileDescriptor{ switch (current) { .linux, .macos => { return FileDescriptor{ .handle = #cast(descriptor), }; }, else => #error("OS not supported"), } } }; const ProtectionFlags = bitfield(u32){ read: bool, write: bool, execute: bool, }; const MapFlags = bitfield(u32){ reserve: bool, commit: bool, }; const VirtualAllocateError = error{ allocation_failed, }; const allocate_virtual_memory = fn(address: ?[&]u8, length: usize, general_protection_flags: ProtectionFlags, general_map_flags: MapFlags) VirtualAllocateError![&]u8 { const protection_flags = system.get_protection_flags(flags = general_protection_flags); const map_flags = system.get_map_flags(flags = general_map_flags); const file_descriptor = -1; const offset = 0; switch (current) { .linux, .macos => { const syscall_result = system.mmap(address, length, protection_flags, map_flags, file_descriptor, offset); if (link_libc) { if (syscall_result != system.MAP_FAILED) { const result_address: [&]u8 = #cast(syscall_result); return result_address; } else { // TODO: unreachable; } } else { const result = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const pointer: [&]u8 = #cast(result); return pointer; } }, else => #error("OS not supported"), } } const FreeError = error{ free_failed, }; const free_virtual_memory = fn(bytes: []const u8) FreeError!void { switch (current) { .linux => { const syscall_result = system.munmap(bytes); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; }, .macos => { const syscall_result = system.munmap(bytes.pointer, bytes.length); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; }, else => #error("OS not supported"), } } const ReadLinkError = error{ failed, }; const readlink = fn(file_path: [&:0]const u8, buffer: []u8) ReadLinkError![]u8 { switch (current) { .linux, .macos => { const syscall_result = system.readlink(file_path, buffer); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const bytes = buffer[0..byte_count]; return bytes; }, else => #error("OS not supported"), } } const max_path_byte_count = switch (current) { .linux => 0x1000, .macos => 1024, else => #error("OS not supported"), }; const CurrentExecutablePath = error{ failed, }; const current_executable_path = fn(buffer: [:0]u8) CurrentExecutablePath![]u8 { switch (current) { .linux => { const bytes = readlink("/proc/self/exe", buffer) catch |err| switch (err) { else => unreachable, }; return bytes; }, .macos => { var symlink_path_buffer: [max_path_byte_count:0]u8 = undefined; var symlink_path_len: u32 = symlink_path_buffer.length + 1; const ns_result = c._NSGetExecutablePath(symlink_path_buffer.&, symlink_path_len.&); if (ns_result == 0) { const symlink_path = symlink_path_buffer[0..symlink_path_len]; if (c.realpath(symlink_path.pointer, buffer.pointer)) |result| { var i: usize = 0; while (i < buffer.length) { if (result[i] == 0) { break; } i += 1; } assert(i < buffer.length); const r: []u8 = result[0..i]; return r; } else { return CurrentExecutablePath.failed; } } else { return CurrentExecutablePath.failed; } }, else => #error("OS not supported"), } } const Process = struct{ const Id = system.ProcessId; }; const DuplicateProcessError = error{ system_resources, out_of_memory, }; const duplicate_process = fn () DuplicateProcessError!Process.Id { switch (current) { .linux, .macos => { const syscall_result = system.fork(); const result = unwrap_syscall(syscall_result) catch |err| return switch (err) { .AGAIN, .NOMEM => DuplicateProcessError.system_resources, else => unreachable, }; const truncated: u32 = #cast(result); const process_id: Process.Id = #cast(truncated); return process_id; }, else => #error("OS not supported"), } } const ExecveError = error{ execve_failed, }; const execute = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) ExecveError!noreturn { switch (current) { .linux, .macos => { const syscall_result = system.execve(path, argv, env); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => return ExecveError.execve_failed, }; unreachable; }, else => #error("OS not supported"), } } const EventFileDescriptorError = error{ }; const event_file_descriptor = fn(initial_value: u32, flags: u32) EventFileDescriptorError!FileDescriptor { switch (current) { .linux => { const syscall_result = linux.event_file_descriptor(count = initial_value, flags); const result = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const file_descriptor: system.FileDescriptor = #cast(result); return file_descriptor; }, else => #error("OS not supported"), } } const Dup2Error = error{ }; const dup2 = fn(old_file_descriptor: system.FileDescriptor, new_file_descriptor: system.FileDescriptor) Dup2Error!void { switch (current) { .linux => { const syscall_result = linux.dup2(old = old_file_descriptor, new = new_file_descriptor); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; }, else => #error("OS not supported"), } } const OpenError = error{ }; const open = fn(path: [&:0]const u8, flags: u32, permissions: u32) OpenError!FileDescriptor{ switch (current) { .linux => { const syscall_result = linux.open(path, flags, permissions); const result = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const file_descriptor = FileDescriptor{ .handle = #cast(result), }; return file_descriptor; }, else => #error("OS not supported"), } } const CloseError = error{ }; const close = fn(file_descriptor: system.FileDescriptor) CloseError!void { switch (current) { .linux => { const syscall_result = system.close(file_descriptor); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; }, else => #error("OS not supported"), } } const Pipe2Error = error{ }; const pipe2 = fn(flags: u32) Pipe2Error![2]system.FileDescriptor{ switch (current) { .linux => { var pipe: [2]system.FileDescriptor = undefined; const syscall_result = linux.pipe2(pipe.&, flags); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; return pipe; }, else => #error("OS not supported"), } } const PollFileDescriptor = system.PollFileDescriptor; const poll = fn(file_descriptors: []PollFileDescriptor, timeout: s32) ?usize { switch (current) { .linux => { if (linux.unwrap_syscall(syscall_result = linux.poll(file_descriptors = file_descriptors.pointer, file_descriptor_count = file_descriptors.length, timeout = timeout))) |result| { return result; } else { return null; } }, else => #error("OS not supported"), } } const termsig = fn(status: u32) u32 { return status & 0x7f; } const ifexited = fn(status: u32) bool { return termsig(status) == 0; } const exitstatus = fn(status: u32) u8 { const result: u8 = #cast((status & 0xff00) >> 8); return result; } const stopsig = fn(status: u32) u32 { return exitstatus(status); } const ifstopped = fn(status: u32) bool { const result: u16 = #cast(((status & 0xffff) * 0x10001) >> 8); return result > 0x7f00; } const ifsignaled = fn(status: u32) bool { return (status & 0xffff) - 1 < 0xff; } const WaitPidError = error{ failed, }; const waitpid = fn(pid: Process.Id, flags: u32) WaitPidError!u32 { switch (current) { .linux => { var status: u32 = undefined; while (true) { const syscall_result = linux.waitpid(pid, status = status.&, flags, resource_usage = 0); const signed_syscall_result: ssize = #cast(syscall_result); if (signed_syscall_result != -4) { _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; return status; } } }, .macos => { var status: s32 = undefined; if (system.waitpid(pid, status.&, #cast(flags)) != -1) { const status_u: u32 = #cast(status); return status_u; } else { unreachable; } }, else => #error("OS not supported"), } } const reserve = fn (size: u64) *!&any { switch (current) { .linux, .macos => { const syscall_result = system.mmap(null, size, .{ .read = false, .write = false, .execute = false, }, .{ .anonymous = true, .private = true, .shared = false, .fixed = false, }, -1, 0); switch (link_libc) { true => { if (syscall_result != system.MAP_FAILED) { const result: &any = #cast(syscall_result); return result; } else { unreachable; } }, false => { const result = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const pointer: &any = #cast(result); return pointer; }, } }, else => #error("OS not supported"), } } const commit = fn (reserved_memory: &any, size: u64) *!void { switch (current) { .linux, .macos => { const syscall_result = system.mprotect(reserved_memory, size, .{ .read = true, .write = true, .execute = false, }); }, else => #error("OS not supported"), } } const MemFdCreateError = error{ }; const memfd_create = fn(name: [&:0]const u8, flags: u32) MemFdCreateError!FileDescriptor{ switch (current) { .linux => { const syscall_result = linux.memfd_create(path, flags); const result = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; const file_descriptor = FileDescriptor{ .handle = #cast(result), }; return file_descriptor; }, else => #error("OS not supported"), } } const IoChannelBehavior = enum{ pipe, close, inherit, ignore, };