diff --git a/std/os/child_process.zig b/std/child_process.zig similarity index 99% rename from std/os/child_process.zig rename to std/child_process.zig index fa5db9b59..9a0f20d1c 100644 --- a/std/os/child_process.zig +++ b/std/child_process.zig @@ -1,4 +1,4 @@ -const std = @import("../std.zig"); +const std = @import("std.zig"); const cstr = std.cstr; const unicode = std.unicode; const io = std.io; @@ -12,10 +12,9 @@ const Buffer = std.Buffer; const builtin = @import("builtin"); const Os = builtin.Os; const LinkedList = std.LinkedList; -const windows_util = @import("windows/util.zig"); const maxInt = std.math.maxInt; -const is_windows = builtin.os == Os.windows; +const is_windows = builtin.os == .windows; pub const ChildProcess = struct { pub pid: if (is_windows) void else i32, diff --git a/std/coff.zig b/std/coff.zig index c31d2d823..b5b573398 100644 --- a/std/coff.zig +++ b/std/coff.zig @@ -91,7 +91,7 @@ pub const Coff = struct { } else return error.InvalidPEMagic; - try self.in_file.seekForward(skip_size); + try self.in_file.seekBy(skip_size); const number_of_rva_and_sizes = try in.readIntLittle(u32); if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) diff --git a/std/crypto.zig b/std/crypto.zig index 2429441fc..2c3d8eba9 100644 --- a/std/crypto.zig +++ b/std/crypto.zig @@ -32,6 +32,9 @@ pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce; pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; pub const X25519 = @import("crypto/x25519.zig").X25519; +const std = @import("std.zig"); +pub const randomBytes = std.posix.getrandom; + test "crypto" { _ = @import("crypto/blake2.zig"); _ = @import("crypto/chacha20.zig"); diff --git a/std/debug.zig b/std/debug.zig index 45abfda88..e30efebdb 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -893,7 +893,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { if (this_record_len % 4 != 0) { const round_to_next_4 = (this_record_len | 0x3) + 1; const march_forward_bytes = round_to_next_4 - this_record_len; - try dbi.seekForward(march_forward_bytes); + try dbi.seekBy(march_forward_bytes); this_record_len += march_forward_bytes; } @@ -1116,7 +1116,7 @@ fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { defer f.close(); // TODO fstat and make sure that the file has the correct size - var buf: [os.page_size]u8 = undefined; + var buf: [mem.page_size]u8 = undefined; var line: usize = 1; var column: usize = 1; var abs_index: usize = 0; @@ -1938,7 +1938,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr }, else => { const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; - try di.dwarf_seekable_stream.seekForward(fwd_amt); + try di.dwarf_seekable_stream.seekBy(fwd_amt); }, } } else if (opcode >= opcode_base) { @@ -1990,7 +1990,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr else => { if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; const len_bytes = standard_opcode_lengths[opcode - 1]; - try di.dwarf_seekable_stream.seekForward(len_bytes); + try di.dwarf_seekable_stream.seekBy(len_bytes); }, } } diff --git a/std/dynamic_library.zig b/std/dynamic_library.zig index 2a04ac6c9..14bde98f1 100644 --- a/std/dynamic_library.zig +++ b/std/dynamic_library.zig @@ -125,7 +125,7 @@ pub const LinuxDynLib = struct { ); errdefer _ = linux.munmap(addr, size); - const bytes = @intToPtr([*]align(std.os.page_size) u8, addr)[0..size]; + const bytes = @intToPtr([*]align(mem.page_size) u8, addr)[0..size]; return DynLib{ .elf_lib = try ElfLib.init(bytes), diff --git a/std/elf.zig b/std/elf.zig index 39617d3cf..898f9d83f 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -410,7 +410,7 @@ pub const Elf = struct { if (version_byte != 1) return error.InvalidFormat; // skip over padding - try seekable_stream.seekForward(9); + try seekable_stream.seekBy(9); elf.file_type = switch (try in.readInt(u16, elf.endian)) { 1 => FileType.Relocatable, @@ -447,7 +447,7 @@ pub const Elf = struct { } // skip over flags - try seekable_stream.seekForward(4); + try seekable_stream.seekBy(4); const header_size = try in.readInt(u16, elf.endian); if ((elf.is_64 and header_size != 64) or (!elf.is_64 and header_size != 52)) { diff --git a/std/event/fs.zig b/std/event/fs.zig index 34325d1b6..f3118fe97 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -688,7 +688,7 @@ pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 defer list.deinit(); while (true) { - try list.ensureCapacity(list.len + os.page_size); + try list.ensureCapacity(list.len + mem.page_size); const buf = list.items[list.len..]; const buf_array = [][]u8{buf}; const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable); diff --git a/std/fs.zig b/std/fs.zig new file mode 100644 index 000000000..50d0b0df7 --- /dev/null +++ b/std/fs.zig @@ -0,0 +1,840 @@ +const std = @import("std.zig"); +const os = std.os; +const mem = std.mem; + +pub const path = @import("fs/path.zig"); +pub const File = @import("fs/file.zig").File; + +pub const symLink = os.symlink; +pub const symLinkC = os.symlinkC; +pub const deleteFile = os.unlink; +pub const deleteFileC = os.unlinkC; +pub const rename = os.rename; +pub const renameC = os.renameC; +pub const changeCurDir = os.chdir; +pub const changeCurDirC = os.chdirC; +pub const realpath = os.realpath; +pub const realpathC = os.realpathC; +pub const realpathW = os.realpathW; + +pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; +pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; + +/// This represents the maximum size of a UTF-8 encoded file path. +/// All file system operations which return a path are guaranteed to +/// fit into a UTF-8 encoded array of this length. +/// path being too long if it is this 0long +pub const MAX_PATH_BYTES = switch (builtin.os) { + .linux, .macosx, .ios, .freebsd, .netbsd => posix.PATH_MAX, + // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. + // If it would require 4 UTF-8 bytes, then there would be a surrogate + // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. + // +1 for the null byte at the end, which can be encoded in 1 byte. + .windows => posix.PATH_MAX_WIDE * 3 + 1, + else => @compileError("Unsupported OS"), +}; + +/// The result is a slice of `out_buffer`, from index `0`. +pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + return posix.getcwd(out_buffer); +} + +/// Caller must free the returned memory. +pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try posix.getcwd(&buf)); +} + +test "getCwdAlloc" { + // at least call it so it gets compiled + var buf: [1000]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator; + _ = getCwdAlloc(allocator) catch {}; +} + +// here we replace the standard +/ with -_ so that it can be used in a file name +const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); + +/// TODO remove the allocator requirement from this API +pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { + if (symLink(existing_path, new_path)) { + return; + } else |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, // TODO zig should know this set does not include PathAlreadyExists + } + + const dirname = os.path.dirname(new_path) orelse "."; + + var rand_buf: [12]u8 = undefined; + const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); + defer allocator.free(tmp_path); + mem.copy(u8, tmp_path[0..], dirname); + tmp_path[dirname.len] = os.path.sep; + while (true) { + try getRandomBytes(rand_buf[0..]); + b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + + if (symLink(existing_path, tmp_path)) { + return rename(tmp_path, new_path); + } else |err| switch (err) { + error.PathAlreadyExists => continue, + else => return err, // TODO zig should know this set does not include PathAlreadyExists + } + } +} + +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +/// in the same directory as dest_path. +/// Destination file will have the same mode as the source file. +pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { + var in_file = try os.File.openRead(source_path); + defer in_file.close(); + + const mode = try in_file.mode(); + const in_stream = &in_file.inStream().stream; + + var atomic_file = try AtomicFile.init(dest_path, mode); + defer atomic_file.deinit(); + + var buf: [mem.page_size]u8 = undefined; + while (true) { + const amt = try in_stream.readFull(buf[0..]); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } + } +} + +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { + var in_file = try os.File.openRead(source_path); + defer in_file.close(); + + var atomic_file = try AtomicFile.init(dest_path, mode); + defer atomic_file.deinit(); + + var buf: [mem.page_size]u8 = undefined; + while (true) { + const amt = try in_file.read(buf[0..]); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } + } +} + +pub const AtomicFile = struct { + file: os.File, + tmp_path_buf: [MAX_PATH_BYTES]u8, + dest_path: []const u8, + finished: bool, + + const InitError = os.File.OpenError; + + /// dest_path must remain valid for the lifetime of AtomicFile + /// call finish to atomically replace dest_path with contents + /// TODO once we have null terminated pointers, use the + /// openWriteNoClobberN function + pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { + const dirname = os.path.dirname(dest_path); + var rand_buf: [12]u8 = undefined; + const dirname_component_len = if (dirname) |d| d.len + 1 else 0; + const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); + const tmp_path_len = dirname_component_len + encoded_rand_len; + var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined; + if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong; + + if (dirname) |dir| { + mem.copy(u8, tmp_path_buf[0..], dir); + tmp_path_buf[dir.len] = os.path.sep; + } + + tmp_path_buf[tmp_path_len] = 0; + + while (true) { + try getRandomBytes(rand_buf[0..]); + b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf); + + const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) { + error.PathAlreadyExists => continue, + // TODO zig should figure out that this error set does not include PathAlreadyExists since + // it is handled in the above switch + else => return err, + }; + + return AtomicFile{ + .file = file, + .tmp_path_buf = tmp_path_buf, + .dest_path = dest_path, + .finished = false, + }; + } + } + + /// always call deinit, even after successful finish() + pub fn deinit(self: *AtomicFile) void { + if (!self.finished) { + self.file.close(); + deleteFileC(&self.tmp_path_buf) catch {}; + self.finished = true; + } + } + + pub fn finish(self: *AtomicFile) !void { + assert(!self.finished); + self.file.close(); + self.finished = true; + if (is_posix) { + const dest_path_c = try toPosixPath(self.dest_path); + return renameC(&self.tmp_path_buf, &dest_path_c); + } else if (is_windows) { + const dest_path_w = try posix.sliceToPrefixedFileW(self.dest_path); + const tmp_path_w = try posix.cStrToPrefixedFileW(&self.tmp_path_buf); + return renameW(&tmp_path_w, &dest_path_w); + } else { + @compileError("Unsupported OS"); + } + } +}; + +const default_new_dir_mode = 0o755; + +/// Create a new directory. +pub fn makeDir(dir_path: []const u8) !void { + return posix.mkdir(dir_path, default_new_dir_mode); +} + +/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string. +pub fn makeDirC(dir_path: [*]const u8) !void { + return posix.mkdirC(dir_path, default_new_dir_mode); +} + +/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string. +pub fn makeDirW(dir_path: [*]const u16) !void { + return posix.mkdirW(dir_path, default_new_dir_mode); +} + +/// Calls makeDir recursively to make an entire path. Returns success if the path +/// already exists and is a directory. +/// This function is not atomic, and if it returns an error, the file system may +/// have been modified regardless. +/// TODO determine if we can remove the allocator requirement from this function +pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { + const resolved_path = try path.resolve(allocator, [][]const u8{full_path}); + defer allocator.free(resolved_path); + + var end_index: usize = resolved_path.len; + while (true) { + makeDir(resolved_path[0..end_index]) catch |err| switch (err) { + error.PathAlreadyExists => { + // TODO stat the file and return an error if it's not a directory + // this is important because otherwise a dangling symlink + // could cause an infinite loop + if (end_index == resolved_path.len) return; + }, + error.FileNotFound => { + // march end_index backward until next path component + while (true) { + end_index -= 1; + if (os.path.isSep(resolved_path[end_index])) break; + } + continue; + }, + else => return err, + }; + if (end_index == resolved_path.len) return; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break; + } + } +} + +/// Returns `error.DirNotEmpty` if the directory is not empty. +/// To delete a directory recursively, see `deleteTree`. +pub fn deleteDir(dir_path: []const u8) DeleteDirError!void { + return posix.rmdir(dir_path); +} + +/// Same as `deleteDir` except the parameter is a null-terminated UTF8-encoded string. +pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void { + return posix.rmdirC(dir_path); +} + +/// Same as `deleteDir` except the parameter is a null-terminated UTF16LE-encoded string. +pub fn deleteDirW(dir_path: [*]const u16) DeleteDirError!void { + return posix.rmdirW(dir_path); +} + +/// Whether ::full_path describes a symlink, file, or directory, this function +/// removes it. If it cannot be removed because it is a non-empty directory, +/// this function recursively removes its entries and then tries again. +const DeleteTreeError = error{ + OutOfMemory, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + NotDir, + FileNotFound, + FileSystem, + FileBusy, + DirNotEmpty, + DeviceBusy, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + Unexpected, +}; + +/// TODO determine if we can remove the allocator requirement +pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { + start_over: while (true) { + var got_access_denied = false; + // First, try deleting the item as a file. This way we don't follow sym links. + if (deleteFile(full_path)) { + return; + } else |err| switch (err) { + error.FileNotFound => return, + error.IsDir => {}, + error.AccessDenied => got_access_denied = true, + + error.InvalidUtf8, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.NotDir, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.Unexpected, + => return err, + } + { + var dir = Dir.open(allocator, full_path) catch |err| switch (err) { + error.NotDir => { + if (got_access_denied) { + return error.AccessDenied; + } + continue :start_over; + }, + + error.OutOfMemory, + error.AccessDenied, + error.FileTooBig, + error.IsDir, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.FileNotFound, + error.SystemResources, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.Unexpected, + error.InvalidUtf8, + error.BadPathName, + error.DeviceBusy, + => return err, + }; + defer dir.close(); + + var full_entry_buf = ArrayList(u8).init(allocator); + defer full_entry_buf.deinit(); + + while (try dir.next()) |entry| { + try full_entry_buf.resize(full_path.len + entry.name.len + 1); + const full_entry_path = full_entry_buf.toSlice(); + mem.copy(u8, full_entry_path, full_path); + full_entry_path[full_path.len] = path.sep; + mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); + + try deleteTree(allocator, full_entry_path); + } + } + return deleteDir(full_path); + } +} + +pub const Dir = struct { + handle: Handle, + allocator: *Allocator, + + pub const Handle = switch (builtin.os) { + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => struct { + fd: i32, + seek: i64, + buf: []u8, + index: usize, + end_index: usize, + }, + Os.linux => struct { + fd: i32, + buf: []u8, + index: usize, + end_index: usize, + }, + Os.windows => struct { + handle: windows.HANDLE, + find_file_data: windows.WIN32_FIND_DATAW, + first: bool, + name_data: [256]u8, + }, + else => @compileError("unimplemented"), + }; + + pub const Entry = struct { + name: []const u8, + kind: Kind, + + pub const Kind = enum { + BlockDevice, + CharacterDevice, + Directory, + NamedPipe, + SymLink, + File, + UnixDomainSocket, + Whiteout, + Unknown, + }; + }; + + pub const OpenError = error{ + FileNotFound, + NotDir, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + OutOfMemory, + InvalidUtf8, + BadPathName, + DeviceBusy, + + Unexpected, + }; + + /// TODO remove the allocator requirement from this API + pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { + return Dir{ + .allocator = allocator, + .handle = switch (builtin.os) { + Os.windows => blk: { + var find_file_data: windows.WIN32_FIND_DATAW = undefined; + const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data); + break :blk Handle{ + .handle = handle, + .find_file_data = find_file_data, // TODO guaranteed copy elision + .first = true, + .name_data = undefined, + }; + }, + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => Handle{ + .fd = try posixOpen( + dir_path, + posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, + 0, + ), + .seek = 0, + .index = 0, + .end_index = 0, + .buf = []u8{}, + }, + Os.linux => Handle{ + .fd = try posixOpen( + dir_path, + posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, + 0, + ), + .index = 0, + .end_index = 0, + .buf = []u8{}, + }, + else => @compileError("unimplemented"), + }, + }; + } + + pub fn close(self: *Dir) void { + switch (builtin.os) { + Os.windows => { + _ = windows.FindClose(self.handle.handle); + }, + Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => { + self.allocator.free(self.handle.buf); + os.close(self.handle.fd); + }, + else => @compileError("unimplemented"), + } + } + + /// Memory such as file names referenced in this returned entry becomes invalid + /// with subsequent calls to next, as well as when this `Dir` is deinitialized. + pub fn next(self: *Dir) !?Entry { + switch (builtin.os) { + Os.linux => return self.nextLinux(), + Os.macosx, Os.ios => return self.nextDarwin(), + Os.windows => return self.nextWindows(), + Os.freebsd => return self.nextFreebsd(), + Os.netbsd => return self.nextFreebsd(), + else => @compileError("unimplemented"), + } + } + + fn nextDarwin(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, mem.page_size); + } + + while (true) { + const result = system.__getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); + if (result == 0) return null; + if (result < 0) { + switch (system.getErrno(result)) { + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + self.handle.index = 0; + self.handle.end_index = @intCast(usize, result); + break; + } + } + const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + darwin_entry.d_reclen; + self.handle.index = next_index; + + const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; + + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (darwin_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + posix.DT_WHT => Entry.Kind.Whiteout, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + + fn nextWindows(self: *Dir) !?Entry { + while (true) { + if (self.handle.first) { + self.handle.first = false; + } else { + if (!try posix.FindNextFile(self.handle.handle, &self.handle.find_file_data)) + return null; + } + const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); + if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{ '.', '.' })) + continue; + // Trust that Windows gives us valid UTF-16LE + const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; + const name_utf8 = self.handle.name_data[0..name_utf8_len]; + const kind = blk: { + const attrs = self.handle.find_file_data.dwFileAttributes; + if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; + if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; + if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File; + break :blk Entry.Kind.Unknown; + }; + return Entry{ + .name = name_utf8, + .kind = kind, + }; + } + } + + fn nextLinux(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, mem.page_size); + } + + while (true) { + const result = posix.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + if (result == 0) return null; + self.handle.index = 0; + self.handle.end_index = result; + break; + } + } + const linux_entry = @ptrCast(*align(1) posix.dirent64, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + linux_entry.d_reclen; + self.handle.index = next_index; + + const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name)); + + // skip . and .. entries + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (linux_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + + fn nextFreebsd(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, mem.page_size); + } + + while (true) { + const result = posix.getdirentries(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + if (result == 0) return null; + self.handle.index = 0; + self.handle.end_index = result; + break; + } + } + const freebsd_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + freebsd_entry.d_reclen; + self.handle.index = next_index; + + const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; + + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (freebsd_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + posix.DT_WHT => Entry.Kind.Whiteout, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } +}; + +/// Read value of a symbolic link. +/// The return value is a slice of buffer, from index `0`. +pub fn readLink(buffer: *[posix.PATH_MAX]u8, pathname: []const u8) ![]u8 { + return posix.readlink(pathname, buffer); +} + +/// Same as `readLink`, except the `pathname` parameter is null-terminated. +pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { + return posix.readlinkC(pathname, buffer); +} + +pub fn openSelfExe() !os.File { + switch (builtin.os) { + Os.linux => return os.File.openReadC(c"/proc/self/exe"), + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + var buf: [MAX_PATH_BYTES]u8 = undefined; + const self_exe_path = try selfExePath(&buf); + buf[self_exe_path.len] = 0; + return os.File.openReadC(self_exe_path.ptr); + }, + Os.windows => { + var buf: [posix.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try selfExePathW(&buf); + return os.File.openReadW(wide_slice.ptr); + }, + else => @compileError("Unsupported OS"), + } +} + +test "openSelfExe" { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios, Os.windows, Os.freebsd => (try openSelfExe()).close(), + else => return error.SkipZigTest, // Unsupported OS. + } +} + +pub fn selfExePathW(out_buffer: *[posix.PATH_MAX_WIDE]u16) ![]u16 { + const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast + const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len); + assert(rc <= out_buffer.len); + if (rc == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return windows.unexpectedError(err), + } + } + return out_buffer[0..rc]; +} + +/// Get the path to the current executable. +/// If you only need the directory, use selfExeDirPath. +/// If you only want an open file handle, use openSelfExe. +/// This function may return an error if the current executable +/// was deleted after spawning. +/// Returned value is a slice of out_buffer. +/// +/// On Linux, depends on procfs being mounted. If the currently executing binary has +/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. +/// TODO make the return type of this a null terminated pointer +pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + switch (builtin.os) { + Os.linux => return readLink(out_buffer, "/proc/self/exe"), + Os.freebsd => { + var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 }; + var out_len: usize = out_buffer.len; + try posix.sysctl(&mib, out_buffer, &out_len, null, 0); + // TODO could this slice from 0 to out_len instead? + return mem.toSlice(u8, out_buffer); + }, + Os.netbsd => { + var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC_ARGS, -1, posix.KERN_PROC_PATHNAME }; + var out_len: usize = out_buffer.len; + try posix.sysctl(&mib, out_buffer, &out_len, null, 0); + // TODO could this slice from 0 to out_len instead? + return mem.toSlice(u8, out_buffer); + }, + Os.windows => { + var utf16le_buf: [posix.PATH_MAX_WIDE]u16 = undefined; + const utf16le_slice = try selfExePathW(&utf16le_buf); + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; + return out_buffer[0..end_index]; + }, + Os.macosx, Os.ios => { + var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast + const rc = c._NSGetExecutablePath(out_buffer, &u32_len); + if (rc != 0) return error.NameTooLong; + return mem.toSlice(u8, out_buffer); + }, + else => @compileError("Unsupported OS"), + } +} + +/// `selfExeDirPath` except allocates the result on the heap. +/// Caller owns returned memory. +pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try selfExeDirPath(&buf)); +} + +/// Get the directory path that contains the current executable. +/// Returned value is a slice of out_buffer. +pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 { + switch (builtin.os) { + Os.linux => { + // If the currently executing binary has been deleted, + // the file path looks something like `/a/b/c/exe (deleted)` + // This path cannot be opened, but it's valid for determining the directory + // the executable was in when it was run. + const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe"); + // Assume that /proc/self/exe has an absolute path, and therefore dirname + // will not return null. + return path.dirname(full_exe_path).?; + }, + Os.windows, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + const self_exe_path = try selfExePath(out_buffer); + // Assume that the OS APIs return absolute paths, and therefore dirname + // will not return null. + return path.dirname(self_exe_path).?; + }, + else => @compileError("Unsupported OS"), + } +} + +/// `realpath`, except caller must free the returned memory. +pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try realpath(pathname, &buf)); +} + +test "" { + _ = @import("fs/path.zig"); + _ = @import("fs/file.zig"); + _ = @import("fs/get_app_data_dir.zig"); +} diff --git a/std/fs/file.zig b/std/fs/file.zig new file mode 100644 index 000000000..8de5d5eb3 --- /dev/null +++ b/std/fs/file.zig @@ -0,0 +1,334 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const os = std.os; +const io = std.io; +const mem = std.mem; +const math = std.math; +const assert = std.debug.assert; +const windows = os.windows; +const Os = builtin.Os; +const maxInt = std.math.maxInt; + +pub const File = struct { + /// The OS-specific file descriptor or file handle. + handle: os.fd_t, + + pub const Mode = switch (builtin.os) { + Os.windows => void, + else => u32, + }; + + pub const default_mode = switch (builtin.os) { + Os.windows => {}, + else => 0o666, + }; + + pub const OpenError = windows.CreateFileError || os.OpenError; + + /// Call close to clean up. + pub fn openRead(path: []const u8) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openReadW(&path_w); + } + const path_c = try os.toPosixPath(path); + return openReadC(&path_c); + } + + /// `openRead` except with a null terminated path + pub fn openReadC(path: [*]const u8) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openReadW(&path_w); + } + const flags = os.O_LARGEFILE | os.O_RDONLY; + const fd = try os.openC(path, flags, 0); + return openHandle(fd); + } + + /// `openRead` except with a null terminated UTF16LE encoded path + pub fn openReadW(path_w: [*]const u16) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + + /// Calls `openWriteMode` with `default_mode` for the mode. + pub fn openWrite(path: []const u8) OpenError!File { + return openWriteMode(path, default_mode); + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination it will be truncated. + /// Call close to clean up. + pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); + } + const path_c = try os.toPosixPath(path); + return openWriteModeC(&path_c, file_mode); + } + + /// Same as `openWriteMode` except `path` is null-terminated. + pub fn openWriteModeC(path: [*]const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); + } + const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; + const fd = try os.openC(path, flags, file_mode); + return openHandle(fd); + } + + /// Same as `openWriteMode` except `path` is null-terminated and UTF16LE encoded + pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_ALWAYS, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination this returns OpenError.PathAlreadyExists + /// Call close to clean up. + pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); + } + const path_c = try os.toPosixPath(path); + return openWriteNoClobberC(&path_c, file_mode); + } + + pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); + } + const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_EXCL; + const fd = try os.openC(path, flags, file_mode); + return openHandle(fd); + } + + pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_NEW, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + + pub fn openHandle(handle: os.fd_t) File { + return File{ .handle = handle }; + } + + /// Test for the existence of `path`. + /// `path` is UTF8-encoded. + pub fn exists(path: []const u8) AccessError!void { + return os.access(path, os.F_OK); + } + + /// Same as `exists` except the parameter is null-terminated. + pub fn existsC(path: [*]const u8) AccessError!void { + return os.accessC(path, os.F_OK); + } + + /// Same as `exists` except the parameter is null-terminated UTF16LE-encoded. + pub fn existsW(path: [*]const u16) AccessError!void { + return os.accessW(path, os.F_OK); + } + + /// Upon success, the stream is in an uninitialized state. To continue using it, + /// you must use the open() function. + pub fn close(self: File) void { + os.close(self.handle); + } + + /// Test whether the file refers to a terminal. + /// See also `supportsAnsiEscapeCodes`. + pub fn isTty(self: File) bool { + return os.isatty(self.handle); + } + + /// Test whether ANSI escape codes will be treated as such. + pub fn supportsAnsiEscapeCodes(self: File) bool { + if (windows.is_the_target) { + return os.isCygwinPty(self.handle); + } + return self.isTty(); + } + + pub const SeekError = os.SeekError; + + /// Repositions read/write file offset relative to the current offset. + pub fn seekBy(self: File, offset: i64) SeekError!void { + return os.lseek_CUR(self.handle, offset); + } + + /// Repositions read/write file offset relative to the end. + pub fn seekFromEnd(self: File, offset: i64) SeekError!void { + return os.lseek_END(self.handle, offset); + } + + /// Repositions read/write file offset relative to the beginning. + pub fn seekTo(self: File, offset: u64) SeekError!void { + return os.lseek_SET(self.handle, offset); + } + + pub const GetPosError = os.SeekError || os.FStatError; + + pub fn getPos(self: File) GetPosError!u64 { + return os.lseek_CUR_get(self.handle); + } + + pub fn getEndPos(self: File) GetPosError!u64 { + if (windows.is_the_target and !builtin.link_libc) { + return windows.GetFileSizeEx(self.handle); + } + const stat = try os.fstat(self.handle); + return @bitCast(u64, stat.size); + } + + pub const ModeError = os.FStatError; + + pub fn mode(self: File) ModeError!Mode { + if (windows.is_the_target and !builtin.link_libc) { + return {}; + } + const stat = try os.fstat(self.handle); + // TODO: we should be able to cast u16 to ModeError!u32, making this + // explicit cast not necessary + return Mode(stat.mode); + } + + pub const ReadError = os.ReadError; + + pub fn read(self: File, buffer: []u8) ReadError!usize { + return os.read(self.handle, buffer); + } + + pub const WriteError = os.WriteError; + + pub fn write(self: File, bytes: []const u8) WriteError!void { + return os.write(self.handle, bytes); + } + + pub fn inStream(file: File) InStream { + return InStream{ + .file = file, + .stream = InStream.Stream{ .readFn = InStream.readFn }, + }; + } + + pub fn outStream(file: File) OutStream { + return OutStream{ + .file = file, + .stream = OutStream.Stream{ .writeFn = OutStream.writeFn }, + }; + } + + pub fn seekableStream(file: File) SeekableStream { + return SeekableStream{ + .file = file, + .stream = SeekableStream.Stream{ + .seekToFn = SeekableStream.seekToFn, + .seekByFn = SeekableStream.seekByFn, + .getPosFn = SeekableStream.getPosFn, + .getEndPosFn = SeekableStream.getEndPosFn, + }, + }; + } + + /// Implementation of io.InStream trait for File + pub const InStream = struct { + file: File, + stream: Stream, + + pub const Error = ReadError; + pub const Stream = io.InStream(Error); + + fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { + const self = @fieldParentPtr(InStream, "stream", in_stream); + return self.file.read(buffer); + } + }; + + /// Implementation of io.OutStream trait for File + pub const OutStream = struct { + file: File, + stream: Stream, + + pub const Error = WriteError; + pub const Stream = io.OutStream(Error); + + fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { + const self = @fieldParentPtr(OutStream, "stream", out_stream); + return self.file.write(bytes); + } + }; + + /// Implementation of io.SeekableStream trait for File + pub const SeekableStream = struct { + file: File, + stream: Stream, + + pub const Stream = io.SeekableStream(SeekError, GetPosError); + + pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.seekTo(pos); + } + + pub fn seekByFn(seekable_stream: *Stream, amt: i64) SeekError!void { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.seekBy(amt); + } + + pub fn getEndPosFn(seekable_stream: *Stream) GetPosError!u64 { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.getEndPos(); + } + + pub fn getPosFn(seekable_stream: *Stream) GetPosError!u64 { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.getPos(); + } + }; + + pub fn stdout() !File { + if (windows.is_the_target) { + const handle = try windows.GetStdHandle(windows.STD_OUTPUT_HANDLE); + return openHandle(handle); + } + return openHandle(os.STDOUT_FILENO); + } + + pub fn stderr() !File { + if (windows.is_the_target) { + const handle = try windows.GetStdHandle(windows.STD_ERROR_HANDLE); + return openHandle(handle); + } + return openHandle(os.STDERR_FILENO); + } + + pub fn stdin() !File { + if (windows.is_the_target) { + const handle = try windows.GetStdHandle(windows.STD_INPUT_HANDLE); + return openHandle(handle); + } + return openHandle(os.STDIN_FILENO); + } +}; diff --git a/std/os/get_app_data_dir.zig b/std/fs/get_app_data_dir.zig similarity index 94% rename from std/os/get_app_data_dir.zig rename to std/fs/get_app_data_dir.zig index e69c03edb..23dbaad86 100644 --- a/std/os/get_app_data_dir.zig +++ b/std/fs/get_app_data_dir.zig @@ -13,7 +13,7 @@ pub const GetAppDataDirError = error{ /// TODO determine if we can remove the allocator requirement pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { switch (builtin.os) { - builtin.Os.windows => { + .windows => { var dir_path_ptr: [*]u16 = undefined; switch (os.windows.SHGetKnownFolderPath( &os.windows.FOLDERID_LocalAppData, @@ -36,14 +36,14 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD else => return error.AppDataDirUnavailable, } }, - builtin.Os.macosx => { + .macosx => { const home_dir = os.getEnvPosix("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; }; return os.path.join(allocator, [][]const u8{ home_dir, "Library", "Application Support", appname }); }, - builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => { + .linux, .freebsd, .netbsd => { const home_dir = os.getEnvPosix("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; @@ -60,7 +60,7 @@ fn utf16lePtrSlice(ptr: [*]const u16) []const u16 { return ptr[0..index]; } -test "std.os.getAppDataDir" { +test "getAppDataDir" { var buf: [512]u8 = undefined; const allocator = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; diff --git a/std/os/path.zig b/std/fs/path.zig similarity index 87% rename from std/os/path.zig rename to std/fs/path.zig index 95950ddff..4c7383460 100644 --- a/std/os/path.zig +++ b/std/fs/path.zig @@ -12,7 +12,6 @@ const math = std.math; const posix = os.posix; const windows = os.windows; const cstr = std.cstr; -const windows_util = @import("windows/util.zig"); pub const sep_windows = '\\'; pub const sep_posix = '/'; @@ -105,7 +104,7 @@ fn testJoinPosix(paths: []const []const u8, expected: []const u8) void { testing.expectEqualSlices(u8, expected, actual); } -test "os.path.join" { +test "join" { testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c"); testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c"); testJoinWindows([][]const u8{ "c:\\a\\b\\", "c" }, "c:\\a\\b\\c"); @@ -164,7 +163,7 @@ pub fn isAbsolutePosix(path: []const u8) bool { return path[0] == sep_posix; } -test "os.path.isAbsoluteWindows" { +test "isAbsoluteWindows" { testIsAbsoluteWindows("/", true); testIsAbsoluteWindows("//", true); testIsAbsoluteWindows("//server", true); @@ -186,7 +185,7 @@ test "os.path.isAbsoluteWindows" { testIsAbsoluteWindows("/usr/local", true); } -test "os.path.isAbsolutePosix" { +test "isAbsolutePosix" { testIsAbsolutePosix("/home/foo", true); testIsAbsolutePosix("/home/foo/..", true); testIsAbsolutePosix("bar/", false); @@ -279,7 +278,7 @@ pub fn windowsParsePath(path: []const u8) WindowsPath { return relative_path; } -test "os.path.windowsParsePath" { +test "windowsParsePath" { { const parsed = windowsParsePath("//a/b"); testing.expect(parsed.is_abs); @@ -637,7 +636,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { return allocator.shrink(result, result_index); } -test "os.path.resolve" { +test "resolve" { const cwd = try os.getCwdAlloc(debug.global_allocator); if (is_windows) { if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { @@ -650,7 +649,7 @@ test "os.path.resolve" { } } -test "os.path.resolveWindows" { +test "resolveWindows" { if (is_windows) { const cwd = try os.getCwdAlloc(debug.global_allocator); const parsed_cwd = windowsParsePath(cwd); @@ -693,7 +692,7 @@ test "os.path.resolveWindows" { testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }), "C:\\foo\\tmp.3\\cycles\\root.js")); } -test "os.path.resolvePosix" { +test "resolvePosix" { testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c" }), "/a/b/c")); testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c", "//d", "e///" }), "/d/e")); testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b/c", "..", "../" }), "/a")); @@ -784,7 +783,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 { return path[0..end_index]; } -test "os.path.dirnamePosix" { +test "dirnamePosix" { testDirnamePosix("/a/b/c", "/a/b"); testDirnamePosix("/a/b/c///", "/a/b"); testDirnamePosix("/a", "/"); @@ -796,7 +795,7 @@ test "os.path.dirnamePosix" { testDirnamePosix("a//", null); } -test "os.path.dirnameWindows" { +test "dirnameWindows" { testDirnameWindows("c:\\", "c:\\"); testDirnameWindows("c:\\foo", "c:\\"); testDirnameWindows("c:\\foo\\", "c:\\"); @@ -909,7 +908,7 @@ pub fn basenameWindows(path: []const u8) []const u8 { return path[start_index + 1 .. end_index]; } -test "os.path.basename" { +test "basename" { testBasename("", ""); testBasename("/", ""); testBasename("/dir/basename.ext", "basename.ext"); @@ -1090,7 +1089,7 @@ pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![ return []u8{}; } -test "os.path.relative" { +test "relative" { testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); @@ -1139,148 +1138,3 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons const result = relativeWindows(debug.global_allocator, from, to) catch unreachable; testing.expectEqualSlices(u8, expected_output, result); } - -pub const RealError = error{ - FileNotFound, - AccessDenied, - NameTooLong, - NotSupported, - NotDir, - SymLinkLoop, - InputOutput, - FileTooBig, - IsDir, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - FileSystem, - BadPathName, - DeviceBusy, - - /// On Windows, file paths must be valid Unicode. - InvalidUtf8, - - PathAlreadyExists, - - Unexpected, -}; - -/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string. -/// Otherwise use `real` or `realC`. -pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 { - const h_file = windows.CreateFileW( - pathname, - windows.GENERIC_READ, - windows.FILE_SHARE_READ, - null, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL, - null, - ); - if (h_file == windows.INVALID_HANDLE_VALUE) { - const err = windows.GetLastError(); - switch (err) { - windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, - windows.ERROR.ACCESS_DENIED => return error.AccessDenied, - windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, - else => return os.unexpectedErrorWindows(err), - } - } - defer os.close(h_file); - var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; - const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast - const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS); - assert(result <= utf16le_buf.len); - if (result == 0) { - const err = windows.GetLastError(); - switch (err) { - windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, - windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, - windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, - windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, - windows.ERROR.INVALID_PARAMETER => unreachable, - else => return os.unexpectedErrorWindows(err), - } - } - const utf16le_slice = utf16le_buf[0..result]; - - // windows returns \\?\ prepended to the path - // we strip it because nobody wants \\?\ prepended to their path - const prefix = []u16{ '\\', '\\', '?', '\\' }; - const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0; - - // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable; - return out_buffer[0..end_index]; -} - -/// See `real` -/// Use this when you have a null terminated pointer path. -pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 { - switch (builtin.os) { - Os.windows => { - const pathname_w = try windows_util.cStrToPrefixedFileW(pathname); - return realW(out_buffer, pathname_w); - }, - Os.freebsd, Os.netbsd, Os.macosx, Os.ios => { - // TODO instead of calling the libc function here, port the implementation to Zig - const err = posix.getErrno(posix.realpath(pathname, out_buffer)); - switch (err) { - 0 => return mem.toSlice(u8, out_buffer), - posix.EINVAL => unreachable, - posix.EBADF => unreachable, - posix.EFAULT => unreachable, - posix.EACCES => return error.AccessDenied, - posix.ENOENT => return error.FileNotFound, - posix.ENOTSUP => return error.NotSupported, - posix.ENOTDIR => return error.NotDir, - posix.ENAMETOOLONG => return error.NameTooLong, - posix.ELOOP => return error.SymLinkLoop, - posix.EIO => return error.InputOutput, - else => return os.unexpectedErrorPosix(err), - } - }, - Os.linux => { - const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); - defer os.close(fd); - - var buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; - const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable; - - return os.readLinkC(out_buffer, proc_path.ptr); - }, - else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)), - } -} - -/// Return the canonicalized absolute pathname. -/// Expands all symbolic links and resolves references to `.`, `..`, and -/// extra `/` characters in ::pathname. -/// The return value is a slice of out_buffer, and not necessarily from the beginning. -pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 { - switch (builtin.os) { - Os.windows => { - const pathname_w = try windows_util.sliceToPrefixedFileW(pathname); - return realW(out_buffer, &pathname_w); - }, - Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => { - const pathname_c = try os.toPosixPath(pathname); - return realC(out_buffer, &pathname_c); - }, - else => @compileError("Unsupported OS"), - } -} - -/// `real`, except caller must free the returned memory. -pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { - var buf: [os.MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try real(&buf, pathname)); -} - -test "os.path.real" { - // at least call it so it gets compiled - var buf: [os.MAX_PATH_BYTES]u8 = undefined; - testing.expectError(error.FileNotFound, real(&buf, "definitely_bogus_does_not_exist1234")); -} diff --git a/std/io.zig b/std/io.zig index 831156831..657352e29 100644 --- a/std/io.zig +++ b/std/io.zig @@ -49,7 +49,7 @@ pub fn InStream(comptime ReadError: type) type { return; } - const new_buf_size = math.min(max_size, actual_buf_len + os.page_size); + const new_buf_size = math.min(max_size, actual_buf_len + mem.page_size); if (new_buf_size == actual_buf_len) return error.StreamTooLong; try buffer.resize(new_buf_size); } @@ -284,7 +284,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim } pub fn BufferedInStream(comptime Error: type) type { - return BufferedInStreamCustom(os.page_size, Error); + return BufferedInStreamCustom(mem.page_size, Error); } pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type) type { @@ -757,7 +757,7 @@ test "io.CountingOutStream" { } pub fn BufferedOutStream(comptime Error: type) type { - return BufferedOutStreamCustom(os.page_size, Error); + return BufferedOutStreamCustom(mem.page_size, Error); } pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type { diff --git a/std/io/seekable_stream.zig b/std/io/seekable_stream.zig index 392e13530..86f76d8c1 100644 --- a/std/io/seekable_stream.zig +++ b/std/io/seekable_stream.zig @@ -8,7 +8,7 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType pub const GetSeekPosError = GetSeekPosErrorType; seekToFn: fn (self: *Self, pos: u64) SeekError!void, - seekForwardFn: fn (self: *Self, pos: i64) SeekError!void, + seekByFn: fn (self: *Self, pos: i64) SeekError!void, getPosFn: fn (self: *Self) GetSeekPosError!u64, getEndPosFn: fn (self: *Self) GetSeekPosError!u64, @@ -17,8 +17,8 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType return self.seekToFn(self, pos); } - pub fn seekForward(self: *Self, amt: i64) SeekError!void { - return self.seekForwardFn(self, amt); + pub fn seekBy(self: *Self, amt: i64) SeekError!void { + return self.seekByFn(self, amt); } pub fn getEndPos(self: *Self) GetSeekPosError!u64 { @@ -52,7 +52,7 @@ pub const SliceSeekableInStream = struct { .stream = Stream{ .readFn = readFn }, .seekable_stream = SeekableInStream{ .seekToFn = seekToFn, - .seekForwardFn = seekForwardFn, + .seekByFn = seekByFn, .getEndPosFn = getEndPosFn, .getPosFn = getPosFn, }, @@ -77,7 +77,7 @@ pub const SliceSeekableInStream = struct { self.pos = usize_pos; } - fn seekForwardFn(in_stream: *SeekableInStream, amt: i64) SeekError!void { + fn seekByFn(in_stream: *SeekableInStream, amt: i64) SeekError!void { const self = @fieldParentPtr(Self, "seekable_stream", in_stream); if (amt < 0) { diff --git a/std/mem.zig b/std/mem.zig index 64fe270ed..50ed2771e 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -8,6 +8,11 @@ const meta = std.meta; const trait = meta.trait; const testing = std.testing; +pub const page_size = switch (builtin.arch) { + .wasm32, .wasm64 => 64 * 1024, + else => 4 * 1024, +}; + pub const Allocator = struct { pub const Error = error{OutOfMemory}; @@ -1457,3 +1462,21 @@ test "std.mem.alignForward" { testing.expect(alignForward(16, 8) == 16); testing.expect(alignForward(17, 8) == 24); } + +pub fn getBaseAddress() usize { + switch (builtin.os) { + .linux => { + const base = std.os.posix.getauxval(std.elf.AT_BASE); + if (base != 0) { + return base; + } + const phdr = std.os.posix.getauxval(std.elf.AT_PHDR); + return phdr - @sizeOf(std.elf.Ehdr); + }, + .macosx, .freebsd, .netbsd => { + return @ptrToInt(&std.c._mh_execute_header); + }, + .windows => return @ptrToInt(windows.GetModuleHandleW(null)), + else => @compileError("Unsupported OS"), + } +} diff --git a/std/os.zig b/std/os.zig index b6be5bf5e..a2fbca812 100644 --- a/std/os.zig +++ b/std/os.zig @@ -1,544 +1,449 @@ +// This file contains thin wrappers around OS-specific APIs, with these +// specific goals in mind: +// * Convert "errno"-style error codes into Zig errors. +// * When null-terminated byte buffers are required, provide APIs which accept +// slices as well as APIs which accept null-terminated byte buffers. Same goes +// for UTF-16LE encoding. +// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide +// cross platform abstracting. +// * When there exists a corresponding libc function and linking libc, the libc +// implementation is used. Exceptions are made for known buggy areas of libc. +// On Linux libc can be side-stepped by using `std.os.linux.sys`. +// * For Windows, this file represents the API that libc would provide for +// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. +// Note: The Zig standard library does not support POSIX thread cancellation, and +// in general EINTR is handled by trying again. + const std = @import("std.zig"); const builtin = @import("builtin"); -const Os = builtin.Os; -const is_windows = builtin.os == Os.windows; -const os = @This(); +const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; comptime { assert(@import("std") == std); // You have to run the std lib tests with --override-std-dir } -test "std.os" { - _ = @import("os/child_process.zig"); - _ = @import("os/darwin.zig"); - _ = @import("os/get_user_id.zig"); - _ = @import("os/linux.zig"); - _ = @import("os/path.zig"); - _ = @import("os/test.zig"); - _ = @import("os/time.zig"); - _ = @import("os/windows.zig"); - _ = @import("os/uefi.zig"); - _ = @import("os/wasi.zig"); - _ = @import("os/get_app_data_dir.zig"); -} - -pub const windows = @import("os/windows.zig"); pub const darwin = @import("os/darwin.zig"); -pub const linux = @import("os/linux.zig"); pub const freebsd = @import("os/freebsd.zig"); +pub const linux = @import("os/linux.zig"); pub const netbsd = @import("os/netbsd.zig"); -pub const zen = @import("os/zen.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); +pub const windows = @import("os/windows.zig"); +pub const zen = @import("os/zen.zig"); +/// When linking libc, this is the C API. Otherwise, it is the OS-specific system interface. pub const system = if (builtin.link_libc) std.c else switch (builtin.os) { - .linux => linux, .macosx, .ios, .watchos, .tvos => darwin, .freebsd => freebsd, + .linux => linux, .netbsd => netbsd, - .zen => zen, .wasi => wasi, .windows => windows, + .zen => zen, else => struct {}, }; -pub const net = @import("net.zig"); +/// See also `getenv`. +pub var environ: [][*]u8 = undefined; -pub const ChildProcess = @import("os/child_process.zig").ChildProcess; -pub const path = @import("os/path.zig"); -pub const File = @import("os/file.zig").File; -pub const time = @import("os/time.zig"); +/// To obtain errno, call this function with the return value of the +/// system function call. For some systems this will obtain the value directly +/// from the return code; for others it will use a thread-local errno variable. +/// Therefore, this function only returns a well-defined value when it is called +/// directly after the system function call which one wants to learn the errno +/// value of. +pub const errno = system.getErrno; -pub const page_size = switch (builtin.arch) { - .wasm32, .wasm64 => 64 * 1024, - else => 4 * 1024, -}; +/// Closes the file descriptor. +/// This function is not capable of returning any indication of failure. An +/// application which wants to ensure writes have succeeded before closing +/// must call `fsync` before `close`. +/// Note: The Zig standard library does not support POSIX thread cancellation. +pub fn close(fd: fd_t) void { + if (windows.is_the_target and !builtin.link_libc) { + return windows.CloseHandle(fd); + } + if (wasi.is_the_target) { + switch (wasi.fd_close(fd)) { + 0 => return, + else => |err| return unexpectedErrno(err), + } + } + switch (errno(system.close(fd))) { + EBADF => unreachable, // Always a race condition. + EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 + else => return, + } +} -pub const unexpected_error_tracing = builtin.mode == .Debug; -pub const UnexpectedError = error{ - /// The Operating System returned an undocumented error code. +pub const GetRandomError = error{}; + +/// Obtain a series of random bytes. These bytes can be used to seed user-space +/// random number generators or for cryptographic purposes. +/// When linking against libc, this calls the +/// appropriate OS-specific library call. Otherwise it uses the zig standard +/// library implementation. +pub fn getrandom(buf: []u8) GetRandomError!void { + if (windows.is_the_target) { + return windows.RtlGenRandom(buf); + } + if (linux.is_the_target) { + while (true) { + switch (errno(system.getrandom(buf.ptr, buf.len, 0))) { + 0 => return, + EINVAL => unreachable, + EFAULT => unreachable, + EINTR => continue, + ENOSYS => return getRandomBytesDevURandom(buf), + else => |err| return unexpectedErrno(err), + } + } + } + if (wasi.is_the_target) { + switch (os.wasi.random_get(buf.ptr, buf.len)) { + 0 => return, + else => |err| return unexpectedErrno(err), + } + } + return getRandomBytesDevURandom(buf); +} + +fn getRandomBytesDevURandom(buf: []u8) !void { + const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0); + defer close(fd); + + const stream = &os.File.openHandle(fd).inStream().stream; + stream.readNoEof(buf) catch return error.Unexpected; +} + +/// Causes abnormal process termination. +/// If linking against libc, this calls the abort() libc function. Otherwise +/// it raises SIGABRT followed by SIGKILL and finally lo +pub fn abort() noreturn { + @setCold(true); + if (builtin.link_libc) { + system.abort(); + } + if (windows.is_the_target) { + if (builtin.mode == .Debug) { + @breakpoint(); + } + windows.kernel32.ExitProcess(3); + } + if (builtin.os == .uefi) { + // TODO there must be a better thing to do here than loop forever + while (true) {} + } + + raise(SIGABRT); + + // TODO the rest of the implementation of abort() from musl libc here + + raise(SIGKILL); + exit(127); +} + +pub const RaiseError = error{}; + +pub fn raise(sig: u8) RaiseError!void { + if (builtin.link_libc) { + switch (errno(system.raise(sig))) { + 0 => return, + else => |err| return unexpectedErrno(err), + } + } + + if (wasi.is_the_target) { + switch (wasi.proc_raise(SIGABRT)) { + 0 => return, + else => |err| return unexpectedErrno(err), + } + } + + if (windows.is_the_target) { + @compileError("TODO implement std.posix.raise for Windows"); + } + + var set: system.sigset_t = undefined; + system.blockAppSignals(&set); + const tid = system.syscall0(system.SYS_gettid); + const rc = system.syscall2(system.SYS_tkill, tid, sig); + system.restoreSignals(&set); + switch (errno(rc)) { + 0 => return, + else => |err| return unexpectedErrno(err), + } +} + +/// Exits the program cleanly with the specified status code. +pub fn exit(status: u8) noreturn { + if (builtin.link_libc) { + system.exit(status); + } + if (windows.is_the_target) { + windows.kernel32.ExitProcess(status); + } + if (wasi.is_the_target) { + wasi.proc_exit(status); + } + if (linux.is_the_target and !builtin.single_threaded) { + linux.exit_group(status); + } + system.exit(status); +} + +pub const ReadError = error{ + InputOutput, + SystemResources, + IsDir, + OperationAborted, + BrokenPipe, Unexpected, }; -/// This represents the maximum size of a UTF-8 encoded file path. -/// All file system operations which return a path are guaranteed to -/// fit into a UTF-8 encoded array of this length. -/// path being too long if it is this 0long -pub const MAX_PATH_BYTES = switch (builtin.os) { - .linux, .macosx, .ios, .freebsd, .netbsd => posix.PATH_MAX, - // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. - // If it would require 4 UTF-8 bytes, then there would be a surrogate - // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. - // +1 for the null byte at the end, which can be encoded in 1 byte. - .windows => posix.PATH_MAX_WIDE * 3 + 1, - else => @compileError("Unsupported OS"), -}; - -pub const UserInfo = @import("os/get_user_id.zig").UserInfo; -pub const getUserInfo = @import("os/get_user_id.zig").getUserInfo; - -const windows_util = @import("os/windows/util.zig"); -pub const windowsWaitSingle = windows_util.windowsWaitSingle; -pub const windowsWrite = windows_util.windowsWrite; -pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; -pub const windowsOpen = windows_util.windowsOpen; -pub const windowsOpenW = windows_util.windowsOpenW; -pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; - -pub const WindowsCreateIoCompletionPortError = windows_util.WindowsCreateIoCompletionPortError; -pub const windowsCreateIoCompletionPort = windows_util.windowsCreateIoCompletionPort; - -pub const WindowsPostQueuedCompletionStatusError = windows_util.WindowsPostQueuedCompletionStatusError; -pub const windowsPostQueuedCompletionStatus = windows_util.windowsPostQueuedCompletionStatus; - -pub const WindowsWaitResult = windows_util.WindowsWaitResult; -pub const windowsGetQueuedCompletionStatus = windows_util.windowsGetQueuedCompletionStatus; - -pub const WindowsWaitError = windows_util.WaitError; -pub const WindowsOpenError = windows_util.OpenError; -pub const WindowsWriteError = windows_util.WriteError; -pub const WindowsReadError = windows_util.ReadError; - -pub const getAppDataDir = @import("os/get_app_data_dir.zig").getAppDataDir; -pub const GetAppDataDirError = @import("os/get_app_data_dir.zig").GetAppDataDirError; - -pub const getRandomBytes = posix.getrandom; -pub const abort = posix.abort; -pub const exit = posix.exit; -pub const symLink = posix.symlink; -pub const symLinkC = posix.symlinkC; -pub const symLinkW = posix.symlinkW; -pub const deleteFile = posix.unlink; -pub const deleteFileC = posix.unlinkC; -pub const deleteFileW = posix.unlinkW; -pub const rename = posix.rename; -pub const renameC = posix.renameC; -pub const renameW = posix.renameW; -pub const changeCurDir = posix.chdir; -pub const changeCurDirC = posix.chdirC; -pub const changeCurDirW = posix.chdirW; - -const debug = std.debug; -const assert = debug.assert; -const testing = std.testing; - -const c = std.c; - -const mem = std.mem; -const Allocator = mem.Allocator; - -const BufMap = std.BufMap; -const cstr = std.cstr; - -const io = std.io; -const base64 = std.base64; -const ArrayList = std.ArrayList; -const Buffer = std.Buffer; -const math = std.math; - -pub fn getBaseAddress() usize { - switch (builtin.os) { - builtin.Os.linux => { - const base = linuxGetAuxVal(std.elf.AT_BASE); - if (base != 0) { - return base; - } - const phdr = linuxGetAuxVal(std.elf.AT_PHDR); - return phdr - @sizeOf(std.elf.Ehdr); - }, - builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => { - return @ptrToInt(&std.c._mh_execute_header); - }, - builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)), - else => @compileError("Unsupported OS"), - } -} - -/// Caller must free result when done. -/// TODO make this go through libc when we have it -pub fn getEnvMap(allocator: *Allocator) !BufMap { - var result = BufMap.init(allocator); - errdefer result.deinit(); - - if (is_windows) { - const ptr = windows.GetEnvironmentStringsW() orelse return error.OutOfMemory; - defer assert(windows.FreeEnvironmentStringsW(ptr) != 0); - - var i: usize = 0; - while (true) { - if (ptr[i] == 0) return result; - - const key_start = i; - - while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} - const key_w = ptr[key_start..i]; - const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); - errdefer allocator.free(key); - - if (ptr[i] == '=') i += 1; - - const value_start = i; - while (ptr[i] != 0) : (i += 1) {} - const value_w = ptr[value_start..i]; - const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); - errdefer allocator.free(value); - - i += 1; // skip over null byte - - try result.setMove(key, value); - } - } else if (builtin.os == Os.wasi) { - var environ_count: usize = undefined; - var environ_buf_size: usize = undefined; - - const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); - if (environ_sizes_get_ret != os.wasi.ESUCCESS) { - return unexpectedErrorPosix(environ_sizes_get_ret); - } - - // TODO: Verify that the documentation is incorrect - // https://github.com/WebAssembly/WASI/issues/27 - var environ = try allocator.alloc(?[*]u8, environ_count + 1); - defer allocator.free(environ); - var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size); - defer allocator.free(environ_buf); - - const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); - if (environ_get_ret != os.wasi.ESUCCESS) { - return unexpectedErrorPosix(environ_get_ret); - } - - for (environ) |env| { - if (env) |ptr| { - const pair = mem.toSlice(u8, ptr); - var parts = mem.separate(pair, "="); - const key = parts.next().?; - const value = parts.next().?; - try result.set(key, value); - } - } - return result; - } else { - for (posix.environ) |ptr| { - var line_i: usize = 0; - while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} - const key = ptr[0..line_i]; - - var end_i: usize = line_i; - while (ptr[end_i] != 0) : (end_i += 1) {} - const value = ptr[line_i + 1 .. end_i]; - - try result.set(key, value); - } - return result; - } -} - -test "os.getEnvMap" { - var env = try getEnvMap(std.debug.global_allocator); - defer env.deinit(); -} - -pub const GetEnvVarOwnedError = error{ - OutOfMemory, - EnvironmentVariableNotFound, - - /// See https://github.com/ziglang/zig/issues/1774 - InvalidUtf8, -}; - -/// Caller must free returned memory. -/// TODO make this go through libc when we have it -pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (is_windows) { - const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); - defer allocator.free(key_with_null); - - var buf = try allocator.alloc(u16, 256); - defer allocator.free(buf); - - while (true) { - const windows_buf_len = math.cast(windows.DWORD, buf.len) catch return error.OutOfMemory; - const result = windows.GetEnvironmentVariableW(key_with_null.ptr, buf.ptr, windows_buf_len); - - if (result == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound, - else => { - windows.unexpectedError(err) catch {}; - return error.EnvironmentVariableNotFound; - }, - }; - } - - if (result > buf.len) { - buf = try allocator.realloc(buf, result); - continue; - } - - return std.unicode.utf16leToUtf8Alloc(allocator, buf) catch |err| switch (err) { - error.DanglingSurrogateHalf => return error.InvalidUtf8, - error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8, - error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8, - error.OutOfMemory => return error.OutOfMemory, - }; - } - } else { - const result = getEnvPosix(key) orelse return error.EnvironmentVariableNotFound; - return mem.dupe(allocator, u8, result); - } -} - -test "os.getEnvVarOwned" { - var ga = debug.global_allocator; - testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); -} - -/// The result is a slice of `out_buffer`, from index `0`. -pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { - return posix.getcwd(out_buffer); -} - -/// Caller must free the returned memory. -pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { - var buf: [os.MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try posix.getcwd(&buf)); -} - -test "getCwdAlloc" { - // at least call it so it gets compiled - var buf: [1000]u8 = undefined; - const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator; - _ = getCwdAlloc(allocator) catch {}; -} - -// here we replace the standard +/ with -_ so that it can be used in a file name -const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); - -/// TODO remove the allocator requirement from this API -pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { - if (symLink(existing_path, new_path)) { - return; - } else |err| switch (err) { - error.PathAlreadyExists => {}, - else => return err, // TODO zig should know this set does not include PathAlreadyExists +/// Returns the number of bytes that were read, which can be less than +/// buf.len. If 0 bytes were read, that means EOF. +/// This function is for blocking file descriptors only. For non-blocking, see +/// `readAsync`. +pub fn read(fd: fd_t, buf: []u8) ReadError!usize { + if (windows.is_the_target and !builtin.link_libc) { + return windows.ReadFile(fd, buf); } - const dirname = os.path.dirname(new_path) orelse "."; + if (wasi.is_the_target and !builtin.link_libc) { + const iovs = [1]was.iovec_t{wasi.iovec_t{ + .buf = buf.ptr, + .buf_len = buf.len, + }}; - var rand_buf: [12]u8 = undefined; - const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); - defer allocator.free(tmp_path); - mem.copy(u8, tmp_path[0..], dirname); - tmp_path[dirname.len] = os.path.sep; - while (true) { - try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); - - if (symLink(existing_path, tmp_path)) { - return rename(tmp_path, new_path); - } else |err| switch (err) { - error.PathAlreadyExists => continue, - else => return err, // TODO zig should know this set does not include PathAlreadyExists - } - } -} - -/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is -/// merged and readily available, -/// there is a possibility of power loss or application termination leaving temporary files present -/// in the same directory as dest_path. -/// Destination file will have the same mode as the source file. -pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { - var in_file = try os.File.openRead(source_path); - defer in_file.close(); - - const mode = try in_file.mode(); - const in_stream = &in_file.inStream().stream; - - var atomic_file = try AtomicFile.init(dest_path, mode); - defer atomic_file.deinit(); - - var buf: [page_size]u8 = undefined; - while (true) { - const amt = try in_stream.readFull(buf[0..]); - try atomic_file.file.write(buf[0..amt]); - if (amt != buf.len) { - return atomic_file.finish(); - } - } -} - -/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is -/// merged and readily available, -/// there is a possibility of power loss or application termination leaving temporary files present -pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { - var in_file = try os.File.openRead(source_path); - defer in_file.close(); - - var atomic_file = try AtomicFile.init(dest_path, mode); - defer atomic_file.deinit(); - - var buf: [page_size]u8 = undefined; - while (true) { - const amt = try in_file.read(buf[0..]); - try atomic_file.file.write(buf[0..amt]); - if (amt != buf.len) { - return atomic_file.finish(); - } - } -} - -pub const AtomicFile = struct { - file: os.File, - tmp_path_buf: [MAX_PATH_BYTES]u8, - dest_path: []const u8, - finished: bool, - - const InitError = os.File.OpenError; - - /// dest_path must remain valid for the lifetime of AtomicFile - /// call finish to atomically replace dest_path with contents - /// TODO once we have null terminated pointers, use the - /// openWriteNoClobberN function - pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { - const dirname = os.path.dirname(dest_path); - var rand_buf: [12]u8 = undefined; - const dirname_component_len = if (dirname) |d| d.len + 1 else 0; - const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); - const tmp_path_len = dirname_component_len + encoded_rand_len; - var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined; - if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong; - - if (dirname) |dir| { - mem.copy(u8, tmp_path_buf[0..], dir); - tmp_path_buf[dir.len] = os.path.sep; - } - - tmp_path_buf[tmp_path_len] = 0; - - while (true) { - try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf); - - const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) { - error.PathAlreadyExists => continue, - // TODO zig should figure out that this error set does not include PathAlreadyExists since - // it is handled in the above switch - else => return err, - }; - - return AtomicFile{ - .file = file, - .tmp_path_buf = tmp_path_buf, - .dest_path = dest_path, - .finished = false, - }; + var nread: usize = undefined; + switch (fd_read(fd, &iovs, iovs.len, &nread)) { + 0 => return nread, + else => |err| return unexpectedErrno(err), } } - /// always call deinit, even after successful finish() - pub fn deinit(self: *AtomicFile) void { - if (!self.finished) { - self.file.close(); - deleteFileC(&self.tmp_path_buf) catch {}; - self.finished = true; - } - } + // Linux can return EINVAL when read amount is > 0x7ffff000 + // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274 + // TODO audit this. Shawn Landden says that this is not actually true. + // if this logic should stay, move it to std.os.linux.sys + const max_buf_len = 0x7ffff000; - pub fn finish(self: *AtomicFile) !void { - assert(!self.finished); - self.file.close(); - self.finished = true; - if (is_posix) { - const dest_path_c = try toPosixPath(self.dest_path); - return renameC(&self.tmp_path_buf, &dest_path_c); - } else if (is_windows) { - const dest_path_w = try posix.sliceToPrefixedFileW(self.dest_path); - const tmp_path_w = try posix.cStrToPrefixedFileW(&self.tmp_path_buf); - return renameW(&tmp_path_w, &dest_path_w); - } else { - @compileError("Unsupported OS"); - } - } -}; - -const default_new_dir_mode = 0o755; - -/// Create a new directory. -pub fn makeDir(dir_path: []const u8) !void { - return posix.mkdir(dir_path, default_new_dir_mode); -} - -/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string. -pub fn makeDirC(dir_path: [*]const u8) !void { - return posix.mkdirC(dir_path, default_new_dir_mode); -} - -/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string. -pub fn makeDirW(dir_path: [*]const u16) !void { - return posix.mkdirW(dir_path, default_new_dir_mode); -} - -/// Calls makeDir recursively to make an entire path. Returns success if the path -/// already exists and is a directory. -/// This function is not atomic, and if it returns an error, the file system may -/// have been modified regardless. -/// TODO determine if we can remove the allocator requirement from this function -pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { - const resolved_path = try path.resolve(allocator, [][]const u8{full_path}); - defer allocator.free(resolved_path); - - var end_index: usize = resolved_path.len; - while (true) { - makeDir(resolved_path[0..end_index]) catch |err| switch (err) { - error.PathAlreadyExists => { - // TODO stat the file and return an error if it's not a directory - // this is important because otherwise a dangling symlink - // could cause an infinite loop - if (end_index == resolved_path.len) return; + var index: usize = 0; + while (index < buf.len) { + const want_to_read = math.min(buf.len - index, usize(max_buf_len)); + const rc = system.read(fd, buf.ptr + index, want_to_read); + switch (errno(rc)) { + 0 => { + index += rc; + if (rc == want_to_read) continue; + // Read returned less than buf.len. + return index; }, - error.FileNotFound => { - // march end_index backward until next path component - while (true) { - end_index -= 1; - if (os.path.isSep(resolved_path[end_index])) break; - } + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking reads. + EBADF => unreachable, // Always a race condition. + EIO => return error.InputOutput, + EISDIR => return error.IsDir, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } + return index; +} + +/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. +/// This function is for blocking file descriptors only. For non-blocking, see +/// `preadvAsync`. +pub fn preadv(fd: fd_t, iov: [*]const iovec, count: usize, offset: u64) ReadError!usize { + if (os.darwin.is_the_target) { + // Darwin does not have preadv but it does have pread. + var off: usize = 0; + var iov_i: usize = 0; + var inner_off: usize = 0; + while (true) { + const v = iov[iov_i]; + const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); + const err = darwin.getErrno(rc); + switch (err) { + 0 => { + off += rc; + inner_off += rc; + if (inner_off == v.iov_len) { + iov_i += 1; + inner_off = 0; + if (iov_i == count) { + return off; + } + } + if (rc == 0) return off; // EOF + continue; + }, + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + ESPIPE => unreachable, // fd is not seekable + EAGAIN => unreachable, // This function is for blocking reads. + EBADF => unreachable, // always a race condition + EIO => return error.InputOutput, + EISDIR => return error.IsDir, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + else => return unexpectedErrno(err), + } + } + } + while (true) { + const rc = system.preadv(fd, iov, count, offset); + switch (errno(rc)) { + 0 => return rc, + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking reads. + EBADF => unreachable, // always a race condition + EIO => return error.InputOutput, + EISDIR => return error.IsDir, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const WriteError = error{ + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + AccessDenied, + BrokenPipe, + SystemResources, + OperationAborted, + Unexpected, +}; + +/// Write to a file descriptor. Keeps trying if it gets interrupted. +/// This function is for blocking file descriptors only. For non-blocking, see +/// `writeAsync`. +pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { + if (windows.is_the_target and !builtin.link_libc) { + return windows.WriteFile(fd, bytes); + } + + if (wasi.is_the_target and !builtin.link_libc) { + const ciovs = [1]wasi.ciovec_t{wasi.ciovec_t{ + .buf = bytes.ptr, + .buf_len = bytes.len, + }}; + var nwritten: usize = undefined; + switch (fd_write(fd, &ciovs, ciovs.len, &nwritten)) { + 0 => return, + else => |err| return unexpectedErrno(err), + } + } + + // Linux can return EINVAL when write amount is > 0x7ffff000 + // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856 + // TODO audit this. Shawn Landden says that this is not actually true. + // if this logic should stay, move it to std.os.linux.sys + const max_bytes_len = 0x7ffff000; + + var index: usize = 0; + while (index < bytes.len) { + const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len)); + const rc = system.write(fd, bytes.ptr + index, amt_to_write); + switch (errno(rc)) { + 0 => { + index += rc; continue; }, - else => return err, - }; - if (end_index == resolved_path.len) return; - // march end_index forward until next path component - while (true) { - end_index += 1; - if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break; + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking writes. + EBADF => unreachable, // Always a race condition. + EDESTADDRREQ => unreachable, // `connect` was never called. + EDQUOT => return error.DiskQuota, + EFBIG => return error.FileTooBig, + EIO => return error.InputOutput, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EPIPE => return error.BrokenPipe, + else => |err| return unexpectedErrno(err), } } } -/// Returns `error.DirNotEmpty` if the directory is not empty. -/// To delete a directory recursively, see `deleteTree`. -pub fn deleteDir(dir_path: []const u8) DeleteDirError!void { - return posix.rmdir(dir_path); +/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted. +/// This function is for blocking file descriptors only. For non-blocking, see +/// `pwritevAsync`. +pub fn pwritev(fd: fd_t, iov: [*]const iovec_const, count: usize, offset: u64) WriteError!void { + if (darwin.is_the_target) { + // Darwin does not have pwritev but it does have pwrite. + var off: usize = 0; + var iov_i: usize = 0; + var inner_off: usize = 0; + while (true) { + const v = iov[iov_i]; + const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); + const err = darwin.getErrno(rc); + switch (err) { + 0 => { + off += rc; + inner_off += rc; + if (inner_off == v.iov_len) { + iov_i += 1; + inner_off = 0; + if (iov_i == count) { + return; + } + } + continue; + }, + EINTR => continue, + ESPIPE => unreachable, // `fd` is not seekable. + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking writes. + EBADF => unreachable, // Always a race condition. + EDESTADDRREQ => unreachable, // `connect` was never called. + EDQUOT => return error.DiskQuota, + EFBIG => return error.FileTooBig, + EIO => return error.InputOutput, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EPIPE => return error.BrokenPipe, + else => return unexpectedErrno(err), + } + } + } + + while (true) { + const rc = system.pwritev(fd, iov, count, offset); + switch (errno(rc)) { + 0 => return, + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking writes. + EBADF => unreachable, // Always a race condition. + EDESTADDRREQ => unreachable, // `connect` was never called. + EDQUOT => return error.DiskQuota, + EFBIG => return error.FileTooBig, + EIO => return error.InputOutput, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EPIPE => return error.BrokenPipe, + else => |err| return unexpectedErrno(err), + } + } } -/// Same as `deleteDir` except the parameter is a null-terminated UTF8-encoded string. -pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void { - return posix.rmdirC(dir_path); -} - -/// Same as `deleteDir` except the parameter is a null-terminated UTF16LE-encoded string. -pub fn deleteDirW(dir_path: [*]const u16) DeleteDirError!void { - return posix.rmdirW(dir_path); -} - -/// Whether ::full_path describes a symlink, file, or directory, this function -/// removes it. If it cannot be removed because it is a non-empty directory, -/// this function recursively removes its entries and then tries again. -const DeleteTreeError = error{ - OutOfMemory, +pub const OpenError = error{ AccessDenied, FileTooBig, IsDir, @@ -547,16 +452,368 @@ const DeleteTreeError = error{ NameTooLong, SystemFdQuotaExceeded, NoDevice, + FileNotFound, SystemResources, NoSpaceLeft, + NotDir, PathAlreadyExists, + DeviceBusy, + Unexpected, +}; + +/// Open and possibly create a file. Keeps trying if it gets interrupted. +/// `file_path` needs to be copied in memory to add a null terminating byte. +/// See also `openC`. +pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t { + const file_path_c = try toPosixPath(file_path); + return openC(&file_path_c, flags, perm); +} + +/// Open and possibly create a file. Keeps trying if it gets interrupted. +/// See also `open`. +/// TODO https://github.com/ziglang/zig/issues/265 +pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t { + while (true) { + const rc = system.open(file_path, flags, perm); + switch (errno(rc)) { + 0 => return @intCast(fd_t, rc), + EINTR => continue, + + EFAULT => unreachable, + EINVAL => unreachable, + EACCES => return error.AccessDenied, + EFBIG => return error.FileTooBig, + EOVERFLOW => return error.FileTooBig, + EISDIR => return error.IsDir, + ELOOP => return error.SymLinkLoop, + EMFILE => return error.ProcessFdQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENFILE => return error.SystemFdQuotaExceeded, + ENODEV => return error.NoDevice, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EPERM => return error.AccessDenied, + EEXIST => return error.PathAlreadyExists, + EBUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { + while (true) { + switch (errno(system.dup2(old_fd, new_fd))) { + 0 => return, + EBUSY, EINTR => continue, + EMFILE => return error.ProcessFdQuotaExceeded, + EINVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } +} + +/// This function must allocate memory to add a null terminating bytes on path and each arg. +/// It must also convert to KEY=VALUE\0 format for environment variables, and include null +/// pointers after the args and after the environment variables. +/// `argv[0]` is the executable path. +/// This function also uses the PATH environment variable to get the full path to the executable. +/// TODO provide execveC which does not take an allocator +pub fn execve(allocator: *Allocator, argv: []const []const u8, env_map: *const BufMap) !void { + const argv_buf = try allocator.alloc(?[*]u8, argv.len + 1); + mem.set(?[*]u8, argv_buf, null); + defer { + for (argv_buf) |arg| { + const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break; + allocator.free(arg_buf); + } + allocator.free(argv_buf); + } + for (argv) |arg, i| { + const arg_buf = try allocator.alloc(u8, arg.len + 1); + @memcpy(arg_buf.ptr, arg.ptr, arg.len); + arg_buf[arg.len] = 0; + + argv_buf[i] = arg_buf.ptr; + } + argv_buf[argv.len] = null; + + const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); + defer freeNullDelimitedEnvMap(allocator, envp_buf); + + const exe_path = argv[0]; + if (mem.indexOfScalar(u8, exe_path, '/') != null) { + return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr))); + } + + const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; + // PATH.len because it is >= the largest search_path + // +1 for the / to join the search path and exe_path + // +1 for the null terminating byte + const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2); + defer allocator.free(path_buf); + var it = mem.tokenize(PATH, ":"); + var seen_eacces = false; + var err: usize = undefined; + while (it.next()) |search_path| { + mem.copy(u8, path_buf, search_path); + path_buf[search_path.len] = '/'; + mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path); + path_buf[search_path.len + exe_path.len + 1] = 0; + err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)); + assert(err > 0); + if (err == EACCES) { + seen_eacces = true; + } else if (err != ENOENT) { + return execveErrnoToErr(err); + } + } + if (seen_eacces) { + err = EACCES; + } + return execveErrnoToErr(err); +} + +pub fn createNullDelimitedEnvMap(allocator: *Allocator, env_map: *const BufMap) ![]?[*]u8 { + const envp_count = env_map.count(); + const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1); + mem.set(?[*]u8, envp_buf, null); + errdefer freeNullDelimitedEnvMap(allocator, envp_buf); + { + var it = env_map.iterator(); + var i: usize = 0; + while (it.next()) |pair| : (i += 1) { + const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2); + @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len); + env_buf[pair.key.len] = '='; + @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); + env_buf[env_buf.len - 1] = 0; + + envp_buf[i] = env_buf.ptr; + } + assert(i == envp_count); + } + assert(envp_buf[envp_count] == null); + return envp_buf; +} + +pub fn freeNullDelimitedEnvMap(allocator: *Allocator, envp_buf: []?[*]u8) void { + for (envp_buf) |env| { + const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break; + allocator.free(env_buf); + } + allocator.free(envp_buf); +} + +pub const ExecveError = error{ + SystemResources, + AccessDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + + Unexpected, +}; + +fn execveErrnoToErr(err: usize) ExecveError { + assert(err > 0); + switch (err) { + EFAULT => unreachable, + E2BIG => return error.SystemResources, + EMFILE => return error.ProcessFdQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENFILE => return error.SystemFdQuotaExceeded, + ENOMEM => return error.SystemResources, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EINVAL => return error.InvalidExe, + ENOEXEC => return error.InvalidExe, + EIO => return error.FileSystem, + ELOOP => return error.FileSystem, + EISDIR => return error.IsDir, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.NotDir, + ETXTBSY => return error.FileBusy, + else => return unexpectedErrno(err), + } +} + +/// Get an environment variable. +/// See also `getenvC`. +/// TODO make this go through libc when we have it +pub fn getenv(key: []const u8) ?[]const u8 { + for (environ) |ptr| { + var line_i: usize = 0; + while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} + const this_key = ptr[0..line_i]; + if (!mem.eql(u8, key, this_key)) continue; + + var end_i: usize = line_i; + while (ptr[end_i] != 0) : (end_i += 1) {} + const this_value = ptr[line_i + 1 .. end_i]; + + return this_value; + } + return null; +} + +/// Get an environment variable with a null-terminated name. +/// See also `getenv`. +/// TODO https://github.com/ziglang/zig/issues/265 +pub fn getenvC(key: [*]const u8) ?[]const u8 { + if (builtin.link_libc) { + const value = system.getenv(key) orelse return null; + return mem.toSliceConst(u8, value); + } + return getenv(mem.toSliceConst(u8, key)); +} + +/// See std.elf for the constants. +pub fn getauxval(index: usize) usize { + if (builtin.link_libc) { + return usize(system.getauxval(index)); + } else if (linux.elf_aux_maybe) |auxv| { + var i: usize = 0; + while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { + if (auxv[i].a_type == index) + return auxv[i].a_un.a_val; + } + } + return 0; +} + +pub const GetCwdError = error{ + NameTooLong, + CurrentWorkingDirectoryUnlinked, + Unexpected, +}; + +/// The result is a slice of out_buffer, indexed from 0. +pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { + if (windows.is_the_target and !builtin.link_libc) { + return windows.GetCurrentDirectory(out_buffer); + } + + const err = if (builtin.link_libc) blk: { + break :blk if (system.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else system._errno().*; + } else blk: { + break :blk errno(system.getcwd(out_buffer, out_buffer.len)); + }; + switch (err) { + 0 => return mem.toSlice(u8, out_buffer), + EFAULT => unreachable, + EINVAL => unreachable, + ENOENT => return error.CurrentWorkingDirectoryUnlinked, + ERANGE => return error.NameTooLong, + else => |err| return unexpectedErrno(err), + } +} + +pub const SymLinkError = error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + FileNotFound, + SystemResources, + NoSpaceLeft, ReadOnlyFileSystem, NotDir, + NameTooLong, + InvalidUtf8, + BadPathName, + Unexpected, +}; + +/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// If `sym_link_path` exists, it will not be overwritten. +/// See also `symlinkC` and `symlinkW`. +pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { + if (windows.is_the_target and !builtin.link_libc) { + const target_path_w = try windows.sliceToPrefixedFileW(target_path); + const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); + return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); + } else { + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkC(&target_path_c, &sym_link_path_c); + } +} + +/// This is the same as `symlink` except the parameters are null-terminated pointers. +/// See also `symlink`. +pub fn symlinkC(target_path: [*]const u8, sym_link_path: [*]const u8) SymLinkError!void { + if (windows.is_the_target and !builtin.link_libc) { + const target_path_w = try windows.cStrToPrefixedFileW(target_path); + const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); + return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); + } + switch (errno(system.symlink(target_path, sym_link_path))) { + 0 => return, + EFAULT => unreachable, + EINVAL => unreachable, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.NotDir, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkatC(target_path_c, newdirfd, sym_link_path_c); +} + +pub fn symlinkatC(target_path: [*]const u8, newdirfd: fd_t, sym_link_path: [*]const u8) SymLinkError!void { + switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { + 0 => return, + EFAULT => unreachable, + EINVAL => unreachable, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.NotDir, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub const UnlinkError = error{ FileNotFound, - FileSystem, + AccessDenied, FileBusy, - DirNotEmpty, - DeviceBusy, + FileSystem, + IsDir, + SymLinkLoop, + NameTooLong, + NotDir, + SystemResources, + ReadOnlyFileSystem, + Unexpected, /// On Windows, file paths must be valid Unicode. InvalidUtf8, @@ -564,1222 +821,1507 @@ const DeleteTreeError = error{ /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' BadPathName, - - Unexpected, }; -/// TODO determine if we can remove the allocator requirement -pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { - start_over: while (true) { - var got_access_denied = false; - // First, try deleting the item as a file. This way we don't follow sym links. - if (deleteFile(full_path)) { - return; - } else |err| switch (err) { - error.FileNotFound => return, - error.IsDir => {}, - error.AccessDenied => got_access_denied = true, - - error.InvalidUtf8, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.NotDir, - error.FileSystem, - error.FileBusy, - error.BadPathName, - error.Unexpected, - => return err, - } - { - var dir = Dir.open(allocator, full_path) catch |err| switch (err) { - error.NotDir => { - if (got_access_denied) { - return error.AccessDenied; - } - continue :start_over; - }, - - error.OutOfMemory, - error.AccessDenied, - error.FileTooBig, - error.IsDir, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.FileNotFound, - error.SystemResources, - error.NoSpaceLeft, - error.PathAlreadyExists, - error.Unexpected, - error.InvalidUtf8, - error.BadPathName, - error.DeviceBusy, - => return err, - }; - defer dir.close(); - - var full_entry_buf = ArrayList(u8).init(allocator); - defer full_entry_buf.deinit(); - - while (try dir.next()) |entry| { - try full_entry_buf.resize(full_path.len + entry.name.len + 1); - const full_entry_path = full_entry_buf.toSlice(); - mem.copy(u8, full_entry_path, full_path); - full_entry_path[full_path.len] = path.sep; - mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); - - try deleteTree(allocator, full_entry_path); - } - } - return deleteDir(full_path); +/// Delete a name and possibly the file it refers to. +/// See also `unlinkC`. +pub fn unlink(file_path: []const u8) UnlinkError!void { + if (windows.is_the_target and !builtin.link_libc) { + const file_path_w = try windows.sliceToPrefixedFileW(file_path); + return windows.DeleteFileW(&file_path_w); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkC(&file_path_c); } } -pub const Dir = struct { - handle: Handle, - allocator: *Allocator, - - pub const Handle = switch (builtin.os) { - Os.macosx, Os.ios, Os.freebsd, Os.netbsd => struct { - fd: i32, - seek: i64, - buf: []u8, - index: usize, - end_index: usize, - }, - Os.linux => struct { - fd: i32, - buf: []u8, - index: usize, - end_index: usize, - }, - Os.windows => struct { - handle: windows.HANDLE, - find_file_data: windows.WIN32_FIND_DATAW, - first: bool, - name_data: [256]u8, - }, - else => @compileError("unimplemented"), - }; - - pub const Entry = struct { - name: []const u8, - kind: Kind, - - pub const Kind = enum { - BlockDevice, - CharacterDevice, - Directory, - NamedPipe, - SymLink, - File, - UnixDomainSocket, - Whiteout, - Unknown, - }; - }; - - pub const OpenError = error{ - FileNotFound, - NotDir, - AccessDenied, - FileTooBig, - IsDir, - SymLinkLoop, - ProcessFdQuotaExceeded, - NameTooLong, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - PathAlreadyExists, - OutOfMemory, - InvalidUtf8, - BadPathName, - DeviceBusy, - - Unexpected, - }; - - /// TODO remove the allocator requirement from this API - pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { - return Dir{ - .allocator = allocator, - .handle = switch (builtin.os) { - Os.windows => blk: { - var find_file_data: windows.WIN32_FIND_DATAW = undefined; - const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data); - break :blk Handle{ - .handle = handle, - .find_file_data = find_file_data, // TODO guaranteed copy elision - .first = true, - .name_data = undefined, - }; - }, - Os.macosx, Os.ios, Os.freebsd, Os.netbsd => Handle{ - .fd = try posixOpen( - dir_path, - posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, - 0, - ), - .seek = 0, - .index = 0, - .end_index = 0, - .buf = []u8{}, - }, - Os.linux => Handle{ - .fd = try posixOpen( - dir_path, - posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, - 0, - ), - .index = 0, - .end_index = 0, - .buf = []u8{}, - }, - else => @compileError("unimplemented"), - }, - }; +/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string. +pub fn unlinkC(file_path: [*]const u8) UnlinkError!void { + if (windows.is_the_target and !builtin.link_libc) { + const file_path_w = try windows.cStrToPrefixedFileW(file_path); + return windows.DeleteFileW(&file_path_w); } - - pub fn close(self: *Dir) void { - switch (builtin.os) { - Os.windows => { - _ = windows.FindClose(self.handle.handle); - }, - Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => { - self.allocator.free(self.handle.buf); - os.close(self.handle.fd); - }, - else => @compileError("unimplemented"), - } + switch (errno(system.unlink(file_path))) { + 0 => return, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EBUSY => return error.FileBusy, + EFAULT => unreachable, + EINVAL => unreachable, + EIO => return error.FileSystem, + EISDIR => return error.IsDir, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.NotDir, + ENOMEM => return error.SystemResources, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), } +} - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to next, as well as when this `Dir` is deinitialized. - pub fn next(self: *Dir) !?Entry { - switch (builtin.os) { - Os.linux => return self.nextLinux(), - Os.macosx, Os.ios => return self.nextDarwin(), - Os.windows => return self.nextWindows(), - Os.freebsd => return self.nextFreebsd(), - Os.netbsd => return self.nextFreebsd(), - else => @compileError("unimplemented"), - } +const RenameError = error{ + AccessDenied, + FileBusy, + DiskQuota, + IsDir, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + NotDir, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + RenameAcrossMountPoints, + Unexpected, +}; + +/// Change the name or location of a file. +pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { + if (windows.is_the_target and !builtin.link_libc) { + const old_path_w = try windows.sliceToPrefixedFileW(old_path); + const new_path_w = try windows.sliceToPrefixedFileW(new_path); + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; + return windows.MoveFileExW(&old_path_w, &new_path_w, flags); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameC(&old_path_c, &new_path_c); } +} - fn nextDarwin(self: *Dir) !?Entry { - start_over: while (true) { - if (self.handle.index >= self.handle.end_index) { - if (self.handle.buf.len == 0) { - self.handle.buf = try self.allocator.alloc(u8, page_size); - } - - while (true) { - const result = system.__getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); - if (result == 0) return null; - if (result < 0) { - switch (system.getErrno(result)) { - posix.EBADF => unreachable, - posix.EFAULT => unreachable, - posix.ENOTDIR => unreachable, - posix.EINVAL => { - self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); - continue; - }, - else => return unexpectedErrorPosix(err), - } - } - self.handle.index = 0; - self.handle.end_index = @intCast(usize, result); - break; - } - } - const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); - const next_index = self.handle.index + darwin_entry.d_reclen; - self.handle.index = next_index; - - const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; - - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - continue :start_over; - } - - const entry_kind = switch (darwin_entry.d_type) { - posix.DT_BLK => Entry.Kind.BlockDevice, - posix.DT_CHR => Entry.Kind.CharacterDevice, - posix.DT_DIR => Entry.Kind.Directory, - posix.DT_FIFO => Entry.Kind.NamedPipe, - posix.DT_LNK => Entry.Kind.SymLink, - posix.DT_REG => Entry.Kind.File, - posix.DT_SOCK => Entry.Kind.UnixDomainSocket, - posix.DT_WHT => Entry.Kind.Whiteout, - else => Entry.Kind.Unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } +/// Same as `rename` except the parameters are null-terminated byte arrays. +pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void { + if (windows.is_the_target and !builtin.link_libc) { + const old_path_w = try windows.cStrToPrefixedFileW(old_path); + const new_path_w = try windows.cStrToPrefixedFileW(new_path); + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; + return windows.MoveFileExW(&old_path_w, &new_path_w, flags); } - - fn nextWindows(self: *Dir) !?Entry { - while (true) { - if (self.handle.first) { - self.handle.first = false; - } else { - if (!try posix.FindNextFile(self.handle.handle, &self.handle.find_file_data)) - return null; - } - const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); - if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{ '.', '.' })) - continue; - // Trust that Windows gives us valid UTF-16LE - const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; - const name_utf8 = self.handle.name_data[0..name_utf8_len]; - const kind = blk: { - const attrs = self.handle.find_file_data.dwFileAttributes; - if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; - if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; - if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File; - break :blk Entry.Kind.Unknown; - }; - return Entry{ - .name = name_utf8, - .kind = kind, - }; - } + switch (errno(system.rename(old_path, new_path))) { + 0 => return, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EBUSY => return error.FileBusy, + EDQUOT => return error.DiskQuota, + EFAULT => unreachable, + EINVAL => unreachable, + EISDIR => return error.IsDir, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.NotDir, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + EEXIST => return error.PathAlreadyExists, + ENOTEMPTY => return error.PathAlreadyExists, + EROFS => return error.ReadOnlyFileSystem, + EXDEV => return error.RenameAcrossMountPoints, + else => |err| return unexpectedErrno(err), } +} - fn nextLinux(self: *Dir) !?Entry { - start_over: while (true) { - if (self.handle.index >= self.handle.end_index) { - if (self.handle.buf.len == 0) { - self.handle.buf = try self.allocator.alloc(u8, page_size); - } +pub const MakeDirError = error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + ReadOnlyFileSystem, + Unexpected, +}; - while (true) { - const result = posix.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len); - const err = posix.getErrno(result); - if (err > 0) { - switch (err) { - posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, - posix.EINVAL => { - self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); - continue; - }, - else => return unexpectedErrorPosix(err), - } - } - if (result == 0) return null; - self.handle.index = 0; - self.handle.end_index = result; - break; - } - } - const linux_entry = @ptrCast(*align(1) posix.dirent64, &self.handle.buf[self.handle.index]); - const next_index = self.handle.index + linux_entry.d_reclen; - self.handle.index = next_index; - - const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name)); - - // skip . and .. entries - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - continue :start_over; - } - - const entry_kind = switch (linux_entry.d_type) { - posix.DT_BLK => Entry.Kind.BlockDevice, - posix.DT_CHR => Entry.Kind.CharacterDevice, - posix.DT_DIR => Entry.Kind.Directory, - posix.DT_FIFO => Entry.Kind.NamedPipe, - posix.DT_LNK => Entry.Kind.SymLink, - posix.DT_REG => Entry.Kind.File, - posix.DT_SOCK => Entry.Kind.UnixDomainSocket, - else => Entry.Kind.Unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } +/// Create a directory. +/// `mode` is ignored on Windows. +pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); + return windows.CreateDirectoryW(&dir_path_w, null); + } else { + const dir_path_c = try toPosixPath(dir_path); + return mkdirC(&dir_path_c, mode); } +} - fn nextFreebsd(self: *Dir) !?Entry { - start_over: while (true) { - if (self.handle.index >= self.handle.end_index) { - if (self.handle.buf.len == 0) { - self.handle.buf = try self.allocator.alloc(u8, page_size); - } - - while (true) { - const result = posix.getdirentries(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); - const err = posix.getErrno(result); - if (err > 0) { - switch (err) { - posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, - posix.EINVAL => { - self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); - continue; - }, - else => return unexpectedErrorPosix(err), - } - } - if (result == 0) return null; - self.handle.index = 0; - self.handle.end_index = result; - break; - } - } - const freebsd_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); - const next_index = self.handle.index + freebsd_entry.d_reclen; - self.handle.index = next_index; - - const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; - - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - continue :start_over; - } - - const entry_kind = switch (freebsd_entry.d_type) { - posix.DT_BLK => Entry.Kind.BlockDevice, - posix.DT_CHR => Entry.Kind.CharacterDevice, - posix.DT_DIR => Entry.Kind.Directory, - posix.DT_FIFO => Entry.Kind.NamedPipe, - posix.DT_LNK => Entry.Kind.SymLink, - posix.DT_REG => Entry.Kind.File, - posix.DT_SOCK => Entry.Kind.UnixDomainSocket, - posix.DT_WHT => Entry.Kind.Whiteout, - else => Entry.Kind.Unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } +/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. +pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); + return windows.CreateDirectoryW(&dir_path_w, null); } + switch (errno(system.mkdir(dir_path, mode))) { + 0 => return, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub const DeleteDirError = error{ + AccessDenied, + FileBusy, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + DirNotEmpty, + ReadOnlyFileSystem, + InvalidUtf8, + BadPathName, + Unexpected, +}; + +/// Deletes an empty directory. +pub fn rmdir(dir_path: []const u8) DeleteDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); + return windows.RemoveDirectoryW(&dir_path_w); + } else { + const dir_path_c = try toPosixPath(dir_path); + return rmdirC(&dir_path_c); + } +} + +/// Same as `rmdir` except the parameter is null-terminated. +pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); + return windows.RemoveDirectoryW(&dir_path_w); + } + switch (errno(system.rmdir(dir_path))) { + 0 => return, + EACCES => return error.AccessDenied, + EPERM => return error.AccessDenied, + EBUSY => return error.FileBusy, + EFAULT => unreachable, + EINVAL => unreachable, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOTDIR => return error.NotDir, + EEXIST => return error.DirNotEmpty, + ENOTEMPTY => return error.DirNotEmpty, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub const ChangeCurDirError = error{ + AccessDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + Unexpected, +}; + +/// Changes the current working directory of the calling process. +/// `dir_path` is recommended to be a UTF-8 encoded string. +pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); + @compileError("TODO implement chdir for Windows"); + } else { + const dir_path_c = try toPosixPath(dir_path); + return chdirC(&dir_path_c); + } +} + +/// Same as `chdir` except the parameter is null-terminated. +pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void { + if (windows.is_the_target and !builtin.link_libc) { + const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); + @compileError("TODO implement chdir for Windows"); + } + switch (errno(system.chdir(dir_path))) { + 0 => return, + EACCES => return error.AccessDenied, + EFAULT => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOTDIR => return error.NotDir, + else => |err| return unexpectedErrno(err), + } +} + +pub const ReadLinkError = error{ + AccessDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + Unexpected, }; /// Read value of a symbolic link. -/// The return value is a slice of buffer, from index `0`. -pub fn readLink(buffer: *[posix.PATH_MAX]u8, pathname: []const u8) ![]u8 { - return posix.readlink(pathname, buffer); -} - -/// Same as `readLink`, except the `pathname` parameter is null-terminated. -pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { - return posix.readlinkC(pathname, buffer); -} - -pub const ArgIteratorPosix = struct { - index: usize, - count: usize, - - pub fn init() ArgIteratorPosix { - return ArgIteratorPosix{ - .index = 0, - .count = raw.len, - }; - } - - pub fn next(self: *ArgIteratorPosix) ?[]const u8 { - if (self.index == self.count) return null; - - const s = raw[self.index]; - self.index += 1; - return cstr.toSlice(s); - } - - pub fn skip(self: *ArgIteratorPosix) bool { - if (self.index == self.count) return false; - - self.index += 1; - return true; - } - - /// This is marked as public but actually it's only meant to be used - /// internally by zig's startup code. - pub var raw: [][*]u8 = undefined; -}; - -pub const ArgIteratorWindows = struct { - index: usize, - cmd_line: [*]const u8, - in_quote: bool, - quote_count: usize, - seen_quote_count: usize, - - pub const NextError = error{OutOfMemory}; - - pub fn init() ArgIteratorWindows { - return initWithCmdLine(windows.GetCommandLineA()); - } - - pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows { - return ArgIteratorWindows{ - .index = 0, - .cmd_line = cmd_line, - .in_quote = false, - .quote_count = countQuotes(cmd_line), - .seen_quote_count = 0, - }; - } - - /// You must free the returned memory when done. - pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) { - // march forward over whitespace - while (true) : (self.index += 1) { - const byte = self.cmd_line[self.index]; - switch (byte) { - 0 => return null, - ' ', '\t' => continue, - else => break, - } - } - - return self.internalNext(allocator); - } - - pub fn skip(self: *ArgIteratorWindows) bool { - // march forward over whitespace - while (true) : (self.index += 1) { - const byte = self.cmd_line[self.index]; - switch (byte) { - 0 => return false, - ' ', '\t' => continue, - else => break, - } - } - - var backslash_count: usize = 0; - while (true) : (self.index += 1) { - const byte = self.cmd_line[self.index]; - switch (byte) { - 0 => return true, - '"' => { - const quote_is_real = backslash_count % 2 == 0; - if (quote_is_real) { - self.seen_quote_count += 1; - } - }, - '\\' => { - backslash_count += 1; - }, - ' ', '\t' => { - if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { - return true; - } - backslash_count = 0; - }, - else => { - backslash_count = 0; - continue; - }, - } - } - } - - fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 { - var buf = try Buffer.initSize(allocator, 0); - defer buf.deinit(); - - var backslash_count: usize = 0; - while (true) : (self.index += 1) { - const byte = self.cmd_line[self.index]; - switch (byte) { - 0 => return buf.toOwnedSlice(), - '"' => { - const quote_is_real = backslash_count % 2 == 0; - try self.emitBackslashes(&buf, backslash_count / 2); - backslash_count = 0; - - if (quote_is_real) { - self.seen_quote_count += 1; - if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { - try buf.appendByte('"'); - } - } else { - try buf.appendByte('"'); - } - }, - '\\' => { - backslash_count += 1; - }, - ' ', '\t' => { - try self.emitBackslashes(&buf, backslash_count); - backslash_count = 0; - if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { - try buf.appendByte(byte); - } else { - return buf.toOwnedSlice(); - } - }, - else => { - try self.emitBackslashes(&buf, backslash_count); - backslash_count = 0; - try buf.appendByte(byte); - }, - } - } - } - - fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void { - var i: usize = 0; - while (i < emit_count) : (i += 1) { - try buf.appendByte('\\'); - } - } - - fn countQuotes(cmd_line: [*]const u8) usize { - var result: usize = 0; - var backslash_count: usize = 0; - var index: usize = 0; - while (true) : (index += 1) { - const byte = cmd_line[index]; - switch (byte) { - 0 => return result, - '\\' => backslash_count += 1, - '"' => { - result += 1 - (backslash_count % 2); - backslash_count = 0; - }, - else => { - backslash_count = 0; - }, - } - } - } -}; - -pub const ArgIterator = struct { - const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix; - - inner: InnerType, - - pub fn init() ArgIterator { - if (builtin.os == Os.wasi) { - // TODO: Figure out a compatible interface accomodating WASI - @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead."); - } - - return ArgIterator{ .inner = InnerType.init() }; - } - - pub const NextError = ArgIteratorWindows.NextError; - - /// You must free the returned memory when done. - pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) { - if (builtin.os == Os.windows) { - return self.inner.next(allocator); - } else { - return mem.dupe(allocator, u8, self.inner.next() orelse return null); - } - } - - /// If you only are targeting posix you can call this and not need an allocator. - pub fn nextPosix(self: *ArgIterator) ?[]const u8 { - return self.inner.next(); - } - - /// Parse past 1 argument without capturing it. - /// Returns `true` if skipped an arg, `false` if we are at the end. - pub fn skip(self: *ArgIterator) bool { - return self.inner.skip(); - } -}; - -pub fn args() ArgIterator { - return ArgIterator.init(); -} - -/// Caller must call argsFree on result. -pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 { - if (builtin.os == Os.wasi) { - var count: usize = undefined; - var buf_size: usize = undefined; - - const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size); - if (args_sizes_get_ret != os.wasi.ESUCCESS) { - return unexpectedErrorPosix(args_sizes_get_ret); - } - - var argv = try allocator.alloc([*]u8, count); - defer allocator.free(argv); - - var argv_buf = try allocator.alloc(u8, buf_size); - const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr); - if (args_get_ret != os.wasi.ESUCCESS) { - return unexpectedErrorPosix(args_get_ret); - } - - var result_slice = try allocator.alloc([]u8, count); - - var i: usize = 0; - while (i < count) : (i += 1) { - result_slice[i] = mem.toSlice(u8, argv[i]); - } - - return result_slice; - } - - // TODO refactor to only make 1 allocation. - var it = args(); - var contents = try Buffer.initSize(allocator, 0); - defer contents.deinit(); - - var slice_list = ArrayList(usize).init(allocator); - defer slice_list.deinit(); - - while (it.next(allocator)) |arg_or_err| { - const arg = try arg_or_err; - defer allocator.free(arg); - try contents.append(arg); - try slice_list.append(arg.len); - } - - const contents_slice = contents.toSliceConst(); - const slice_sizes = slice_list.toSliceConst(); - const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); - const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); - const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); - errdefer allocator.free(buf); - - const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]); - const result_contents = buf[slice_list_bytes..]; - mem.copy(u8, result_contents, contents_slice); - - var contents_index: usize = 0; - for (slice_sizes) |len, i| { - const new_index = contents_index + len; - result_slice_list[i] = result_contents[contents_index..new_index]; - contents_index = new_index; - } - - return result_slice_list; -} - -pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { - if (builtin.os == Os.wasi) { - const last_item = args_alloc[args_alloc.len - 1]; - const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated - const first_item_ptr = args_alloc[0].ptr; - const len = last_byte_addr - @ptrToInt(first_item_ptr); - allocator.free(first_item_ptr[0..len]); - - return allocator.free(args_alloc); - } - - var total_bytes: usize = 0; - for (args_alloc) |arg| { - total_bytes += @sizeOf([]u8) + arg.len; - } - const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes]; - const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); - return allocator.free(aligned_allocated_buf); -} - -test "windows arg parsing" { - testWindowsCmdLine(c"a b\tc d", [][]const u8{ "a", "b", "c", "d" }); - testWindowsCmdLine(c"\"abc\" d e", [][]const u8{ "abc", "d", "e" }); - testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{ "a\\\\\\b", "de fg", "h" }); - testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{ "a\\\"b", "c", "d" }); - testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{ "a\\\\b c", "d", "e" }); - testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{ "a", "b", "c", "\"d", "f" }); - - testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8{ - ".\\..\\zig-cache\\build", - "bin\\zig.exe", - ".\\..", - ".\\..\\zig-cache", - "--help", - }); -} - -fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void { - var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); - for (expected_args) |expected_arg| { - const arg = it.next(debug.global_allocator).? catch unreachable; - testing.expectEqualSlices(u8, expected_arg, arg); - } - testing.expect(it.next(debug.global_allocator) == null); -} - -pub fn openSelfExe() !os.File { - switch (builtin.os) { - Os.linux => return os.File.openReadC(c"/proc/self/exe"), - Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { - var buf: [MAX_PATH_BYTES]u8 = undefined; - const self_exe_path = try selfExePath(&buf); - buf[self_exe_path.len] = 0; - return os.File.openReadC(self_exe_path.ptr); - }, - Os.windows => { - var buf: [posix.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try selfExePathW(&buf); - return os.File.openReadW(wide_slice.ptr); - }, - else => @compileError("Unsupported OS"), +/// The return value is a slice of `out_buffer` from index 0. +pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (windows.is_the_target and !builtin.link_libc) { + const file_path_w = try windows.sliceToPrefixedFileW(file_path); + @compileError("TODO implement readlink for Windows"); + } else { + const file_path_c = try toPosixPath(file_path); + return readlinkC(&file_path_c, out_buffer); } } -test "openSelfExe" { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios, Os.windows, Os.freebsd => (try openSelfExe()).close(), - else => return error.SkipZigTest, // Unsupported OS. +/// Same as `readlink` except `file_path` is null-terminated. +pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (windows.is_the_target and !builtin.link_libc) { + const file_path_w = try windows.cStrToPrefixedFileW(file_path); + @compileError("TODO implement readlink for Windows"); + } + const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); + switch (errno(rc)) { + 0 => return out_buffer[0..rc], + EACCES => return error.AccessDenied, + EFAULT => unreachable, + EINVAL => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOTDIR => return error.NotDir, + else => |err| return unexpectedErrno(err), } } -pub fn selfExePathW(out_buffer: *[posix.PATH_MAX_WIDE]u16) ![]u16 { - const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast - const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len); - assert(rc <= out_buffer.len); - if (rc == 0) { - const err = windows.GetLastError(); - switch (err) { - else => return windows.unexpectedError(err), - } - } - return out_buffer[0..rc]; -} - -/// Get the path to the current executable. -/// If you only need the directory, use selfExeDirPath. -/// If you only want an open file handle, use openSelfExe. -/// This function may return an error if the current executable -/// was deleted after spawning. -/// Returned value is a slice of out_buffer. -/// -/// On Linux, depends on procfs being mounted. If the currently executing binary has -/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. -/// TODO make the return type of this a null terminated pointer -pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { - switch (builtin.os) { - Os.linux => return readLink(out_buffer, "/proc/self/exe"), - Os.freebsd => { - var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 }; - var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer, &out_len, null, 0); - // TODO could this slice from 0 to out_len instead? - return mem.toSlice(u8, out_buffer); - }, - Os.netbsd => { - var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC_ARGS, -1, posix.KERN_PROC_PATHNAME }; - var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer, &out_len, null, 0); - // TODO could this slice from 0 to out_len instead? - return mem.toSlice(u8, out_buffer); - }, - Os.windows => { - var utf16le_buf: [posix.PATH_MAX_WIDE]u16 = undefined; - const utf16le_slice = try selfExePathW(&utf16le_buf); - // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; - return out_buffer[0..end_index]; - }, - Os.macosx, Os.ios => { - var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast - const rc = c._NSGetExecutablePath(out_buffer, &u32_len); - if (rc != 0) return error.NameTooLong; - return mem.toSlice(u8, out_buffer); - }, - else => @compileError("Unsupported OS"), - } -} - -/// `selfExeDirPath` except allocates the result on the heap. -/// Caller owns returned memory. -pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { - var buf: [MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try selfExeDirPath(&buf)); -} - -/// Get the directory path that contains the current executable. -/// Returned value is a slice of out_buffer. -pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 { - switch (builtin.os) { - Os.linux => { - // If the currently executing binary has been deleted, - // the file path looks something like `/a/b/c/exe (deleted)` - // This path cannot be opened, but it's valid for determining the directory - // the executable was in when it was run. - const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe"); - // Assume that /proc/self/exe has an absolute path, and therefore dirname - // will not return null. - return path.dirname(full_exe_path).?; - }, - Os.windows, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { - const self_exe_path = try selfExePath(out_buffer); - // Assume that the OS APIs return absolute paths, and therefore dirname - // will not return null. - return path.dirname(self_exe_path).?; - }, - else => @compileError("Unsupported OS"), - } -} - -pub const Thread = struct { - data: Data, - - pub const use_pthreads = is_posix and builtin.link_libc; - - /// Represents a kernel thread handle. - /// May be an integer or a pointer depending on the platform. - /// On Linux and POSIX, this is the same as Id. - pub const Handle = if (use_pthreads) - c.pthread_t - else switch (builtin.os) { - builtin.Os.linux => i32, - builtin.Os.windows => windows.HANDLE, - else => @compileError("Unsupported OS"), - }; - - /// Represents a unique ID per thread. - /// May be an integer or pointer depending on the platform. - /// On Linux and POSIX, this is the same as Handle. - pub const Id = switch (builtin.os) { - builtin.Os.windows => windows.DWORD, - else => Handle, - }; - - pub const Data = if (use_pthreads) - struct { - handle: Thread.Handle, - mmap_addr: usize, - mmap_len: usize, - } - else switch (builtin.os) { - builtin.Os.linux => struct { - handle: Thread.Handle, - mmap_addr: usize, - mmap_len: usize, - }, - builtin.Os.windows => struct { - handle: Thread.Handle, - alloc_start: *c_void, - heap_handle: windows.HANDLE, - }, - else => @compileError("Unsupported OS"), - }; - - /// Returns the ID of the calling thread. - /// Makes a syscall every time the function is called. - /// On Linux and POSIX, this Id is the same as a Handle. - pub fn getCurrentId() Id { - if (use_pthreads) { - return c.pthread_self(); - } else - return switch (builtin.os) { - builtin.Os.linux => linux.gettid(), - builtin.Os.windows => windows.GetCurrentThreadId(), - else => @compileError("Unsupported OS"), - }; - } - - /// Returns the handle of this thread. - /// On Linux and POSIX, this is the same as Id. - /// On Linux, it is possible that the thread spawned with `spawnThread` - /// finishes executing entirely before the clone syscall completes. In this - /// case, this function will return 0 rather than the no-longer-existing thread's - /// pid. - pub fn handle(self: Thread) Handle { - return self.data.handle; - } - - pub fn wait(self: *const Thread) void { - if (use_pthreads) { - const err = c.pthread_join(self.data.handle, null); - switch (err) { - 0 => {}, - posix.EINVAL => unreachable, - posix.ESRCH => unreachable, - posix.EDEADLK => unreachable, - else => unreachable, - } - assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); - } else switch (builtin.os) { - builtin.Os.linux => { - while (true) { - const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); - if (pid_value == 0) break; - const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null); - switch (linux.getErrno(rc)) { - 0 => continue, - posix.EINTR => continue, - posix.EAGAIN => continue, - else => unreachable, - } - } - assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); - }, - builtin.Os.windows => { - assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); - assert(windows.CloseHandle(self.data.handle) != 0); - assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0); - }, - else => @compileError("Unsupported OS"), - } - } -}; - -pub const SpawnThreadError = error{ - /// A system-imposed limit on the number of threads was encountered. - /// There are a number of limits that may trigger this error: - /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), - /// which limits the number of processes and threads for a real - /// user ID, was reached; - /// * the kernel's system-wide limit on the number of processes and - /// threads, /proc/sys/kernel/threads-max, was reached (see - /// proc(5)); - /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was - /// reached (see proc(5)); or - /// * the PID limit (pids.max) imposed by the cgroup "process num‐ - /// ber" (PIDs) controller was reached. - ThreadQuotaExceeded, - - /// The kernel cannot allocate sufficient memory to allocate a task structure - /// for the child, or to copy those parts of the caller's context that need to - /// be copied. - SystemResources, - - /// Not enough userland memory to spawn the thread. - OutOfMemory, - +pub const SetIdError = error{ + ResourceLimitReached, + InvalidUserId, + PermissionDenied, Unexpected, }; -/// caller must call wait on the returned thread -/// fn startFn(@typeOf(context)) T -/// where T is u8, noreturn, void, or !void -/// caller must call wait on the returned thread -pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread { - if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); - // TODO compile-time call graph analysis to determine stack upper bound - // https://github.com/ziglang/zig/issues/157 - const default_stack_size = 8 * 1024 * 1024; - - const Context = @typeOf(context); - comptime assert(@ArgType(@typeOf(startFn), 0) == Context); - - if (builtin.os == builtin.Os.windows) { - const WinThread = struct { - const OuterContext = struct { - thread: Thread, - inner: Context, - }; - extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; - switch (@typeId(@typeOf(startFn).ReturnType)) { - builtin.TypeId.Int => { - return startFn(arg); - }, - builtin.TypeId.Void => { - startFn(arg); - return 0; - }, - else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), - } - } - }; - - const heap_handle = windows.GetProcessHeap() orelse return SpawnThreadError.OutOfMemory; - const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); - const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) orelse return SpawnThreadError.OutOfMemory; - errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0); - const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; - const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; - outer_context.* = WinThread.OuterContext{ - .thread = Thread{ - .data = Thread.Data{ - .heap_handle = heap_handle, - .alloc_start = bytes_ptr, - .handle = undefined, - }, - }, - .inner = context, - }; - - const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); - outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { - switch (windows.GetLastError()) { - else => |err| windows.unexpectedError(err), - } - }; - return &outer_context.thread; - } - - const MainFuncs = struct { - extern fn linuxThreadMain(ctx_addr: usize) u8 { - const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; - - switch (@typeId(@typeOf(startFn).ReturnType)) { - builtin.TypeId.Int => { - return startFn(arg); - }, - builtin.TypeId.Void => { - startFn(arg); - return 0; - }, - else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), - } - } - extern fn posixThreadMain(ctx: ?*c_void) ?*c_void { - if (@sizeOf(Context) == 0) { - _ = startFn({}); - return null; - } else { - _ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*); - return null; - } - } - }; - - const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; - - var stack_end_offset: usize = undefined; - var thread_start_offset: usize = undefined; - var context_start_offset: usize = undefined; - var tls_start_offset: usize = undefined; - const mmap_len = blk: { - // First in memory will be the stack, which grows downwards. - var l: usize = mem.alignForward(default_stack_size, os.page_size); - stack_end_offset = l; - // Above the stack, so that it can be in the same mmap call, put the Thread object. - l = mem.alignForward(l, @alignOf(Thread)); - thread_start_offset = l; - l += @sizeOf(Thread); - // Next, the Context object. - if (@sizeOf(Context) != 0) { - l = mem.alignForward(l, @alignOf(Context)); - context_start_offset = l; - l += @sizeOf(Context); - } - // Finally, the Thread Local Storage, if any. - if (!Thread.use_pthreads) { - if (linux.tls.tls_image) |tls_img| { - l = mem.alignForward(l, @alignOf(usize)); - tls_start_offset = l; - l += tls_img.alloc_size; - } - } - break :blk l; - }; - const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); - if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory; - errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0); - - const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); - thread_ptr.data.mmap_addr = mmap_addr; - thread_ptr.data.mmap_len = mmap_len; - - var arg: usize = undefined; - if (@sizeOf(Context) != 0) { - arg = mmap_addr + context_start_offset; - const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); - context_ptr.* = context; - } - - if (Thread.use_pthreads) { - // use pthreads - var attr: c.pthread_attr_t = undefined; - if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; - defer assert(c.pthread_attr_destroy(&attr) == 0); - - assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0); - - const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg)); - switch (err) { - 0 => return thread_ptr, - posix.EAGAIN => return SpawnThreadError.SystemResources, - posix.EPERM => unreachable, - posix.EINVAL => unreachable, - else => return unexpectedErrorPosix(@intCast(usize, err)), - } - } else if (builtin.os == builtin.Os.linux) { - var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | - posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | - posix.CLONE_DETACHED; - var newtls: usize = undefined; - if (linux.tls.tls_image) |tls_img| { - newtls = linux.tls.copyTLS(mmap_addr + tls_start_offset); - flags |= posix.CLONE_SETTLS; - } - const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); - const err = posix.getErrno(rc); - switch (err) { - 0 => return thread_ptr, - posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, - posix.EINVAL => unreachable, - posix.ENOMEM => return SpawnThreadError.SystemResources, - posix.ENOSPC => unreachable, - posix.EPERM => unreachable, - posix.EUSERS => unreachable, - else => return unexpectedErrorPosix(err), - } - } else { - @compileError("Unsupported OS"); +pub fn setuid(uid: u32) SetIdError!void { + switch (errno(system.setuid(uid))) { + 0 => return, + EAGAIN => return error.ResourceLimitReached, + EINVAL => return error.InvalidUserId, + EPERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), } } -pub const CpuCountError = error{ - OutOfMemory, +pub fn setreuid(ruid: u32, euid: u32) SetIdError!void { + switch (errno(system.setreuid(ruid, euid))) { + 0 => return, + EAGAIN => return error.ResourceLimitReached, + EINVAL => return error.InvalidUserId, + EPERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub fn setgid(gid: u32) SetIdError!void { + switch (errno(system.setgid(gid))) { + 0 => return, + EAGAIN => return error.ResourceLimitReached, + EINVAL => return error.InvalidUserId, + EPERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub fn setregid(rgid: u32, egid: u32) SetIdError!void { + switch (errno(system.setregid(rgid, egid))) { + 0 => return, + EAGAIN => return error.ResourceLimitReached, + EINVAL => return error.InvalidUserId, + EPERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +/// Test whether a file descriptor refers to a terminal. +pub fn isatty(handle: fd_t) bool { + if (builtin.link_libc) { + return system.isatty(handle) != 0; + } + if (windows.is_the_target) { + if (isCygwinPty(handle)) + return true; + + var out: windows.DWORD = undefined; + return windows.kernel32.GetConsoleMode(handle, &out) != 0; + } + if (wasi.is_the_target) { + @compileError("TODO implement std.os.posix.isatty for WASI"); + } + if (linux.is_the_target) { + var wsz: system.winsize = undefined; + return system.syscall3(system.SYS_ioctl, @bitCast(usize, isize(handle)), TIOCGWINSZ, @ptrToInt(&wsz)) == 0; + } + unreachable; +} + +pub fn isCygwinPty(handle: fd_t) bool { + if (!windows.is_the_target) return false; + + const size = @sizeOf(windows.FILE_NAME_INFO); + var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH); + + if (windows.kernel32.GetFileInformationByHandleEx( + handle, + windows.FileNameInfo, + @ptrCast(*c_void, &name_info_bytes), + name_info_bytes.len, + ) == 0) { + return false; + } + + const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]); + const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)]; + const name_wide = @bytesToSlice(u16, name_bytes); + return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or + mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null; +} + +pub const SocketError = error{ + /// Permission to create a socket of the specified type and/or + /// pro‐tocol is denied. PermissionDenied, + /// The implementation does not support the specified address family. + AddressFamilyNotSupported, + + /// Unknown protocol, or protocol family not available. + ProtocolFamilyNotAvailable, + + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + /// Insufficient memory is available. The socket cannot be created until sufficient + /// resources are freed. + SystemResources, + + /// The protocol type or the specified protocol is not supported within this domain. + ProtocolNotSupported, + Unexpected, }; -pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize { - switch (builtin.os) { - .macosx, .freebsd, .netbsd => { - var count: c_int = undefined; - var count_len: usize = @sizeOf(c_int); - const name = switch (builtin.os) { - builtin.Os.macosx => c"hw.logicalcpu", - else => c"hw.ncpu", - }; - try posix.sysctlbyname(name, @ptrCast(*c_void, &count), &count_len, null, 0); - return @intCast(usize, count); - }, - .linux => { - const usize_count = 16; - const allocator = std.heap.stackFallback(usize_count * @sizeOf(usize), fallback_allocator).get(); - - var set = try allocator.alloc(usize, usize_count); - defer allocator.free(set); - - while (true) { - const rc = posix.sched_getaffinity(0, set); - const err = posix.getErrno(rc); - switch (err) { - 0 => { - if (rc < set.len * @sizeOf(usize)) { - const result = set[0 .. rc / @sizeOf(usize)]; - var sum: usize = 0; - for (result) |x| { - sum += @popCount(usize, x); - } - return sum; - } else { - set = try allocator.realloc(set, set.len * 2); - continue; - } - }, - posix.EFAULT => unreachable, - posix.EINVAL => unreachable, - posix.EPERM => return CpuCountError.PermissionDenied, - posix.ESRCH => unreachable, - else => return os.unexpectedErrorPosix(err), - } - } - }, - .windows => { - var system_info: windows.SYSTEM_INFO = undefined; - windows.GetSystemInfo(&system_info); - return @intCast(usize, system_info.dwNumberOfProcessors); - }, - else => @compileError("unsupported OS"), +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 { + const rc = system.socket(domain, socket_type, protocol); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EACCES => return error.PermissionDenied, + EAFNOSUPPORT => return error.AddressFamilyNotSupported, + EINVAL => return error.ProtocolFamilyNotAvailable, + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENOBUFS, ENOMEM => return error.SystemResources, + EPROTONOSUPPORT => return error.ProtocolNotSupported, + else => |err| return unexpectedErrno(err), } } + +pub const BindError = error{ + /// The address is protected, and the user is not the superuser. + /// For UNIX domain sockets: Search permission is denied on a component + /// of the path prefix. + AccessDenied, + + /// The given address is already in use, or in the case of Internet domain sockets, + /// The port number was specified as zero in the socket + /// address structure, but, upon attempting to bind to an ephemeral port, it was + /// determined that all port numbers in the ephemeral port range are currently in + /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7). + AddressInUse, + + /// A nonexistent interface was requested or the requested address was not local. + AddressNotAvailable, + + /// Too many symbolic links were encountered in resolving addr. + SymLinkLoop, + + /// addr is too long. + NameTooLong, + + /// A component in the directory prefix of the socket pathname does not exist. + FileNotFound, + + /// Insufficient kernel memory was available. + SystemResources, + + /// A component of the path prefix is not a directory. + NotDir, + + /// The socket inode would reside on a read-only filesystem. + ReadOnlyFileSystem, + + Unexpected, +}; + +/// addr is `*const T` where T is one of the sockaddr +pub fn bind(fd: i32, addr: *const sockaddr) BindError!void { + const rc = system.bind(fd, system, @sizeOf(sockaddr)); + switch (errno(rc)) { + 0 => return, + EACCES => return error.AccessDenied, + EADDRINUSE => return error.AddressInUse, + EBADF => unreachable, // always a race condition if this error is returned + EINVAL => unreachable, + ENOTSOCK => unreachable, + EADDRNOTAVAIL => return error.AddressNotAvailable, + EFAULT => unreachable, + ELOOP => return error.SymLinkLoop, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOTDIR => return error.NotDir, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +const ListenError = error{ + /// Another socket is already listening on the same port. + /// For Internet domain sockets, the socket referred to by sockfd had not previously + /// been bound to an address and, upon attempting to bind it to an ephemeral port, it + /// was determined that all port numbers in the ephemeral port range are currently in + /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). + AddressInUse, + + /// The file descriptor sockfd does not refer to a socket. + FileDescriptorNotASocket, + + /// The socket is not of a type that supports the listen() operation. + OperationNotSupported, + + Unexpected, +}; + +pub fn listen(sockfd: i32, backlog: u32) ListenError!void { + const rc = system.listen(sockfd, backlog); + switch (errno(rc)) { + 0 => return, + EADDRINUSE => return error.AddressInUse, + EBADF => unreachable, + ENOTSOCK => return error.FileDescriptorNotASocket, + EOPNOTSUPP => return error.OperationNotSupported, + else => |err| return unexpectedErrno(err), + } +} + +pub const AcceptError = error{ + ConnectionAborted, + + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + /// Not enough free memory. This often means that the memory allocation is limited + /// by the socket buffer limits, not by the system memory. + SystemResources, + + /// The file descriptor sockfd does not refer to a socket. + FileDescriptorNotASocket, + + /// The referenced socket is not of type SOCK_STREAM. + OperationNotSupported, + + ProtocolFailure, + + /// Firewall rules forbid connection. + BlockedByFirewall, + + Unexpected, +}; + +/// Accept a connection on a socket. `fd` must be opened in blocking mode. +/// See also `accept4_async`. +pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 { + while (true) { + var sockaddr_size = u32(@sizeOf(sockaddr)); + const rc = system.accept4(fd, addr, &sockaddr_size, flags); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EINTR => continue, + else => |err| return unexpectedErrno(err), + + EAGAIN => unreachable, // This function is for blocking only. + EBADF => unreachable, // always a race condition + ECONNABORTED => return error.ConnectionAborted, + EFAULT => unreachable, + EINVAL => unreachable, + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + ENOTSOCK => return error.FileDescriptorNotASocket, + EOPNOTSUPP => return error.OperationNotSupported, + EPROTO => return error.ProtocolFailure, + EPERM => return error.BlockedByFirewall, + } + } +} + +/// This is the same as `accept4` except `fd` is expected to be non-blocking. +/// Returns -1 if would block. +pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 { + while (true) { + var sockaddr_size = u32(@sizeOf(sockaddr)); + const rc = system.accept4(fd, addr, &sockaddr_size, flags); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EINTR => continue, + else => |err| return unexpectedErrno(err), + + EAGAIN => return -1, + EBADF => unreachable, // always a race condition + ECONNABORTED => return error.ConnectionAborted, + EFAULT => unreachable, + EINVAL => unreachable, + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + ENOTSOCK => return error.FileDescriptorNotASocket, + EOPNOTSUPP => return error.OperationNotSupported, + EPROTO => return error.ProtocolFailure, + EPERM => return error.BlockedByFirewall, + } + } +} + +pub const EpollCreateError = error{ + /// The per-user limit on the number of epoll instances imposed by + /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further + /// details. + /// Or, The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + /// There was insufficient memory to create the kernel object. + SystemResources, + + Unexpected, +}; + +pub fn epoll_create1(flags: u32) EpollCreateError!i32 { + const rc = system.epoll_create1(flags); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + else => |err| return unexpectedErrno(err), + + EINVAL => unreachable, + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENOMEM => return error.SystemResources, + } +} + +pub const EpollCtlError = error{ + /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered + /// with this epoll instance. + FileDescriptorAlreadyPresentInSet, + + /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a + /// circular loop of epoll instances monitoring one another. + OperationCausesCircularLoop, + + /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll + /// instance. + FileDescriptorNotRegistered, + + /// There was insufficient memory to handle the requested op control operation. + SystemResources, + + /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while + /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance. + /// See epoll(7) for further details. + UserResourceLimitReached, + + /// The target file fd does not support epoll. This error can occur if fd refers to, + /// for example, a regular file or a directory. + FileDescriptorIncompatibleWithEpoll, + + Unexpected, +}; + +pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: *epoll_event) EpollCtlError!void { + const rc = system.epoll_ctl(epfd, op, fd, event); + switch (errno(rc)) { + 0 => return, + else => |err| return unexpectedErrno(err), + + EBADF => unreachable, // always a race condition if this happens + EEXIST => return error.FileDescriptorAlreadyPresentInSet, + EINVAL => unreachable, + ELOOP => return error.OperationCausesCircularLoop, + ENOENT => return error.FileDescriptorNotRegistered, + ENOMEM => return error.SystemResources, + ENOSPC => return error.UserResourceLimitReached, + EPERM => return error.FileDescriptorIncompatibleWithEpoll, + } +} + +/// Waits for an I/O event on an epoll file descriptor. +/// Returns the number of file descriptors ready for the requested I/O, +/// or zero if no file descriptor became ready during the requested timeout milliseconds. +pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize { + while (true) { + // TODO get rid of the @intCast + const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout); + switch (errno(rc)) { + 0 => return rc, + EINTR => continue, + EBADF => unreachable, + EFAULT => unreachable, + EINVAL => unreachable, + else => unreachable, + } + } +} + +pub const EventFdError = error{ + SystemResources, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + Unexpected, +}; + +pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { + const rc = system.eventfd(initval, flags); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + else => |err| return unexpectedErrno(err), + + EINVAL => unreachable, // invalid parameters + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENODEV => return error.SystemResources, + ENOMEM => return error.SystemResources, + } +} + +pub const GetSockNameError = error{ + /// Insufficient resources were available in the system to perform the operation. + SystemResources, + + Unexpected, +}; + +pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr { + var addr: sockaddr = undefined; + var addrlen: socklen_t = @sizeOf(sockaddr); + switch (errno(system.getsockname(sockfd, &addr, &addrlen))) { + 0 => return addr, + else => |err| return unexpectedErrno(err), + + EBADF => unreachable, // always a race condition + EFAULT => unreachable, + EINVAL => unreachable, // invalid parameters + ENOTSOCK => unreachable, + ENOBUFS => return error.SystemResources, + } +} + +pub const ConnectError = error{ + /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket + /// file, or search permission is denied for one of the directories in the path prefix. + /// or + /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or + /// the connection request failed because of a local firewall rule. + PermissionDenied, + + /// Local address is already in use. + AddressInUse, + + /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an + /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers + /// in the ephemeral port range are currently in use. See the discussion of + /// /proc/sys/net/ipv4/ip_local_port_range in ip(7). + AddressNotAvailable, + + /// The passed address didn't have the correct address family in its sa_family field. + AddressFamilyNotSupported, + + /// Insufficient entries in the routing cache. + SystemResources, + + /// A connect() on a stream socket found no one listening on the remote address. + ConnectionRefused, + + /// Network is unreachable. + NetworkUnreachable, + + /// Timeout while attempting connection. The server may be too busy to accept new connections. Note + /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. + ConnectionTimedOut, + + Unexpected, +}; + +/// Initiate a connection on a socket. +/// This is for blocking file descriptors only. +/// For non-blocking, see `connect_async`. +pub fn connect(sockfd: i32, sockaddr: *const sockaddr) ConnectError!void { + while (true) { + switch (errno(system.connect(sockfd, sockaddr, @sizeOf(sockaddr)))) { + 0 => return, + else => |err| return unexpectedErrno(err), + + EACCES => return error.PermissionDenied, + EPERM => return error.PermissionDenied, + EADDRINUSE => return error.AddressInUse, + EADDRNOTAVAIL => return error.AddressNotAvailable, + EAFNOSUPPORT => return error.AddressFamilyNotSupported, + EAGAIN => return error.SystemResources, + EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + EBADF => unreachable, // sockfd is not a valid open file descriptor. + ECONNREFUSED => return error.ConnectionRefused, + EFAULT => unreachable, // The socket structure address is outside the user's address space. + EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately. + EINTR => continue, + EISCONN => unreachable, // The socket is already connected. + ENETUNREACH => return error.NetworkUnreachable, + ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + ETIMEDOUT => return error.ConnectionTimedOut, + } + } +} + +/// Same as `connect` except it is for blocking socket file descriptors. +/// It expects to receive EINPROGRESS`. +pub fn connect_async(sockfd: i32, sockaddr: *const c_void, len: u32) ConnectError!void { + while (true) { + switch (errno(system.connect(sockfd, sockaddr, len))) { + 0, EINPROGRESS => return, + EINTR => continue, + else => |err| return unexpectedErrno(err), + + EACCES => return error.PermissionDenied, + EPERM => return error.PermissionDenied, + EADDRINUSE => return error.AddressInUse, + EADDRNOTAVAIL => return error.AddressNotAvailable, + EAFNOSUPPORT => return error.AddressFamilyNotSupported, + EAGAIN => return error.SystemResources, + EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + EBADF => unreachable, // sockfd is not a valid open file descriptor. + ECONNREFUSED => return error.ConnectionRefused, + EFAULT => unreachable, // The socket structure address is outside the user's address space. + EISCONN => unreachable, // The socket is already connected. + ENETUNREACH => return error.NetworkUnreachable, + ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + ETIMEDOUT => return error.ConnectionTimedOut, + } + } +} + +pub fn getsockoptError(sockfd: i32) ConnectError!void { + var err_code: i32 = undefined; + var size: u32 = @sizeOf(i32); + const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size); + assert(size == 4); + switch (errno(rc)) { + 0 => switch (err_code) { + 0 => return, + EACCES => return error.PermissionDenied, + EPERM => return error.PermissionDenied, + EADDRINUSE => return error.AddressInUse, + EADDRNOTAVAIL => return error.AddressNotAvailable, + EAFNOSUPPORT => return error.AddressFamilyNotSupported, + EAGAIN => return error.SystemResources, + EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + EBADF => unreachable, // sockfd is not a valid open file descriptor. + ECONNREFUSED => return error.ConnectionRefused, + EFAULT => unreachable, // The socket structure address is outside the user's address space. + EISCONN => unreachable, // The socket is already connected. + ENETUNREACH => return error.NetworkUnreachable, + ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + ETIMEDOUT => return error.ConnectionTimedOut, + else => |err| return unexpectedErrno(err), + }, + EBADF => unreachable, // The argument sockfd is not a valid file descriptor. + EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space. + EINVAL => unreachable, + ENOPROTOOPT => unreachable, // The option is unknown at the level indicated. + ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + else => |err| return unexpectedErrno(err), + } +} + +pub fn waitpid(pid: i32) i32 { + var status: i32 = undefined; + while (true) { + switch (errno(system.waitpid(pid, &status, 0))) { + 0 => return status, + EINTR => continue, + ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. + EINVAL => unreachable, // The options argument was invalid + else => unreachable, + } + } +} + +pub const FStatError = error{ + SystemResources, + Unexpected, +}; + +pub fn fstat(fd: fd_t) FStatError!Stat { + var stat: Stat = undefined; + if (os.darwin.is_the_target) { + switch (errno(system.@"fstat$INODE64"(fd, buf))) { + 0 => return stat, + EBADF => unreachable, // Always a race condition. + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } + + switch (errno(system.fstat(fd, &stat))) { + 0 => return stat, + EBADF => unreachable, // Always a race condition. + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub const KQueueError = error{ + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + Unexpected, +}; + +pub fn kqueue() KQueueError!i32 { + const rc = system.kqueue(); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + else => |err| return unexpectedErrno(err), + } +} + +pub const KEventError = error{ + /// The process does not have permission to register a filter. + AccessDenied, + + /// The event could not be found to be modified or deleted. + EventNotFound, + + /// No memory was available to register the event. + SystemResources, + + /// The specified process to attach to does not exist. + ProcessNotFound, +}; + +pub fn kevent( + kq: i32, + changelist: []const Kevent, + eventlist: []Kevent, + timeout: ?*const timespec, +) KEventError!usize { + while (true) { + const rc = system.kevent(kq, changelist, eventlist, timeout); + switch (errno(rc)) { + 0 => return rc, + EACCES => return error.AccessDenied, + EFAULT => unreachable, + EBADF => unreachable, // Always a race condition. + EINTR => continue, + EINVAL => unreachable, + ENOENT => return error.EventNotFound, + ENOMEM => return error.SystemResources, + ESRCH => return error.ProcessNotFound, + else => unreachable, + } + } +} + +pub const INotifyInitError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + SystemResources, + Unexpected, +}; + +/// initialize an inotify instance +pub fn inotify_init1(flags: u32) INotifyInitError!i32 { + const rc = system.inotify_init1(flags); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EINVAL => unreachable, + EMFILE => return error.ProcessFdQuotaExceeded, + ENFILE => return error.SystemFdQuotaExceeded, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub const INotifyAddWatchError = error{ + AccessDenied, + NameTooLong, + FileNotFound, + SystemResources, + UserResourceLimitReached, + Unexpected, +}; + +/// add a watch to an initialized inotify instance +pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { + const pathname_c = try toPosixPath(pathname); + return inotify_add_watchC(inotify_fd, &pathname_c, mask); +} + +/// Same as `inotify_add_watch` except pathname is null-terminated. +pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 { + const rc = system.inotify_add_watch(inotify_fd, pathname, mask); + switch (errno(rc)) { + 0 => return @intCast(i32, rc), + EACCES => return error.AccessDenied, + EBADF => unreachable, + EFAULT => unreachable, + EINVAL => unreachable, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.UserResourceLimitReached, + else => |err| return unexpectedErrno(err), + } +} + +/// remove an existing watch from an inotify instance +pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { + switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { + 0 => return, + EBADF => unreachable, + EINVAL => unreachable, + else => unreachable, + } +} + +pub const MProtectError = error{ + AccessDenied, + OutOfMemory, + Unexpected, +}; + +/// address and length must be page-aligned +pub fn mprotect(address: usize, length: usize, protection: u32) MProtectError!void { + const negative_page_size = @bitCast(usize, -isize(mem.page_size)); + const aligned_address = address & negative_page_size; + const aligned_end = (address + length + mem.page_size - 1) & negative_page_size; + assert(address == aligned_address); + assert(length == aligned_end - aligned_address); + switch (errno(system.mprotect(address, length, protection))) { + 0 => return, + EINVAL => unreachable, + EACCES => return error.AccessDenied, + ENOMEM => return error.OutOfMemory, + else => return unexpectedErrno(err), + } +} + +pub const ForkError = error{ + SystemResources, + Unexpected, +}; + +pub fn fork() ForkError!pid_t { + const rc = system.fork(); + switch (errno(rc)) { + 0 => return rc, + EAGAIN => return error.SystemResources, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub const MMapError = error{ + AccessDenied, + PermissionDenied, + LockedMemoryLimitExceeded, + SystemFdQuotaExceeded, + MemoryMappingNotSupported, + OutOfMemory, +}; + +/// Map files or devices into memory. +/// 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(address: ?[*]u8, length: usize, prot: u32, flags: u32, fd: fd_t, offset: isize) MMapError!usize { + const err = if (builtin.link_libc) blk: { + const rc = system.mmap(address, length, prot, flags, fd, offset); + if (rc != system.MMAP_FAILED) return rc; + break :blk system._errno().*; + } else blk: { + const rc = system.mmap(address, length, prot, flags, fd, offset); + const err = errno(rc); + if (err == 0) return rc; + break :blk err; + }; + switch (err) { + ETXTBSY => return error.AccessDenied, + EACCES => return error.AccessDenied, + EPERM => return error.PermissionDenied, + EAGAIN => return error.LockedMemoryLimitExceeded, + EBADF => unreachable, // Always a race condition. + EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow. + ENFILE => return error.SystemFdQuotaExceeded, + ENODEV => return error.MemoryMappingNotSupported, + EINVAL => unreachable, // Invalid parameters to mmap() + ENOMEM => return error.OutOfMemory, + else => return unexpectedErrno(err), + } +} + +/// Deletes the mappings for the specified address range, causing +/// further references to addresses within the range to generate invalid memory references. +/// Note that while POSIX allows unmapping a region in the middle of an existing mapping, +/// Zig's munmap function does not, for two reasons: +/// * It violates the Zig principle that resource deallocation must succeed. +/// * The Windows function, VirtualFree, has this restriction. +pub fn munmap(address: usize, length: usize) void { + switch (errno(system.munmap(address, length))) { + 0 => return, + EINVAL => unreachable, // Invalid parameters. + ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping. + else => unreachable, + } +} + +pub const AccessError = error{ + PermissionDenied, + FileNotFound, + NameTooLong, + InputOutput, + SystemResources, + BadPathName, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + Unexpected, +}; + +/// check user's permissions for a file +/// TODO currently this assumes `mode` is `F_OK` on Windows. +pub fn access(path: []const u8, mode: u32) AccessError!void { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.sliceToPrefixedFileW(path); + _ = try windows.GetFileAttributesW(&path_w); + return; + } + const path_c = try toPosixPath(path); + return accessC(&path_c, mode); +} + +/// Same as `access` except `path` is null-terminated. +pub fn accessC(path: [*]const u8, mode: u32) AccessError!void { + if (windows.is_the_target and !builtin.link_libc) { + const path_w = try windows.cStrToPrefixedFileW(path); + _ = try windows.GetFileAttributesW(&path_w); + return; + } + switch (errno(system.access(path, mode))) { + 0 => return, + EACCES => return error.PermissionDenied, + EROFS => return error.PermissionDenied, + ELOOP => return error.PermissionDenied, + ETXTBSY => return error.PermissionDenied, + ENOTDIR => return error.FileNotFound, + ENOENT => return error.FileNotFound, + + ENAMETOOLONG => return error.NameTooLong, + EINVAL => unreachable, + EFAULT => unreachable, + EIO => return error.InputOutput, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub const PipeError = error{ + SystemFdQuotaExceeded, + ProcessFdQuotaExceeded, +}; + +/// Creates a unidirectional data channel that can be used for interprocess communication. +pub fn pipe(fds: *[2]fd_t) PipeError!void { + switch (errno(system.pipe(fds))) { + 0 => return, + EINVAL => unreachable, // Invalid parameters to pipe() + EFAULT => unreachable, // Invalid fds pointer + ENFILE => return error.SystemFdQuotaExceeded, + EMFILE => return error.ProcessFdQuotaExceeded, + else => |err| return unexpectedErrno(err), + } +} + +pub fn pipe2(fds: *[2]fd_t, flags: u32) PipeError!void { + switch (errno(system.pipe2(fds, flags))) { + 0 => return, + EINVAL => unreachable, // Invalid flags + EFAULT => unreachable, // Invalid fds pointer + ENFILE => return error.SystemFdQuotaExceeded, + EMFILE => return error.ProcessFdQuotaExceeded, + else => |err| return unexpectedErrno(err), + } +} + +pub const SysCtlError = error{ + PermissionDenied, + SystemResources, + Unexpected, +}; + +pub fn sysctl( + name: []const c_int, + oldp: ?*c_void, + oldlenp: ?*usize, + newp: ?*c_void, + newlen: usize, +) SysCtlError!void { + switch (errno(system.sysctl(name.ptr, name.len, oldp, oldlenp, newp, newlen))) { + 0 => return, + EFAULT => unreachable, + EPERM => return error.PermissionDenied, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub fn sysctlbynameC( + name: [*]const u8, + oldp: ?*c_void, + oldlenp: ?*usize, + newp: ?*c_void, + newlen: usize, +) SysCtlError!void { + switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { + 0 => return, + EFAULT => unreachable, + EPERM => return error.PermissionDenied, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { + switch (errno(system.gettimeofday(tv, tz))) { + 0 => return, + EINVAL => unreachable, + else => unreachable, + } +} + +pub fn nanosleep(req: timespec) void { + var rem = req; + while (true) { + switch (errno(system.nanosleep(&rem, &rem))) { + 0 => return, + EINVAL => unreachable, // Invalid parameters. + EFAULT => unreachable, + EINTR => continue, + } + } +} + +pub const SeekError = error{ + Unseekable, + Unexpected, +}; + +/// Repositions read/write file offset relative to the beginning. +pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { + if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) { + switch (errno(system.llseek(fd, offset, null, SEEK_SET))) { + 0 => return, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (windows.is_the_target and !builtin.link_libc) { + return windows.SetFilePointerEx_BEGIN(fd, offset); + } + const ipos = @bitCast(i64, offset); // the OS treats this as unsigned + switch (errno(system.lseek(fd, ipos, SEEK_SET))) { + 0 => return, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Repositions read/write file offset relative to the current offset. +pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { + if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) { + switch (errno(system.llseek(fd, @bitCast(u64, offset), null, SEEK_CUR))) { + 0 => return, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (windows.is_the_target and !builtin.link_libc) { + return windows.SetFilePointerEx_CURRENT(fd, offset); + } + switch (errno(system.lseek(fd, offset, SEEK_CUR))) { + 0 => return, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Repositions read/write file offset relative to the end. +pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { + if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) { + switch (errno(system.llseek(fd, @bitCast(u64, offset), null, SEEK_END))) { + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (windows.is_the_target and !builtin.link_libc) { + return windows.SetFilePointerEx_END(fd, offset); + } + switch (errno(system.lseek(fd, offset, SEEK_END))) { + 0 => return, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Returns the read/write file offset relative to the beginning. +pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { + if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) { + 0 => return result, + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (windows.is_the_target and !builtin.link_libc) { + return windows.SetFilePointerEx_CURRENT_get(fd); + } + const rc = system.lseek(fd, 0, SEEK_CUR); + switch (errno(rc)) { + 0 => return @bitCast(u64, rc), + EBADF => unreachable, // always a race condition + EINVAL => return error.Unseekable, + EOVERFLOW => return error.Unseekable, + ESPIPE => return error.Unseekable, + ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +pub const RealPathError = error{ + FileNotFound, + AccessDenied, + NameTooLong, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + BadPathName, + DeviceBusy, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + PathAlreadyExists, + + Unexpected, +}; + +/// Return the canonicalized absolute pathname. +/// Expands all symbolic links and resolves references to `.`, `..`, and +/// extra `/` characters in `pathname`. +/// The return value is a slice of `out_buffer`, but not necessarily from the beginning. +/// See also `realpathC` and `realpathW`. +pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { + if (windows.is_the_target and !builtin.link_libc) { + const pathname_w = try windows.sliceToPrefixedFileW(pathname); + return realpathW(&pathname_w, out_buffer); + } + const pathname_c = try toPosixPath(pathname); + return realpathC(&pathname_c, out_buffer); +} + +/// Same as `realpath` except `pathname` is null-terminated. +pub fn realpathC(pathname: [*]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { + if (windows.is_the_target and !builtin.link_libc) { + const pathname_w = try windows.cStrToPrefixedFileW(pathname); + return realpathW(&pathname_w, out_buffer); + } + if (linux.is_the_target and !builtin.link_libc) { + const fd = try openC(pathname, O_PATH | O_NONBLOCK | O_CLOEXEC, 0); + defer close(fd); + + var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable; + + return readlinkC(proc_path.ptr, out_buffer); + } + const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) { + EINVAL => unreachable, + EBADF => unreachable, + EFAULT => unreachable, + EACCES => return error.AccessDenied, + ENOENT => return error.FileNotFound, + ENOTSUP => return error.NotSupported, + ENOTDIR => return error.NotDir, + ENAMETOOLONG => return error.NameTooLong, + ELOOP => return error.SymLinkLoop, + EIO => return error.InputOutput, + else => |err| return unexpectedErrno(err), + }; + return mem.toSlice(u8, result_path); +} + +/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. +pub fn realpathW(pathname: [*]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { + const h_file = try windows.CreateFileW( + pathname, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + ); + defer windows.CloseHandle(h_file); + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_len = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS); + assert(wide_len <= wide_buf.len); + const wide_slice = wide_len[0..wide_len]; + + // Windows returns \\?\ prepended to the path. + // We strip it to make this function consistent across platforms. + const prefix = []u16{ '\\', '\\', '?', '\\' }; + const start_index = if (mem.startsWith(u16, wide_slice, prefix)) prefix.len else 0; + + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable; + return out_buffer[0..end_index]; +} + +/// Used to convert a slice to a null terminated slice on the stack. +/// TODO https://github.com/ziglang/zig/issues/287 +pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 { + var path_with_null: [PATH_MAX]u8 = undefined; + // >= rather than > to make room for the null byte + if (file_path.len >= PATH_MAX) return error.NameTooLong; + mem.copy(u8, &path_with_null, file_path); + path_with_null[file_path.len] = 0; + return path_with_null; +} + +/// Whether or not error.Unexpected will print its value and a stack trace. +/// if this happens the fix is to add the error code to the corresponding +/// switch expression, possibly introduce a new error in the error set, and +/// send a patch to Zig. +pub const unexpected_error_tracing = builtin.mode == .Debug; + +pub const UnexpectedError = error{ + /// The Operating System returned an undocumented error code. + /// This error is in theory not possible, but it would be better + /// to handle this error than to invoke undefined behavior. + Unexpected, +}; + +/// Call this when you made a syscall or something that sets errno +/// and you get an unexpected error. +pub fn unexpectedErrno(err: usize) UnexpectedError { + if (unexpected_error_tracing) { + std.debug.warn("unexpected errno: {}\n", err); + std.debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + +test "" { + _ = @import("os/darwin.zig"); + _ = @import("os/freebsd.zig"); + _ = @import("os/linux.zig"); + _ = @import("os/netbsd.zig"); + _ = @import("os/uefi.zig"); + _ = @import("os/wasi.zig"); + _ = @import("os/windows.zig"); + _ = @import("os/zen.zig"); + + _ = @import("os/test.zig"); +} diff --git a/std/os/file.zig b/std/os/file.zig deleted file mode 100644 index a54dbdeb7..000000000 --- a/std/os/file.zig +++ /dev/null @@ -1,450 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const os = std.os; -const io = std.io; -const mem = std.mem; -const math = std.math; -const assert = std.debug.assert; -const posix = os.posix; -const windows = os.windows; -const Os = builtin.Os; -const windows_util = @import("windows/util.zig"); -const maxInt = std.math.maxInt; - -const is_posix = builtin.os != builtin.Os.windows; -const is_windows = builtin.os == builtin.Os.windows; - -pub const File = struct { - /// The OS-specific file descriptor or file handle. - handle: posix.fd_t, - - pub const Mode = switch (builtin.os) { - Os.windows => void, - else => u32, - }; - - pub const default_mode = switch (builtin.os) { - Os.windows => {}, - else => 0o666, - }; - - pub const OpenError = os.WindowsOpenError || os.PosixOpenError; - - /// `openRead` except with a null terminated path - pub fn openReadC(path: [*]const u8) OpenError!File { - if (is_posix) { - const flags = posix.O_LARGEFILE | posix.O_RDONLY; - const fd = try os.posixOpenC(path, flags, 0); - return openHandle(fd); - } - if (is_windows) { - return openRead(mem.toSliceConst(u8, path)); - } - @compileError("Unsupported OS"); - } - - /// Call close to clean up. - pub fn openRead(path: []const u8) OpenError!File { - if (is_posix) { - const path_c = try os.toPosixPath(path); - return openReadC(&path_c); - } - if (is_windows) { - const path_w = try windows_util.sliceToPrefixedFileW(path); - return openReadW(&path_w); - } - @compileError("Unsupported OS"); - } - - pub fn openReadW(path_w: [*]const u16) OpenError!File { - const handle = try os.windowsOpenW( - path_w, - windows.GENERIC_READ, - windows.FILE_SHARE_READ, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); - } - - /// Calls `openWriteMode` with os.File.default_mode for the mode. - pub fn openWrite(path: []const u8) OpenError!File { - return openWriteMode(path, os.File.default_mode); - } - - /// If the path does not exist it will be created. - /// If a file already exists in the destination it will be truncated. - /// Call close to clean up. - pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { - if (is_posix) { - const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC; - const fd = try os.posixOpen(path, flags, file_mode); - return openHandle(fd); - } else if (is_windows) { - const path_w = try windows_util.sliceToPrefixedFileW(path); - return openWriteModeW(&path_w, file_mode); - } else { - @compileError("TODO implement openWriteMode for this OS"); - } - } - - pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File { - const handle = try os.windowsOpenW( - path_w, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_ALWAYS, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); - } - - /// If the path does not exist it will be created. - /// If a file already exists in the destination this returns OpenError.PathAlreadyExists - /// Call close to clean up. - pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { - if (is_posix) { - const path_c = try os.toPosixPath(path); - return openWriteNoClobberC(&path_c, file_mode); - } else if (is_windows) { - const path_w = try windows_util.sliceToPrefixedFileW(path); - return openWriteNoClobberW(&path_w, file_mode); - } else { - @compileError("TODO implement openWriteMode for this OS"); - } - } - - pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File { - if (is_posix) { - const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL; - const fd = try os.posixOpenC(path, flags, file_mode); - return openHandle(fd); - } else if (is_windows) { - const path_w = try windows_util.cStrToPrefixedFileW(path); - return openWriteNoClobberW(&path_w, file_mode); - } else { - @compileError("TODO implement openWriteMode for this OS"); - } - } - - pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File { - const handle = try os.windowsOpenW( - path_w, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_NEW, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); - } - - pub fn openHandle(handle: posix.fd_t) File { - return File{ .handle = handle }; - } - - /// Test for the existence of `path`. - /// `path` is UTF8-encoded. - pub fn exists(path: []const u8) AccessError!void { - return posix.access(path, posix.F_OK); - } - - /// Same as `exists` except the parameter is null-terminated UTF16LE-encoded. - pub fn existsW(path: [*]const u16) AccessError!void { - return posix.accessW(path, posix.F_OK); - } - - /// Same as `exists` except the parameter is null-terminated. - pub fn existsC(path: [*]const u8) AccessError!void { - return posix.accessC(path, posix.F_OK); - } - - /// Upon success, the stream is in an uninitialized state. To continue using it, - /// you must use the open() function. - pub fn close(self: File) void { - os.close(self.handle); - } - - /// Test whether the file refers to a terminal. - /// See also `supportsAnsiEscapeCodes`. - pub fn isTty(self: File) bool { - return posix.isatty(self.handle); - } - - /// Test whether ANSI escape codes will be treated as such. - pub fn supportsAnsiEscapeCodes(self: File) bool { - if (windows.is_the_target) { - return posix.isCygwinPty(self.handle); - } - return self.isTty(); - } - - pub const SeekError = error{ - /// TODO make this error impossible to get - Overflow, - Unseekable, - Unexpected, - }; - - pub fn seekForward(self: File, amount: i64) SeekError!void { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { - const iamount = try math.cast(isize, amount); - const result = posix.lseek(self.handle, iamount, posix.SEEK_CUR); - const err = posix.getErrno(result); - if (err > 0) { - return switch (err) { - // We do not make this an error code because if you get EBADF it's always a bug, - // since the fd could have been reused. - posix.EBADF => unreachable, - posix.EINVAL => error.Unseekable, - posix.EOVERFLOW => error.Unseekable, - posix.ESPIPE => error.Unseekable, - posix.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - }, - Os.windows => { - if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.INVALID_PARAMETER => unreachable, - else => os.unexpectedErrorWindows(err), - }; - } - }, - else => @compileError("unsupported OS"), - } - } - - pub fn seekTo(self: File, pos: u64) SeekError!void { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { - const ipos = try math.cast(isize, pos); - const result = posix.lseek(self.handle, ipos, posix.SEEK_SET); - const err = posix.getErrno(result); - if (err > 0) { - return switch (err) { - // We do not make this an error code because if you get EBADF it's always a bug, - // since the fd could have been reused. - posix.EBADF => unreachable, - posix.EINVAL => error.Unseekable, - posix.EOVERFLOW => error.Unseekable, - posix.ESPIPE => error.Unseekable, - posix.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - }, - Os.windows => { - const ipos = try math.cast(isize, pos); - if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.INVALID_PARAMETER => unreachable, - windows.ERROR.INVALID_HANDLE => unreachable, - else => os.unexpectedErrorWindows(err), - }; - } - }, - else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), - } - } - - pub const GetSeekPosError = error{ - SystemResources, - Unseekable, - Unexpected, - }; - - pub fn getPos(self: File) GetSeekPosError!u64 { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { - const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); - const err = posix.getErrno(result); - if (err > 0) { - return switch (err) { - // We do not make this an error code because if you get EBADF it's always a bug, - // since the fd could have been reused. - posix.EBADF => unreachable, - posix.EINVAL => error.Unseekable, - posix.EOVERFLOW => error.Unseekable, - posix.ESPIPE => error.Unseekable, - posix.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - return u64(result); - }, - Os.windows => { - var pos: windows.LARGE_INTEGER = undefined; - if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.INVALID_PARAMETER => unreachable, - else => os.unexpectedErrorWindows(err), - }; - } - - return @intCast(u64, pos); - }, - else => @compileError("unsupported OS"), - } - } - - pub fn getEndPos(self: File) GetSeekPosError!u64 { - if (is_posix) { - const stat = try os.posixFStat(self.handle); - return @intCast(u64, stat.size); - } else if (is_windows) { - var file_size: windows.LARGE_INTEGER = undefined; - if (windows.GetFileSizeEx(self.handle, &file_size) == 0) { - const err = windows.GetLastError(); - return switch (err) { - else => os.unexpectedErrorWindows(err), - }; - } - return @intCast(u64, file_size); - } else { - @compileError("TODO support getEndPos on this OS"); - } - } - - pub const ModeError = error{ - SystemResources, - Unexpected, - }; - - pub fn mode(self: File) ModeError!Mode { - if (is_posix) { - var stat: posix.Stat = undefined; - const err = posix.getErrno(posix.fstat(self.handle, &stat)); - if (err > 0) { - return switch (err) { - // We do not make this an error code because if you get EBADF it's always a bug, - // since the fd could have been reused. - posix.EBADF => unreachable, - posix.ENOMEM => error.SystemResources, - else => os.unexpectedErrorPosix(err), - }; - } - - // TODO: we should be able to cast u16 to ModeError!u32, making this - // explicit cast not necessary - return Mode(stat.mode); - } else if (is_windows) { - return {}; - } else { - @compileError("TODO support file mode on this OS"); - } - } - - pub const ReadError = posix.ReadError; - - pub fn read(self: File, buffer: []u8) ReadError!usize { - return posix.read(self.handle, buffer); - } - - pub const WriteError = posix.WriteError; - - pub fn write(self: File, bytes: []const u8) WriteError!void { - return posix.write(self.handle, bytes); - } - - pub fn inStream(file: File) InStream { - return InStream{ - .file = file, - .stream = InStream.Stream{ .readFn = InStream.readFn }, - }; - } - - pub fn outStream(file: File) OutStream { - return OutStream{ - .file = file, - .stream = OutStream.Stream{ .writeFn = OutStream.writeFn }, - }; - } - - pub fn seekableStream(file: File) SeekableStream { - return SeekableStream{ - .file = file, - .stream = SeekableStream.Stream{ - .seekToFn = SeekableStream.seekToFn, - .seekForwardFn = SeekableStream.seekForwardFn, - .getPosFn = SeekableStream.getPosFn, - .getEndPosFn = SeekableStream.getEndPosFn, - }, - }; - } - - /// Implementation of io.InStream trait for File - pub const InStream = struct { - file: File, - stream: Stream, - - pub const Error = ReadError; - pub const Stream = io.InStream(Error); - - fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { - const self = @fieldParentPtr(InStream, "stream", in_stream); - return self.file.read(buffer); - } - }; - - /// Implementation of io.OutStream trait for File - pub const OutStream = struct { - file: File, - stream: Stream, - - pub const Error = WriteError; - pub const Stream = io.OutStream(Error); - - fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { - const self = @fieldParentPtr(OutStream, "stream", out_stream); - return self.file.write(bytes); - } - }; - - /// Implementation of io.SeekableStream trait for File - pub const SeekableStream = struct { - file: File, - stream: Stream, - - pub const Stream = io.SeekableStream(SeekError, GetSeekPosError); - - pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void { - const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); - return self.file.seekTo(pos); - } - - pub fn seekForwardFn(seekable_stream: *Stream, amt: i64) SeekError!void { - const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); - return self.file.seekForward(amt); - } - - pub fn getEndPosFn(seekable_stream: *Stream) GetSeekPosError!u64 { - const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); - return self.file.getEndPos(); - } - - pub fn getPosFn(seekable_stream: *Stream) GetSeekPosError!u64 { - const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); - return self.file.getPos(); - } - }; - - pub fn stdout() !File { - const handle = try posix.GetStdHandle(posix.STD_OUTPUT_HANDLE); - return openHandle(handle); - } - - pub fn stderr() !File { - const handle = try posix.GetStdHandle(posix.STD_ERROR_HANDLE); - return openHandle(handle); - } - - pub fn stdin() !File { - const handle = try posix.GetStdHandle(posix.STD_INPUT_HANDLE); - return openHandle(handle); - } -}; diff --git a/std/os/get_user_id.zig b/std/os/get_user_id.zig deleted file mode 100644 index db44c6838..000000000 --- a/std/os/get_user_id.zig +++ /dev/null @@ -1,104 +0,0 @@ -const builtin = @import("builtin"); -const Os = builtin.Os; -const os = @import("../os.zig"); -const io = @import("../io.zig"); - -pub const UserInfo = struct { - uid: u32, - gid: u32, -}; - -/// POSIX function which gets a uid from username. -pub fn getUserInfo(name: []const u8) !UserInfo { - return switch (builtin.os) { - Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => posixGetUserInfo(name), - else => @compileError("Unsupported OS"), - }; -} - -const State = enum { - Start, - WaitForNextLine, - SkipPassword, - ReadUserId, - ReadGroupId, -}; - -// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else -// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. - -pub fn posixGetUserInfo(name: []const u8) !UserInfo { - var in_stream = try io.InStream.open("/etc/passwd", null); - defer in_stream.close(); - - var buf: [os.page_size]u8 = undefined; - var name_index: usize = 0; - var state = State.Start; - var uid: u32 = 0; - var gid: u32 = 0; - - while (true) { - const amt_read = try in_stream.read(buf[0..]); - for (buf[0..amt_read]) |byte| { - switch (state) { - State.Start => switch (byte) { - ':' => { - state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine; - }, - '\n' => return error.CorruptPasswordFile, - else => { - if (name_index == name.len or name[name_index] != byte) { - state = State.WaitForNextLine; - } - name_index += 1; - }, - }, - State.WaitForNextLine => switch (byte) { - '\n' => { - name_index = 0; - state = State.Start; - }, - else => continue, - }, - State.SkipPassword => switch (byte) { - '\n' => return error.CorruptPasswordFile, - ':' => { - state = State.ReadUserId; - }, - else => continue, - }, - State.ReadUserId => switch (byte) { - ':' => { - state = State.ReadGroupId; - }, - '\n' => return error.CorruptPasswordFile, - else => { - const digit = switch (byte) { - '0'...'9' => byte - '0', - else => return error.CorruptPasswordFile, - }; - if (@mulWithOverflow(u32, uid, 10, *uid)) return error.CorruptPasswordFile; - if (@addWithOverflow(u32, uid, digit, *uid)) return error.CorruptPasswordFile; - }, - }, - State.ReadGroupId => switch (byte) { - '\n', ':' => { - return UserInfo{ - .uid = uid, - .gid = gid, - }; - }, - else => { - const digit = switch (byte) { - '0'...'9' => byte - '0', - else => return error.CorruptPasswordFile, - }; - if (@mulWithOverflow(u32, gid, 10, *gid)) return error.CorruptPasswordFile; - if (@addWithOverflow(u32, gid, digit, *gid)) return error.CorruptPasswordFile; - }, - }, - } - } - if (amt_read < buf.len) return error.UserNotFound; - } -} diff --git a/std/os/linux/sys.zig b/std/os/linux/sys.zig index 61b21ed2b..a5939c579 100644 --- a/std/os/linux/sys.zig +++ b/std/os/linux/sys.zig @@ -320,8 +320,21 @@ pub fn close(fd: i32) usize { return syscall1(SYS_close, @bitCast(usize, isize(fd))); } -pub fn lseek(fd: i32, offset: isize, ref_pos: usize) usize { - return syscall3(SYS_lseek, @bitCast(usize, isize(fd)), @bitCast(usize, offset), ref_pos); +/// Can only be called on 32 bit systems. For 64 bit see `lseek`. +pub fn llseek(fd: i32, offset: u64, result: ?*u64, whence: usize) usize { + return syscall5( + SYS__llseek, + @bitCast(usize, isize(fd)), + @truncate(usize, offset >> 32), + @truncate(usize, offset), + @ptrToInt(result), + whence, + ); +} + +/// Can only be called on 64 bit systems. For 32 bit see `llseek`. +pub fn lseek(fd: i32, offset: i64, whence: usize) usize { + return syscall3(SYS_lseek, @bitCast(usize, isize(fd)), @bitCast(usize, offset), whence); } pub fn exit(status: i32) noreturn { diff --git a/std/os/posix.zig b/std/os/posix.zig deleted file mode 100644 index dd0a6b54e..000000000 --- a/std/os/posix.zig +++ /dev/null @@ -1,2307 +0,0 @@ -// This is the "Zig-flavored POSIX" API layer. -// The purpose is not to match POSIX as closely as possible. Instead, -// the goal is to provide a very specific layer of abstraction: -// * Implement the POSIX functions, types, and definitions where possible, -// using lower-level target-specific API. -// * When null-terminated byte buffers are required, provide APIs which accept -// slices as well as APIs which accept null-terminated byte buffers. Same goes -// for UTF-16LE encoding. -// * Convert "errno"-style error codes into Zig errors. -// * Implement the OS-specific functions, types, and definitions that the Zig -// standard library needs, at the same API abstraction layer as outlined above. -// For example kevent() and getrandom(). Windows-specific functions are separate, -// in `std.os.windows`. -// * When there exists a corresponding libc function and linking libc, the libc -// implementation is used. Exceptions are made for known buggy areas of libc. -// On Linux libc can be side-stepped by using `std.os.linux.sys`. -// Note: The Zig standard library does not support POSIX thread cancellation, and -// in general EINTR is handled by trying again. - -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const os = @import("../os.zig"); -const system = os.system; -const mem = std.mem; -const BufMap = std.BufMap; -const Allocator = mem.Allocator; -const windows = os.windows; -const kernel32 = windows.kernel32; -const wasi = os.wasi; -const linux = os.linux; -const testing = std.testing; - -pub use system.posix; - -/// See also `getenv`. -pub var environ: [][*]u8 = undefined; - -/// To obtain errno, call this function with the return value of the -/// system function call. For some systems this will obtain the value directly -/// from the return code; for others it will use a thread-local errno variable. -/// Therefore, this function only returns a well-defined value when it is called -/// directly after the system function call which one wants to learn the errno -/// value of. -pub const errno = system.getErrno; - -/// Closes the file descriptor. -/// This function is not capable of returning any indication of failure. An -/// application which wants to ensure writes have succeeded before closing -/// must call `fsync` before `close`. -/// Note: The Zig standard library does not support POSIX thread cancellation. -pub fn close(fd: fd_t) void { - if (windows.is_the_target and !builtin.link_libc) { - assert(kernel32.CloseHandle(fd) != 0); - return; - } - if (wasi.is_the_target) { - switch (wasi.fd_close(fd)) { - 0 => return, - else => |err| return unexpectedErrno(err), - } - } - switch (errno(system.close(fd))) { - EBADF => unreachable, // Always a race condition. - EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 - else => return, - } -} - -pub const GetRandomError = error{}; - -/// Obtain a series of random bytes. These bytes can be used to seed user-space -/// random number generators or for cryptographic purposes. -/// When linking against libc, this calls the -/// appropriate OS-specific library call. Otherwise it uses the zig standard -/// library implementation. -pub fn getrandom(buf: []u8) GetRandomError!void { - if (windows.is_the_target) { - // Call RtlGenRandom() instead of CryptGetRandom() on Windows - // https://github.com/rust-lang-nursery/rand/issues/111 - // https://bugzilla.mozilla.org/show_bug.cgi?id=504270 - if (windows.advapi32.RtlGenRandom(buf.ptr, buf.len) == 0) { - switch (kernel32.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - return; - } - if (linux.is_the_target) { - while (true) { - switch (errno(system.getrandom(buf.ptr, buf.len, 0))) { - 0 => return, - EINVAL => unreachable, - EFAULT => unreachable, - EINTR => continue, - ENOSYS => return getRandomBytesDevURandom(buf), - else => |err| return unexpectedErrno(err), - } - } - } - if (wasi.is_the_target) { - switch (os.wasi.random_get(buf.ptr, buf.len)) { - 0 => return, - else => |err| return unexpectedErrno(err), - } - } - return getRandomBytesDevURandom(buf); -} - -fn getRandomBytesDevURandom(buf: []u8) !void { - const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0); - defer close(fd); - - const stream = &os.File.openHandle(fd).inStream().stream; - stream.readNoEof(buf) catch return error.Unexpected; -} - -test "os.getRandomBytes" { - var buf_a: [50]u8 = undefined; - var buf_b: [50]u8 = undefined; - try getRandomBytes(&buf_a); - try getRandomBytes(&buf_b); - // If this test fails the chance is significantly higher that there is a bug than - // that two sets of 50 bytes were equal. - testing.expect(!mem.eql(u8, buf_a, buf_b)); -} - -/// Causes abnormal process termination. -/// If linking against libc, this calls the abort() libc function. Otherwise -/// it raises SIGABRT followed by SIGKILL and finally lo -pub fn abort() noreturn { - @setCold(true); - if (builtin.link_libc) { - system.abort(); - } - if (windows.is_the_target) { - if (builtin.mode == .Debug) { - @breakpoint(); - } - windows.ExitProcess(3); - } - if (builtin.os == .uefi) { - // TODO there must be a better thing to do here than loop forever - while (true) {} - } - - raise(SIGABRT); - - // TODO the rest of the implementation of abort() from musl libc here - - raise(SIGKILL); - exit(127); -} - -pub const RaiseError = error{}; - -pub fn raise(sig: u8) RaiseError!void { - if (builtin.link_libc) { - switch (errno(system.raise(sig))) { - 0 => return, - else => |err| return unexpectedErrno(err), - } - } - - if (wasi.is_the_target) { - switch (wasi.proc_raise(SIGABRT)) { - 0 => return, - else => |err| return unexpectedErrno(err), - } - } - - if (windows.is_the_target) { - @compileError("TODO implement std.posix.raise for Windows"); - } - - var set: system.sigset_t = undefined; - system.blockAppSignals(&set); - const tid = system.syscall0(system.SYS_gettid); - const rc = system.syscall2(system.SYS_tkill, tid, sig); - system.restoreSignals(&set); - switch (errno(rc)) { - 0 => return, - else => |err| return unexpectedErrno(err), - } -} - -/// Exits the program cleanly with the specified status code. -pub fn exit(status: u8) noreturn { - if (builtin.link_libc) { - system.exit(status); - } - if (windows.is_the_target) { - windows.ExitProcess(status); - } - if (wasi.is_the_target) { - wasi.proc_exit(status); - } - if (linux.is_the_target and !builtin.single_threaded) { - linux.exit_group(status); - } - system.exit(status); -} - -pub const ReadError = error{ - InputOutput, - SystemResources, - IsDir, - OperationAborted, - BrokenPipe, - Unexpected, -}; - -/// Returns the number of bytes that were read, which can be less than -/// buf.len. If 0 bytes were read, that means EOF. -/// This function is for blocking file descriptors only. For non-blocking, see -/// `readAsync`. -pub fn read(fd: fd_t, buf: []u8) ReadError!usize { - if (windows.is_the_target and !builtin.link_libc) { - var index: usize = 0; - while (index < buffer.len) { - const want_read_count = @intCast(windows.DWORD, math.min(windows.DWORD(math.maxInt(windows.DWORD)), buffer.len - index)); - var amt_read: windows.DWORD = undefined; - if (windows.ReadFile(fd, buffer.ptr + index, want_read_count, &amt_read, null) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.OPERATION_ABORTED => continue, - windows.ERROR.BROKEN_PIPE => return index, - else => |err| return windows.unexpectedError(err), - } - } - if (amt_read == 0) return index; - index += amt_read; - } - return index; - } - - if (wasi.is_the_target and !builtin.link_libc) { - const iovs = [1]was.iovec_t{wasi.iovec_t{ - .buf = buf.ptr, - .buf_len = buf.len, - }}; - - var nread: usize = undefined; - switch (fd_read(fd, &iovs, iovs.len, &nread)) { - 0 => return nread, - else => |err| return unexpectedErrno(err), - } - } - - // Linux can return EINVAL when read amount is > 0x7ffff000 - // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274 - const max_buf_len = 0x7ffff000; - - var index: usize = 0; - while (index < buf.len) { - const want_to_read = math.min(buf.len - index, usize(max_buf_len)); - const rc = system.read(fd, buf.ptr + index, want_to_read); - switch (errno(rc)) { - 0 => { - index += rc; - if (rc == want_to_read) continue; - // Read returned less than buf.len. - return index; - }, - EINTR => continue, - EINVAL => unreachable, - EFAULT => unreachable, - EAGAIN => unreachable, // This function is for blocking reads. - EBADF => unreachable, // Always a race condition. - EIO => return error.InputOutput, - EISDIR => return error.IsDir, - ENOBUFS => return error.SystemResources, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } - return index; -} - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// This function is for blocking file descriptors only. For non-blocking, see -/// `preadvAsync`. -pub fn preadv(fd: fd_t, iov: [*]const iovec, count: usize, offset: u64) ReadError!usize { - if (os.darwin.is_the_target) { - // Darwin does not have preadv but it does have pread. - var off: usize = 0; - var iov_i: usize = 0; - var inner_off: usize = 0; - while (true) { - const v = iov[iov_i]; - const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); - const err = darwin.getErrno(rc); - switch (err) { - 0 => { - off += rc; - inner_off += rc; - if (inner_off == v.iov_len) { - iov_i += 1; - inner_off = 0; - if (iov_i == count) { - return off; - } - } - if (rc == 0) return off; // EOF - continue; - }, - EINTR => continue, - EINVAL => unreachable, - EFAULT => unreachable, - ESPIPE => unreachable, // fd is not seekable - EAGAIN => unreachable, // This function is for blocking reads. - EBADF => unreachable, // always a race condition - EIO => return error.InputOutput, - EISDIR => return error.IsDir, - ENOBUFS => return error.SystemResources, - ENOMEM => return error.SystemResources, - else => return unexpectedErrno(err), - } - } - } - while (true) { - const rc = system.preadv(fd, iov, count, offset); - switch (errno(rc)) { - 0 => return rc, - EINTR => continue, - EINVAL => unreachable, - EFAULT => unreachable, - EAGAIN => unreachable, // This function is for blocking reads. - EBADF => unreachable, // always a race condition - EIO => return error.InputOutput, - EISDIR => return error.IsDir, - ENOBUFS => return error.SystemResources, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const WriteError = error{ - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - AccessDenied, - BrokenPipe, - SystemResources, - OperationAborted, - Unexpected, -}; - -/// Write to a file descriptor. Keeps trying if it gets interrupted. -/// This function is for blocking file descriptors only. For non-blocking, see -/// `writeAsync`. -pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { - if (windows.is_the_target and !builtin.link_libc) { - var bytes_written: windows.DWORD = undefined; - // TODO replace this @intCast with a loop that writes all the bytes - if (windows.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.INVALID_USER_BUFFER => return error.SystemResources, - windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, - windows.ERROR.OPERATION_ABORTED => return error.OperationAborted, - windows.ERROR.NOT_ENOUGH_QUOTA => return error.SystemResources, - windows.ERROR.IO_PENDING => unreachable, - windows.ERROR.BROKEN_PIPE => return error.BrokenPipe, - else => |err| return windows.unexpectedError(err), - } - } - } - - if (wasi.is_the_target and !builtin.link_libc) { - const ciovs = [1]wasi.ciovec_t{wasi.ciovec_t{ - .buf = bytes.ptr, - .buf_len = bytes.len, - }}; - var nwritten: usize = undefined; - switch (fd_write(fd, &ciovs, ciovs.len, &nwritten)) { - 0 => return, - else => |err| return unexpectedErrno(err), - } - } - - // Linux can return EINVAL when write amount is > 0x7ffff000 - // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856 - const max_bytes_len = 0x7ffff000; - - var index: usize = 0; - while (index < bytes.len) { - const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len)); - const rc = system.write(fd, bytes.ptr + index, amt_to_write); - switch (errno(rc)) { - 0 => { - index += rc; - continue; - }, - EINTR => continue, - EINVAL => unreachable, - EFAULT => unreachable, - EAGAIN => unreachable, // This function is for blocking writes. - EBADF => unreachable, // Always a race condition. - EDESTADDRREQ => unreachable, // `connect` was never called. - EDQUOT => return error.DiskQuota, - EFBIG => return error.FileTooBig, - EIO => return error.InputOutput, - ENOSPC => return error.NoSpaceLeft, - EPERM => return error.AccessDenied, - EPIPE => return error.BrokenPipe, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted. -/// This function is for blocking file descriptors only. For non-blocking, see -/// `pwritevAsync`. -pub fn pwritev(fd: fd_t, iov: [*]const iovec_const, count: usize, offset: u64) WriteError!void { - if (darwin.is_the_target) { - // Darwin does not have pwritev but it does have pwrite. - var off: usize = 0; - var iov_i: usize = 0; - var inner_off: usize = 0; - while (true) { - const v = iov[iov_i]; - const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); - const err = darwin.getErrno(rc); - switch (err) { - 0 => { - off += rc; - inner_off += rc; - if (inner_off == v.iov_len) { - iov_i += 1; - inner_off = 0; - if (iov_i == count) { - return; - } - } - continue; - }, - EINTR => continue, - ESPIPE => unreachable, // `fd` is not seekable. - EINVAL => unreachable, - EFAULT => unreachable, - EAGAIN => unreachable, // This function is for blocking writes. - EBADF => unreachable, // Always a race condition. - EDESTADDRREQ => unreachable, // `connect` was never called. - EDQUOT => return error.DiskQuota, - EFBIG => return error.FileTooBig, - EIO => return error.InputOutput, - ENOSPC => return error.NoSpaceLeft, - EPERM => return error.AccessDenied, - EPIPE => return error.BrokenPipe, - else => return unexpectedErrno(err), - } - } - } - - while (true) { - const rc = system.pwritev(fd, iov, count, offset); - switch (errno(rc)) { - 0 => return, - EINTR => continue, - EINVAL => unreachable, - EFAULT => unreachable, - EAGAIN => unreachable, // This function is for blocking writes. - EBADF => unreachable, // Always a race condition. - EDESTADDRREQ => unreachable, // `connect` was never called. - EDQUOT => return error.DiskQuota, - EFBIG => return error.FileTooBig, - EIO => return error.InputOutput, - ENOSPC => return error.NoSpaceLeft, - EPERM => return error.AccessDenied, - EPIPE => return error.BrokenPipe, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const OpenError = error{ - AccessDenied, - FileTooBig, - IsDir, - SymLinkLoop, - ProcessFdQuotaExceeded, - NameTooLong, - SystemFdQuotaExceeded, - NoDevice, - FileNotFound, - SystemResources, - NoSpaceLeft, - NotDir, - PathAlreadyExists, - DeviceBusy, - Unexpected, -}; - -/// Open and possibly create a file. Keeps trying if it gets interrupted. -/// `file_path` needs to be copied in memory to add a null terminating byte. -/// See also `openC`. -pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t { - const file_path_c = try toPosixPath(file_path); - return openC(&file_path_c, flags, perm); -} - -/// Open and possibly create a file. Keeps trying if it gets interrupted. -/// See also `open`. -/// TODO https://github.com/ziglang/zig/issues/265 -pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t { - while (true) { - const rc = system.open(file_path, flags, perm); - switch (errno(rc)) { - 0 => return @intCast(fd_t, rc), - EINTR => continue, - - EFAULT => unreachable, - EINVAL => unreachable, - EACCES => return error.AccessDenied, - EFBIG => return error.FileTooBig, - EOVERFLOW => return error.FileTooBig, - EISDIR => return error.IsDir, - ELOOP => return error.SymLinkLoop, - EMFILE => return error.ProcessFdQuotaExceeded, - ENAMETOOLONG => return error.NameTooLong, - ENFILE => return error.SystemFdQuotaExceeded, - ENODEV => return error.NoDevice, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOSPC => return error.NoSpaceLeft, - ENOTDIR => return error.NotDir, - EPERM => return error.AccessDenied, - EEXIST => return error.PathAlreadyExists, - EBUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { - while (true) { - switch (errno(system.dup2(old_fd, new_fd))) { - 0 => return, - EBUSY, EINTR => continue, - EMFILE => return error.ProcessFdQuotaExceeded, - EINVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } -} - -/// This function must allocate memory to add a null terminating bytes on path and each arg. -/// It must also convert to KEY=VALUE\0 format for environment variables, and include null -/// pointers after the args and after the environment variables. -/// `argv[0]` is the executable path. -/// This function also uses the PATH environment variable to get the full path to the executable. -/// TODO provide execveC which does not take an allocator -pub fn execve(allocator: *Allocator, argv: []const []const u8, env_map: *const BufMap) !void { - const argv_buf = try allocator.alloc(?[*]u8, argv.len + 1); - mem.set(?[*]u8, argv_buf, null); - defer { - for (argv_buf) |arg| { - const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break; - allocator.free(arg_buf); - } - allocator.free(argv_buf); - } - for (argv) |arg, i| { - const arg_buf = try allocator.alloc(u8, arg.len + 1); - @memcpy(arg_buf.ptr, arg.ptr, arg.len); - arg_buf[arg.len] = 0; - - argv_buf[i] = arg_buf.ptr; - } - argv_buf[argv.len] = null; - - const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); - defer freeNullDelimitedEnvMap(allocator, envp_buf); - - const exe_path = argv[0]; - if (mem.indexOfScalar(u8, exe_path, '/') != null) { - return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr))); - } - - const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; - // PATH.len because it is >= the largest search_path - // +1 for the / to join the search path and exe_path - // +1 for the null terminating byte - const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2); - defer allocator.free(path_buf); - var it = mem.tokenize(PATH, ":"); - var seen_eacces = false; - var err: usize = undefined; - while (it.next()) |search_path| { - mem.copy(u8, path_buf, search_path); - path_buf[search_path.len] = '/'; - mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path); - path_buf[search_path.len + exe_path.len + 1] = 0; - err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)); - assert(err > 0); - if (err == EACCES) { - seen_eacces = true; - } else if (err != ENOENT) { - return execveErrnoToErr(err); - } - } - if (seen_eacces) { - err = EACCES; - } - return execveErrnoToErr(err); -} - -pub fn createNullDelimitedEnvMap(allocator: *Allocator, env_map: *const BufMap) ![]?[*]u8 { - const envp_count = env_map.count(); - const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1); - mem.set(?[*]u8, envp_buf, null); - errdefer freeNullDelimitedEnvMap(allocator, envp_buf); - { - var it = env_map.iterator(); - var i: usize = 0; - while (it.next()) |pair| : (i += 1) { - const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2); - @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len); - env_buf[pair.key.len] = '='; - @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); - env_buf[env_buf.len - 1] = 0; - - envp_buf[i] = env_buf.ptr; - } - assert(i == envp_count); - } - assert(envp_buf[envp_count] == null); - return envp_buf; -} - -pub fn freeNullDelimitedEnvMap(allocator: *Allocator, envp_buf: []?[*]u8) void { - for (envp_buf) |env| { - const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break; - allocator.free(env_buf); - } - allocator.free(envp_buf); -} - -pub const ExecveError = error{ - SystemResources, - AccessDenied, - InvalidExe, - FileSystem, - IsDir, - FileNotFound, - NotDir, - FileBusy, - - Unexpected, -}; - -fn execveErrnoToErr(err: usize) ExecveError { - assert(err > 0); - switch (err) { - EFAULT => unreachable, - E2BIG => return error.SystemResources, - EMFILE => return error.ProcessFdQuotaExceeded, - ENAMETOOLONG => return error.NameTooLong, - ENFILE => return error.SystemFdQuotaExceeded, - ENOMEM => return error.SystemResources, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EINVAL => return error.InvalidExe, - ENOEXEC => return error.InvalidExe, - EIO => return error.FileSystem, - ELOOP => return error.FileSystem, - EISDIR => return error.IsDir, - ENOENT => return error.FileNotFound, - ENOTDIR => return error.NotDir, - ETXTBSY => return error.FileBusy, - else => return unexpectedErrno(err), - } -} - -/// Get an environment variable. -/// See also `getenvC`. -/// TODO make this go through libc when we have it -pub fn getenv(key: []const u8) ?[]const u8 { - for (environ) |ptr| { - var line_i: usize = 0; - while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} - const this_key = ptr[0..line_i]; - if (!mem.eql(u8, key, this_key)) continue; - - var end_i: usize = line_i; - while (ptr[end_i] != 0) : (end_i += 1) {} - const this_value = ptr[line_i + 1 .. end_i]; - - return this_value; - } - return null; -} - -/// Get an environment variable with a null-terminated name. -/// See also `getenv`. -/// TODO https://github.com/ziglang/zig/issues/265 -pub fn getenvC(key: [*]const u8) ?[]const u8 { - if (builtin.link_libc) { - const value = system.getenv(key) orelse return null; - return mem.toSliceConst(u8, value); - } - return getenv(mem.toSliceConst(u8, key)); -} - -/// See std.elf for the constants. -pub fn getauxval(index: usize) usize { - if (builtin.link_libc) { - return usize(system.getauxval(index)); - } else if (linux.elf_aux_maybe) |auxv| { - var i: usize = 0; - while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { - if (auxv[i].a_type == index) - return auxv[i].a_un.a_val; - } - } - return 0; -} - -pub const GetCwdError = error{ - NameTooLong, - CurrentWorkingDirectoryUnlinked, - Unexpected, -}; - -/// The result is a slice of out_buffer, indexed from 0. -pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { - if (windows.is_the_target and !builtin.link_libc) { - var utf16le_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast - const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast - const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr); - if (result == 0) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - assert(result <= utf16le_buf.len); - const utf16le_slice = utf16le_buf[0..result]; - // Trust that Windows gives us valid UTF-16LE. - var end_index: usize = 0; - var it = std.unicode.Utf16LeIterator.init(utf16le); - while (it.nextCodepoint() catch unreachable) |codepoint| { - if (end_index + std.unicode.utf8CodepointSequenceLength(codepoint) >= out_buffer.len) - return error.NameTooLong; - end_index += utf8Encode(codepoint, out_buffer[end_index..]) catch unreachable; - } - return out_buffer[0..end_index]; - } - - const err = if (builtin.link_libc) blk: { - break :blk if (system.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else system._errno().*; - } else blk: { - break :blk errno(system.getcwd(out_buffer, out_buffer.len)); - }; - switch (err) { - 0 => return mem.toSlice(u8, out_buffer), - EFAULT => unreachable, - EINVAL => unreachable, - ENOENT => return error.CurrentWorkingDirectoryUnlinked, - ERANGE => return error.NameTooLong, - else => |err| return unexpectedErrno(err), - } -} - -test "getcwd" { - // at least call it so it gets compiled - var buf: [os.MAX_PATH_BYTES]u8 = undefined; - _ = getcwd(&buf) catch {}; -} - -pub const SymLinkError = error{ - AccessDenied, - DiskQuota, - PathAlreadyExists, - FileSystem, - SymLinkLoop, - FileNotFound, - SystemResources, - NoSpaceLeft, - ReadOnlyFileSystem, - NotDir, - NameTooLong, - InvalidUtf8, - BadPathName, - Unexpected, -}; - -/// Creates a symbolic link named `new_path` which contains the string `target_path`. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// If `new_path` exists, it will not be overwritten. -/// See also `symlinkC` and `symlinkW`. -pub fn symlink(target_path: []const u8, new_path: []const u8) SymLinkError!void { - if (windows.is_the_target and !builtin.link_libc) { - const target_path_w = try cStrToPrefixedFileW(target_path); - const new_path_w = try cStrToPrefixedFileW(new_path); - return symlinkW(&target_path_w, &new_path_w); - } else { - const target_path_c = try toPosixPath(target_path); - const new_path_c = try toPosixPath(new_path); - return symlinkC(&target_path_c, &new_path_c); - } -} - -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, new_path: []const u8) SymLinkError!void { - const target_path_c = try toPosixPath(target_path); - const new_path_c = try toPosixPath(new_path); - return symlinkatC(target_path_c, newdirfd, new_path_c); -} - -pub fn symlinkatC(target_path: [*]const u8, newdirfd: fd_t, new_path: [*]const u8) SymLinkError!void { - switch (errno(system.symlinkat(target_path, newdirfd, new_path))) { - 0 => return, - EFAULT => unreachable, - EINVAL => unreachable, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EDQUOT => return error.DiskQuota, - EEXIST => return error.PathAlreadyExists, - EIO => return error.FileSystem, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOTDIR => return error.NotDir, - ENOMEM => return error.SystemResources, - ENOSPC => return error.NoSpaceLeft, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -/// This is the same as `symlink` except the parameters are null-terminated pointers. -/// See also `symlink` and `symlinkW`. -pub fn symlinkC(target_path: [*]const u8, new_path: [*]const u8) SymLinkError!void { - if (windows.is_the_target and !builtin.link_libc) { - const target_path_w = try cStrToPrefixedFileW(target_path); - const new_path_w = try cStrToPrefixedFileW(new_path); - return symlinkW(&target_path_w, &new_path_w); - } - switch (errno(system.symlink(target_path, new_path))) { - 0 => return, - EFAULT => unreachable, - EINVAL => unreachable, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EDQUOT => return error.DiskQuota, - EEXIST => return error.PathAlreadyExists, - EIO => return error.FileSystem, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOTDIR => return error.NotDir, - ENOMEM => return error.SystemResources, - ENOSPC => return error.NoSpaceLeft, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -/// This is the same as `symlink` except the parameters are null-terminated pointers to -/// UTF-16LE encoded strings. -/// See also `symlink` and `symlinkC`. -/// TODO handle when linking libc -pub fn symlinkW(target_path_w: [*]const u16, new_path_w: [*]const u16) SymLinkError!void { - if (windows.CreateSymbolicLinkW(target_path_w, new_path_w, 0) == 0) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } -} - -pub const UnlinkError = error{ - FileNotFound, - AccessDenied, - FileBusy, - FileSystem, - IsDir, - SymLinkLoop, - NameTooLong, - NotDir, - SystemResources, - ReadOnlyFileSystem, - Unexpected, - - /// On Windows, file paths must be valid Unicode. - InvalidUtf8, - - /// On Windows, file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, -}; - -/// Delete a name and possibly the file it refers to. -pub fn unlink(file_path: []const u8) UnlinkError!void { - if (windows.is_the_target and !builtin.link_libc) { - const file_path_w = try sliceToPrefixedFileW(file_path); - return unlinkW(&file_path_w); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkC(&file_path_c); - } -} - -/// Same as `unlink` except the parameter is a UTF16LE-encoded string. -/// TODO handle when linking libc -pub fn unlinkW(file_path: [*]const u16) UnlinkError!void { - if (windows.unlinkW(file_path) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, - windows.ERROR.ACCESS_DENIED => return error.AccessDenied, - windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, - windows.ERROR.INVALID_PARAMETER => return error.NameTooLong, - else => |err| return windows.unexpectedError(err), - } - } -} - -/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string. -pub fn unlinkC(file_path: [*]const u8) UnlinkError!void { - if (windows.is_the_target and !builtin.link_libc) { - const file_path_w = try cStrToPrefixedFileW(file_path); - return unlinkW(&file_path_w); - } - switch (errno(system.unlink(file_path))) { - 0 => return, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EBUSY => return error.FileBusy, - EFAULT => unreachable, - EINVAL => unreachable, - EIO => return error.FileSystem, - EISDIR => return error.IsDir, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOTDIR => return error.NotDir, - ENOMEM => return error.SystemResources, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -const RenameError = error{}; // TODO - -/// Change the name or location of a file. -pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { - if (windows.is_the_target and !builtin.link_libc) { - const old_path_w = try sliceToPrefixedFileW(old_path); - const new_path_w = try sliceToPrefixedFileW(new_path); - return renameW(&old_path_w, &new_path_w); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameC(&old_path_c, &new_path_c); - } -} - -/// Same as `rename` except the parameters are null-terminated byte arrays. -pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void { - if (windows.is_the_target and !builtin.link_libc) { - const old_path_w = try cStrToPrefixedFileW(old_path); - const new_path_w = try cStrToPrefixedFileW(new_path); - return renameW(&old_path_w, &new_path_w); - } - switch (errno(system.rename(old_path, new_path))) { - 0 => return, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EBUSY => return error.FileBusy, - EDQUOT => return error.DiskQuota, - EFAULT => unreachable, - EINVAL => unreachable, - EISDIR => return error.IsDir, - ELOOP => return error.SymLinkLoop, - EMLINK => return error.LinkQuotaExceeded, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOTDIR => return error.NotDir, - ENOMEM => return error.SystemResources, - ENOSPC => return error.NoSpaceLeft, - EEXIST => return error.PathAlreadyExists, - ENOTEMPTY => return error.PathAlreadyExists, - EROFS => return error.ReadOnlyFileSystem, - EXDEV => return error.RenameAcrossMountPoints, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `rename` except the parameters are null-terminated UTF16LE-encoded strings. -/// TODO handle when linking libc -pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) RenameError!void { - const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; - if (windows.MoveFileExW(old_path, new_path, flags) == 0) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } -} - -pub const MakeDirError = error{}; - -/// Create a directory. -/// `mode` is ignored on Windows. -pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try sliceToPrefixedFileW(dir_path); - return mkdirW(&dir_path_w, mode); - } else { - const dir_path_c = try toPosixPath(dir_path); - return mkdirC(&dir_path_c, mode); - } -} - -/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. -pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try cStrToPrefixedFileW(dir_path); - return mkdirW(&dir_path_w, mode); - } - switch (errno(system.mkdir(dir_path, mode))) { - 0 => return, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EDQUOT => return error.DiskQuota, - EEXIST => return error.PathAlreadyExists, - EFAULT => unreachable, - ELOOP => return error.SymLinkLoop, - EMLINK => return error.LinkQuotaExceeded, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOSPC => return error.NoSpaceLeft, - ENOTDIR => return error.NotDir, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `mkdir` but the parameter is a null-terminated UTF16LE-encoded string. -pub fn mkdirW(dir_path: []const u8, mode: u32) MakeDirError!void { - const dir_path_w = try sliceToPrefixedFileW(dir_path); - - if (windows.CreateDirectoryW(&dir_path_w, null) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.ALREADY_EXISTS => return error.PathAlreadyExists, - windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, - else => |err| return windows.unexpectedError(err), - } - } -} - -pub const DeleteDirError = error{ - AccessDenied, - FileBusy, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotDir, - DirNotEmpty, - ReadOnlyFileSystem, - InvalidUtf8, - BadPathName, - Unexpected, -}; - -/// Deletes an empty directory. -pub fn rmdir(dir_path: []const u8) DeleteDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try sliceToPrefixedFileW(dir_path); - return rmdirW(&dir_path_w); - } else { - const dir_path_c = try toPosixPath(dir_path); - return rmdirC(&dir_path_c); - } -} - -/// Same as `rmdir` except the parameter is a null-terminated UTF8-encoded string. -pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try cStrToPrefixedFileW(dir_path); - return rmdirW(&dir_path_w); - } - switch (errno(system.rmdir(dir_path))) { - 0 => return, - EACCES => return error.AccessDenied, - EPERM => return error.AccessDenied, - EBUSY => return error.FileBusy, - EFAULT => unreachable, - EINVAL => unreachable, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOTDIR => return error.NotDir, - EEXIST => return error.DirNotEmpty, - ENOTEMPTY => return error.DirNotEmpty, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `rmdir` except the parameter is a null-terminated UTF16LE-encoded string. -/// TODO handle linking libc -pub fn rmdirW(dir_path_w: [*]const u16) DeleteDirError!void { - if (windows.RemoveDirectoryW(dir_path_w) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, - windows.ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty, - else => |err| return windows.unexpectedError(err), - } - } -} - -pub const ChangeCurDirError = error{}; - -/// Changes the current working directory of the calling process. -/// `dir_path` is recommended to be a UTF-8 encoded string. -pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try sliceToPrefixedFileW(dir_path); - return chdirW(&dir_path_w); - } else { - const dir_path_c = try toPosixPath(dir_path); - return chdirC(&dir_path_c); - } -} - -/// Same as `chdir` except the parameter is null-terminated. -pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void { - if (windows.is_the_target and !builtin.link_libc) { - const dir_path_w = try cStrToPrefixedFileW(dir_path); - return chdirW(&dir_path_w); - } - switch (errno(system.chdir(dir_path))) { - 0 => return, - EACCES => return error.AccessDenied, - EFAULT => unreachable, - EIO => return error.FileSystem, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOTDIR => return error.NotDir, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `chdir` except the parameter is a null-terminated, UTF16LE-encoded string. -/// TODO handle linking libc -pub fn chdirW(dir_path: [*]const u16) ChangeCurDirError!void { - @compileError("TODO implement chdir for Windows"); -} - -pub const ReadLinkError = error{}; - -/// Read value of a symbolic link. -/// The return value is a slice of `out_buffer` from index 0. -pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (windows.is_the_target and !builtin.link_libc) { - const file_path_w = try sliceToPrefixedFileW(file_path); - return readlinkW(&file_path_w, out_buffer); - } else { - const file_path_c = try toPosixPath(file_path); - return readlinkC(&file_path_c, out_buffer); - } -} - -/// Same as `readlink` except `file_path` is null-terminated. -pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (windows.is_the_target and !builtin.link_libc) { - const file_path_w = try cStrToPrefixedFileW(file_path); - return readlinkW(&file_path_w, out_buffer); - } - const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); - switch (errno(rc)) { - 0 => return out_buffer[0..rc], - EACCES => return error.AccessDenied, - EFAULT => unreachable, - EINVAL => unreachable, - EIO => return error.FileSystem, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOTDIR => return error.NotDir, - else => |err| return unexpectedErrno(err), - } -} - -pub const SetIdError = error{ - ResourceLimitReached, - InvalidUserId, - PermissionDenied, - Unexpected, -}; - -pub fn setuid(uid: u32) SetIdError!void { - switch (errno(system.setuid(uid))) { - 0 => return, - EAGAIN => return error.ResourceLimitReached, - EINVAL => return error.InvalidUserId, - EPERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn setreuid(ruid: u32, euid: u32) SetIdError!void { - switch (errno(system.setreuid(ruid, euid))) { - 0 => return, - EAGAIN => return error.ResourceLimitReached, - EINVAL => return error.InvalidUserId, - EPERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn setgid(gid: u32) SetIdError!void { - switch (errno(system.setgid(gid))) { - 0 => return, - EAGAIN => return error.ResourceLimitReached, - EINVAL => return error.InvalidUserId, - EPERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn setregid(rgid: u32, egid: u32) SetIdError!void { - switch (errno(system.setregid(rgid, egid))) { - 0 => return, - EAGAIN => return error.ResourceLimitReached, - EINVAL => return error.InvalidUserId, - EPERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const GetStdHandleError = error{ - NoStandardHandleAttached, - Unexpected, -}; - -pub fn GetStdHandle(handle_id: windows.DWORD) GetStdHandleError!fd_t { - if (windows.is_the_target) { - const handle = windows.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached; - if (handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| windows.unexpectedError(err), - } - } - return handle; - } - - switch (handle_id) { - windows.STD_ERROR_HANDLE => return STDERR_FILENO, - windows.STD_OUTPUT_HANDLE => return STDOUT_FILENO, - windows.STD_INPUT_HANDLE => return STDIN_FILENO, - else => unreachable, - } -} - -/// Test whether a file descriptor refers to a terminal. -pub fn isatty(handle: fd_t) bool { - if (builtin.link_libc) { - return system.isatty(handle) != 0; - } - if (windows.is_the_target) { - if (isCygwinPty(handle)) - return true; - - var out: windows.DWORD = undefined; - return windows.GetConsoleMode(handle, &out) != 0; - } - if (wasi.is_the_target) { - @compileError("TODO implement std.os.posix.isatty for WASI"); - } - - var wsz: system.winsize = undefined; - return system.syscall3(system.SYS_ioctl, @bitCast(usize, isize(handle)), TIOCGWINSZ, @ptrToInt(&wsz)) == 0; -} - -pub fn isCygwinPty(handle: fd_t) bool { - if (!windows.is_the_target) return false; - - const size = @sizeOf(windows.FILE_NAME_INFO); - var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH); - - if (windows.GetFileInformationByHandleEx( - handle, - windows.FileNameInfo, - @ptrCast(*c_void, &name_info_bytes[0]), - @intCast(u32, name_info_bytes.len), - ) == 0) { - return false; - } - - const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]); - const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)]; - const name_wide = @bytesToSlice(u16, name_bytes); - return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or - mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null; -} - -pub const SocketError = error{ - /// Permission to create a socket of the specified type and/or - /// pro‐tocol is denied. - PermissionDenied, - - /// The implementation does not support the specified address family. - AddressFamilyNotSupported, - - /// Unknown protocol, or protocol family not available. - ProtocolFamilyNotAvailable, - - /// The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - /// Insufficient memory is available. The socket cannot be created until sufficient - /// resources are freed. - SystemResources, - - /// The protocol type or the specified protocol is not supported within this domain. - ProtocolNotSupported, - - Unexpected, -}; - -pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 { - const rc = system.socket(domain, socket_type, protocol); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EACCES => return error.PermissionDenied, - EAFNOSUPPORT => return error.AddressFamilyNotSupported, - EINVAL => return error.ProtocolFamilyNotAvailable, - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENOBUFS, ENOMEM => return error.SystemResources, - EPROTONOSUPPORT => return error.ProtocolNotSupported, - else => |err| return unexpectedErrno(err), - } -} - -pub const BindError = error{ - /// The address is protected, and the user is not the superuser. - /// For UNIX domain sockets: Search permission is denied on a component - /// of the path prefix. - AccessDenied, - - /// The given address is already in use, or in the case of Internet domain sockets, - /// The port number was specified as zero in the socket - /// address structure, but, upon attempting to bind to an ephemeral port, it was - /// determined that all port numbers in the ephemeral port range are currently in - /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7). - AddressInUse, - - /// A nonexistent interface was requested or the requested address was not local. - AddressNotAvailable, - - /// Too many symbolic links were encountered in resolving addr. - SymLinkLoop, - - /// addr is too long. - NameTooLong, - - /// A component in the directory prefix of the socket pathname does not exist. - FileNotFound, - - /// Insufficient kernel memory was available. - SystemResources, - - /// A component of the path prefix is not a directory. - NotDir, - - /// The socket inode would reside on a read-only filesystem. - ReadOnlyFileSystem, - - Unexpected, -}; - -/// addr is `*const T` where T is one of the sockaddr -pub fn bind(fd: i32, addr: *const sockaddr) BindError!void { - const rc = system.bind(fd, system, @sizeOf(sockaddr)); - switch (errno(rc)) { - 0 => return, - EACCES => return error.AccessDenied, - EADDRINUSE => return error.AddressInUse, - EBADF => unreachable, // always a race condition if this error is returned - EINVAL => unreachable, - ENOTSOCK => unreachable, - EADDRNOTAVAIL => return error.AddressNotAvailable, - EFAULT => unreachable, - ELOOP => return error.SymLinkLoop, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOTDIR => return error.NotDir, - EROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -const ListenError = error{ - /// Another socket is already listening on the same port. - /// For Internet domain sockets, the socket referred to by sockfd had not previously - /// been bound to an address and, upon attempting to bind it to an ephemeral port, it - /// was determined that all port numbers in the ephemeral port range are currently in - /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). - AddressInUse, - - /// The file descriptor sockfd does not refer to a socket. - FileDescriptorNotASocket, - - /// The socket is not of a type that supports the listen() operation. - OperationNotSupported, - - Unexpected, -}; - -pub fn listen(sockfd: i32, backlog: u32) ListenError!void { - const rc = system.listen(sockfd, backlog); - switch (errno(rc)) { - 0 => return, - EADDRINUSE => return error.AddressInUse, - EBADF => unreachable, - ENOTSOCK => return error.FileDescriptorNotASocket, - EOPNOTSUPP => return error.OperationNotSupported, - else => |err| return unexpectedErrno(err), - } -} - -pub const AcceptError = error{ - ConnectionAborted, - - /// The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - /// Not enough free memory. This often means that the memory allocation is limited - /// by the socket buffer limits, not by the system memory. - SystemResources, - - /// The file descriptor sockfd does not refer to a socket. - FileDescriptorNotASocket, - - /// The referenced socket is not of type SOCK_STREAM. - OperationNotSupported, - - ProtocolFailure, - - /// Firewall rules forbid connection. - BlockedByFirewall, - - Unexpected, -}; - -/// Accept a connection on a socket. `fd` must be opened in blocking mode. -/// See also `accept4_async`. -pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 { - while (true) { - var sockaddr_size = u32(@sizeOf(sockaddr)); - const rc = system.accept4(fd, addr, &sockaddr_size, flags); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EINTR => continue, - else => |err| return unexpectedErrno(err), - - EAGAIN => unreachable, // This function is for blocking only. - EBADF => unreachable, // always a race condition - ECONNABORTED => return error.ConnectionAborted, - EFAULT => unreachable, - EINVAL => unreachable, - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENOBUFS => return error.SystemResources, - ENOMEM => return error.SystemResources, - ENOTSOCK => return error.FileDescriptorNotASocket, - EOPNOTSUPP => return error.OperationNotSupported, - EPROTO => return error.ProtocolFailure, - EPERM => return error.BlockedByFirewall, - } - } -} - -/// This is the same as `accept4` except `fd` is expected to be non-blocking. -/// Returns -1 if would block. -pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 { - while (true) { - var sockaddr_size = u32(@sizeOf(sockaddr)); - const rc = system.accept4(fd, addr, &sockaddr_size, flags); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EINTR => continue, - else => |err| return unexpectedErrno(err), - - EAGAIN => return -1, - EBADF => unreachable, // always a race condition - ECONNABORTED => return error.ConnectionAborted, - EFAULT => unreachable, - EINVAL => unreachable, - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENOBUFS => return error.SystemResources, - ENOMEM => return error.SystemResources, - ENOTSOCK => return error.FileDescriptorNotASocket, - EOPNOTSUPP => return error.OperationNotSupported, - EPROTO => return error.ProtocolFailure, - EPERM => return error.BlockedByFirewall, - } - } -} - -pub const EpollCreateError = error{ - /// The per-user limit on the number of epoll instances imposed by - /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further - /// details. - /// Or, The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - /// There was insufficient memory to create the kernel object. - SystemResources, - - Unexpected, -}; - -pub fn epoll_create1(flags: u32) EpollCreateError!i32 { - const rc = system.epoll_create1(flags); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - else => |err| return unexpectedErrno(err), - - EINVAL => unreachable, - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENOMEM => return error.SystemResources, - } -} - -pub const EpollCtlError = error{ - /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered - /// with this epoll instance. - FileDescriptorAlreadyPresentInSet, - - /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a - /// circular loop of epoll instances monitoring one another. - OperationCausesCircularLoop, - - /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll - /// instance. - FileDescriptorNotRegistered, - - /// There was insufficient memory to handle the requested op control operation. - SystemResources, - - /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while - /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance. - /// See epoll(7) for further details. - UserResourceLimitReached, - - /// The target file fd does not support epoll. This error can occur if fd refers to, - /// for example, a regular file or a directory. - FileDescriptorIncompatibleWithEpoll, - - Unexpected, -}; - -pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: *epoll_event) EpollCtlError!void { - const rc = system.epoll_ctl(epfd, op, fd, event); - switch (errno(rc)) { - 0 => return, - else => |err| return unexpectedErrno(err), - - EBADF => unreachable, // always a race condition if this happens - EEXIST => return error.FileDescriptorAlreadyPresentInSet, - EINVAL => unreachable, - ELOOP => return error.OperationCausesCircularLoop, - ENOENT => return error.FileDescriptorNotRegistered, - ENOMEM => return error.SystemResources, - ENOSPC => return error.UserResourceLimitReached, - EPERM => return error.FileDescriptorIncompatibleWithEpoll, - } -} - -/// Waits for an I/O event on an epoll file descriptor. -/// Returns the number of file descriptors ready for the requested I/O, -/// or zero if no file descriptor became ready during the requested timeout milliseconds. -pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize { - while (true) { - // TODO get rid of the @intCast - const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout); - switch (errno(rc)) { - 0 => return rc, - EINTR => continue, - EBADF => unreachable, - EFAULT => unreachable, - EINVAL => unreachable, - else => unreachable, - } - } -} - -pub const EventFdError = error{ - SystemResources, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - Unexpected, -}; - -pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { - const rc = system.eventfd(initval, flags); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - else => |err| return unexpectedErrno(err), - - EINVAL => unreachable, // invalid parameters - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENODEV => return error.SystemResources, - ENOMEM => return error.SystemResources, - } -} - -pub const GetSockNameError = error{ - /// Insufficient resources were available in the system to perform the operation. - SystemResources, - - Unexpected, -}; - -pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr { - var addr: sockaddr = undefined; - var addrlen: socklen_t = @sizeOf(sockaddr); - switch (errno(system.getsockname(sockfd, &addr, &addrlen))) { - 0 => return addr, - else => |err| return unexpectedErrno(err), - - EBADF => unreachable, // always a race condition - EFAULT => unreachable, - EINVAL => unreachable, // invalid parameters - ENOTSOCK => unreachable, - ENOBUFS => return error.SystemResources, - } -} - -pub const ConnectError = error{ - /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket - /// file, or search permission is denied for one of the directories in the path prefix. - /// or - /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or - /// the connection request failed because of a local firewall rule. - PermissionDenied, - - /// Local address is already in use. - AddressInUse, - - /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an - /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers - /// in the ephemeral port range are currently in use. See the discussion of - /// /proc/sys/net/ipv4/ip_local_port_range in ip(7). - AddressNotAvailable, - - /// The passed address didn't have the correct address family in its sa_family field. - AddressFamilyNotSupported, - - /// Insufficient entries in the routing cache. - SystemResources, - - /// A connect() on a stream socket found no one listening on the remote address. - ConnectionRefused, - - /// Network is unreachable. - NetworkUnreachable, - - /// Timeout while attempting connection. The server may be too busy to accept new connections. Note - /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. - ConnectionTimedOut, - - Unexpected, -}; - -/// Initiate a connection on a socket. -/// This is for blocking file descriptors only. -/// For non-blocking, see `connect_async`. -pub fn connect(sockfd: i32, sockaddr: *const sockaddr) ConnectError!void { - while (true) { - switch (errno(system.connect(sockfd, sockaddr, @sizeOf(sockaddr)))) { - 0 => return, - else => |err| return unexpectedErrno(err), - - EACCES => return error.PermissionDenied, - EPERM => return error.PermissionDenied, - EADDRINUSE => return error.AddressInUse, - EADDRNOTAVAIL => return error.AddressNotAvailable, - EAFNOSUPPORT => return error.AddressFamilyNotSupported, - EAGAIN => return error.SystemResources, - EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. - EBADF => unreachable, // sockfd is not a valid open file descriptor. - ECONNREFUSED => return error.ConnectionRefused, - EFAULT => unreachable, // The socket structure address is outside the user's address space. - EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately. - EINTR => continue, - EISCONN => unreachable, // The socket is already connected. - ENETUNREACH => return error.NetworkUnreachable, - ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - ETIMEDOUT => return error.ConnectionTimedOut, - } - } -} - -/// Same as `connect` except it is for blocking socket file descriptors. -/// It expects to receive EINPROGRESS`. -pub fn connect_async(sockfd: i32, sockaddr: *const c_void, len: u32) ConnectError!void { - while (true) { - switch (errno(system.connect(sockfd, sockaddr, len))) { - 0, EINPROGRESS => return, - EINTR => continue, - else => |err| return unexpectedErrno(err), - - EACCES => return error.PermissionDenied, - EPERM => return error.PermissionDenied, - EADDRINUSE => return error.AddressInUse, - EADDRNOTAVAIL => return error.AddressNotAvailable, - EAFNOSUPPORT => return error.AddressFamilyNotSupported, - EAGAIN => return error.SystemResources, - EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. - EBADF => unreachable, // sockfd is not a valid open file descriptor. - ECONNREFUSED => return error.ConnectionRefused, - EFAULT => unreachable, // The socket structure address is outside the user's address space. - EISCONN => unreachable, // The socket is already connected. - ENETUNREACH => return error.NetworkUnreachable, - ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - ETIMEDOUT => return error.ConnectionTimedOut, - } - } -} - -pub fn getsockoptError(sockfd: i32) ConnectError!void { - var err_code: i32 = undefined; - var size: u32 = @sizeOf(i32); - const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size); - assert(size == 4); - switch (errno(rc)) { - 0 => switch (err_code) { - 0 => return, - EACCES => return error.PermissionDenied, - EPERM => return error.PermissionDenied, - EADDRINUSE => return error.AddressInUse, - EADDRNOTAVAIL => return error.AddressNotAvailable, - EAFNOSUPPORT => return error.AddressFamilyNotSupported, - EAGAIN => return error.SystemResources, - EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. - EBADF => unreachable, // sockfd is not a valid open file descriptor. - ECONNREFUSED => return error.ConnectionRefused, - EFAULT => unreachable, // The socket structure address is outside the user's address space. - EISCONN => unreachable, // The socket is already connected. - ENETUNREACH => return error.NetworkUnreachable, - ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - ETIMEDOUT => return error.ConnectionTimedOut, - else => |err| return unexpectedErrno(err), - }, - EBADF => unreachable, // The argument sockfd is not a valid file descriptor. - EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space. - EINVAL => unreachable, - ENOPROTOOPT => unreachable, // The option is unknown at the level indicated. - ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - else => |err| return unexpectedErrno(err), - } -} - -pub fn waitpid(pid: i32) i32 { - var status: i32 = undefined; - while (true) { - switch (errno(system.waitpid(pid, &status, 0))) { - 0 => return status, - EINTR => continue, - ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - EINVAL => unreachable, // The options argument was invalid - else => unreachable, - } - } -} - -pub const FStatError = error{ - SystemResources, - Unexpected, -}; - -pub fn fstat(fd: fd_t) FStatError!Stat { - var stat: Stat = undefined; - if (os.darwin.is_the_target) { - switch (errno(system.@"fstat$INODE64"(fd, buf))) { - 0 => return stat, - EBADF => unreachable, // Always a race condition. - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } - - switch (errno(system.fstat(fd, &stat))) { - 0 => return stat, - EBADF => unreachable, // Always a race condition. - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const KQueueError = error{ - /// The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - Unexpected, -}; - -pub fn kqueue() KQueueError!i32 { - const rc = system.kqueue(); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - -pub const KEventError = error{ - /// The process does not have permission to register a filter. - AccessDenied, - - /// The event could not be found to be modified or deleted. - EventNotFound, - - /// No memory was available to register the event. - SystemResources, - - /// The specified process to attach to does not exist. - ProcessNotFound, -}; - -pub fn kevent( - kq: i32, - changelist: []const Kevent, - eventlist: []Kevent, - timeout: ?*const timespec, -) KEventError!usize { - while (true) { - const rc = system.kevent(kq, changelist, eventlist, timeout); - switch (errno(rc)) { - 0 => return rc, - EACCES => return error.AccessDenied, - EFAULT => unreachable, - EBADF => unreachable, // Always a race condition. - EINTR => continue, - EINVAL => unreachable, - ENOENT => return error.EventNotFound, - ENOMEM => return error.SystemResources, - ESRCH => return error.ProcessNotFound, - else => unreachable, - } - } -} - -pub const INotifyInitError = error{ - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - SystemResources, - Unexpected, -}; - -/// initialize an inotify instance -pub fn inotify_init1(flags: u32) INotifyInitError!i32 { - const rc = system.inotify_init1(flags); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EINVAL => unreachable, - EMFILE => return error.ProcessFdQuotaExceeded, - ENFILE => return error.SystemFdQuotaExceeded, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyAddWatchError = error{ - AccessDenied, - NameTooLong, - FileNotFound, - SystemResources, - UserResourceLimitReached, - Unexpected, -}; - -/// add a watch to an initialized inotify instance -pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { - const pathname_c = try toPosixPath(pathname); - return inotify_add_watchC(inotify_fd, &pathname_c, mask); -} - -/// Same as `inotify_add_watch` except pathname is null-terminated. -pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 { - const rc = system.inotify_add_watch(inotify_fd, pathname, mask); - switch (errno(rc)) { - 0 => return @intCast(i32, rc), - EACCES => return error.AccessDenied, - EBADF => unreachable, - EFAULT => unreachable, - EINVAL => unreachable, - ENAMETOOLONG => return error.NameTooLong, - ENOENT => return error.FileNotFound, - ENOMEM => return error.SystemResources, - ENOSPC => return error.UserResourceLimitReached, - else => |err| return unexpectedErrno(err), - } -} - -/// remove an existing watch from an inotify instance -pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { - switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { - 0 => return, - EBADF => unreachable, - EINVAL => unreachable, - else => unreachable, - } -} - -pub const MProtectError = error{ - AccessDenied, - OutOfMemory, - Unexpected, -}; - -/// address and length must be page-aligned -pub fn mprotect(address: usize, length: usize, protection: u32) MProtectError!void { - const negative_page_size = @bitCast(usize, -isize(os.page_size)); - const aligned_address = address & negative_page_size; - const aligned_end = (address + length + os.page_size - 1) & negative_page_size; - assert(address == aligned_address); - assert(length == aligned_end - aligned_address); - switch (errno(system.mprotect(address, length, protection))) { - 0 => return, - EINVAL => unreachable, - EACCES => return error.AccessDenied, - ENOMEM => return error.OutOfMemory, - else => return unexpectedErrno(err), - } -} - -pub const ForkError = error{ - SystemResources, - Unexpected, -}; - -pub fn fork() ForkError!pid_t { - const rc = system.fork(); - switch (errno(rc)) { - 0 => return rc, - EAGAIN => return error.SystemResources, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const MMapError = error{ - AccessDenied, - PermissionDenied, - LockedMemoryLimitExceeded, - SystemFdQuotaExceeded, - MemoryMappingNotSupported, - OutOfMemory, -}; - -/// Map files or devices into memory. -/// 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(address: ?[*]u8, length: usize, prot: u32, flags: u32, fd: fd_t, offset: isize) MMapError!usize { - const err = if (builtin.link_libc) blk: { - const rc = system.mmap(address, length, prot, flags, fd, offset); - if (rc != system.MMAP_FAILED) return rc; - break :blk system._errno().*; - } else blk: { - const rc = system.mmap(address, length, prot, flags, fd, offset); - const err = errno(rc); - if (err == 0) return rc; - break :blk err; - }; - switch (err) { - ETXTBSY => return error.AccessDenied, - EACCES => return error.AccessDenied, - EPERM => return error.PermissionDenied, - EAGAIN => return error.LockedMemoryLimitExceeded, - EBADF => unreachable, // Always a race condition. - EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow. - ENFILE => return error.SystemFdQuotaExceeded, - ENODEV => return error.MemoryMappingNotSupported, - EINVAL => unreachable, // Invalid parameters to mmap() - ENOMEM => return error.OutOfMemory, - else => return unexpectedErrno(err), - } -} - -/// Deletes the mappings for the specified address range, causing -/// further references to addresses within the range to generate invalid memory references. -/// Note that while POSIX allows unmapping a region in the middle of an existing mapping, -/// Zig's munmap function does not, for two reasons: -/// * It violates the Zig principle that resource deallocation must succeed. -/// * The Windows function, VirtualFree, has this restriction. -pub fn munmap(address: usize, length: usize) void { - switch (errno(system.munmap(address, length))) { - 0 => return, - EINVAL => unreachable, // Invalid parameters. - ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping. - else => unreachable, - } -} - -pub const AccessError = error{ - PermissionDenied, - FileNotFound, - NameTooLong, - InputOutput, - SystemResources, - BadPathName, - - /// On Windows, file paths must be valid Unicode. - InvalidUtf8, - - Unexpected, -}; - -/// check user's permissions for a file -pub fn access(path: []const u8, mode: u32) AccessError!void { - if (windows.is_the_target and !builtin.link_libc) { - const path_w = try sliceToPrefixedFileW(path); - return accessW(&path_w, mode); - } - const path_c = try toPosixPath(path); - return accessC(&path_c, mode); -} - -/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string. -/// Otherwise use `access` or `accessC`. -/// TODO currently this ignores `mode`. -pub fn accessW(path: [*]const u16, mode: u32) AccessError!void { - if (windows.GetFileAttributesW(path) != windows.INVALID_FILE_ATTRIBUTES) { - return; - } - switch (windows.GetLastError()) { - windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, - windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, - windows.ERROR.ACCESS_DENIED => return error.PermissionDenied, - else => |err| return windows.unexpectedError(err), - } -} - -/// Call if you have a UTF-8 encoded, null-terminated string. -/// Otherwise use `access` or `accessW`. -pub fn accessC(path: [*]const u8, mode: u32) AccessError!void { - if (windows.is_the_target) { - const path_w = try cStrToPrefixedFileW(path); - return accessW(&path_w, mode); - } - switch (errno(system.access(path, mode))) { - 0 => return, - EACCES => return error.PermissionDenied, - EROFS => return error.PermissionDenied, - ELOOP => return error.PermissionDenied, - ETXTBSY => return error.PermissionDenied, - ENOTDIR => return error.FileNotFound, - ENOENT => return error.FileNotFound, - - ENAMETOOLONG => return error.NameTooLong, - EINVAL => unreachable, - EFAULT => unreachable, - EIO => return error.InputOutput, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const PipeError = error{ - SystemFdQuotaExceeded, - ProcessFdQuotaExceeded, -}; - -/// Creates a unidirectional data channel that can be used for interprocess communication. -pub fn pipe(fds: *[2]fd_t) PipeError!void { - switch (errno(system.pipe(fds))) { - 0 => return, - EINVAL => unreachable, // Invalid parameters to pipe() - EFAULT => unreachable, // Invalid fds pointer - ENFILE => return error.SystemFdQuotaExceeded, - EMFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - -pub fn pipe2(fds: *[2]fd_t, flags: u32) PipeError!void { - switch (errno(system.pipe2(fds, flags))) { - 0 => return, - EINVAL => unreachable, // Invalid flags - EFAULT => unreachable, // Invalid fds pointer - ENFILE => return error.SystemFdQuotaExceeded, - EMFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - -pub const SysCtlError = error{ - PermissionDenied, - SystemResources, - Unexpected, -}; - -pub fn sysctl( - name: []const c_int, - oldp: ?*c_void, - oldlenp: ?*usize, - newp: ?*c_void, - newlen: usize, -) SysCtlError!void { - switch (errno(system.sysctl(name.ptr, name.len, oldp, oldlenp, newp, newlen))) { - 0 => return, - EFAULT => unreachable, - EPERM => return error.PermissionDenied, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub fn sysctlbynameC( - name: [*]const u8, - oldp: ?*c_void, - oldlenp: ?*usize, - newp: ?*c_void, - newlen: usize, -) SysCtlError!void { - switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { - 0 => return, - EFAULT => unreachable, - EPERM => return error.PermissionDenied, - ENOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { - switch (errno(system.gettimeofday(tv, tz))) { - 0 => return, - EINVAL => unreachable, - else => unreachable, - } -} - -pub fn nanosleep(req: timespec) void { - var rem = req; - while (true) { - switch (errno(system.nanosleep(&rem, &rem))) { - 0 => return, - EINVAL => unreachable, // Invalid parameters. - EFAULT => unreachable, - EINTR => continue, - } - } -} - -pub const realpath = std.os.path.real; -pub const realpathC = std.os.path.realC; -pub const realpathW = std.os.path.realW; - -pub const WaitForSingleObjectError = error{ - WaitAbandoned, - WaitTimeOut, - Unexpected, -}; - -pub fn WaitForSingleObject(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitForSingleObjectError!void { - switch (windows.WaitForSingleObject(handle, milliseconds)) { - windows.WAIT_ABANDONED => return error.WaitAbandoned, - windows.WAIT_OBJECT_0 => return, - windows.WAIT_TIMEOUT => return error.WaitTimeOut, - windows.WAIT_FAILED => { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - }, - else => return error.Unexpected, - } -} - -pub fn FindFirstFile( - dir_path: []const u8, - find_file_data: *windows.WIN32_FIND_DATAW, -) !windows.HANDLE { - const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{ '\\', '*', 0 }); - const handle = windows.FindFirstFileW(&dir_path_w, find_file_data); - - if (handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, - windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, - else => |err| return windows.unexpectedError(err), - } - } - - return handle; -} - -/// Returns `true` if there was another file, `false` otherwise. -pub fn FindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAW) !bool { - if (windows.FindNextFileW(handle, find_file_data) == 0) { - switch (windows.GetLastError()) { - windows.ERROR.NO_MORE_FILES => return false, - else => |err| return windows.unexpectedError(err), - } - } - return true; -} - -pub const CreateIoCompletionPortError = error{Unexpected}; - -pub fn CreateIoCompletionPort( - file_handle: windows.HANDLE, - existing_completion_port: ?windows.HANDLE, - completion_key: usize, - concurrent_thread_count: windows.DWORD, -) CreateIoCompletionPortError!windows.HANDLE { - const handle = windows.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { - switch (windows.GetLastError()) { - windows.ERROR.INVALID_PARAMETER => unreachable, - else => |err| return windows.unexpectedError(err), - } - }; - return handle; -} - -pub const WindowsPostQueuedCompletionStatusError = error{Unexpected}; - -pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_transferred_count: windows.DWORD, completion_key: usize, lpOverlapped: ?*windows.OVERLAPPED) WindowsPostQueuedCompletionStatusError!void { - if (windows.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) { - const err = windows.GetLastError(); - switch (err) { - else => return windows.unexpectedError(err), - } - } -} - -pub const GetQueuedCompletionStatusResult = enum { - Normal, - Aborted, - Cancelled, - EOF, -}; - -pub fn GetQueuedCompletionStatus( - completion_port: windows.HANDLE, - bytes_transferred_count: *windows.DWORD, - lpCompletionKey: *usize, - lpOverlapped: *?*windows.OVERLAPPED, - dwMilliseconds: windows.DWORD, -) GetQueuedCompletionStatusResult { - if (windows.GetQueuedCompletionStatus(completion_port, bytes_transferred_count, lpCompletionKey, lpOverlapped, dwMilliseconds) == windows.FALSE) { - switch (windows.GetLastError()) { - windows.ERROR.ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted, - windows.ERROR.OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Cancelled, - windows.ERROR.HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF, - else => |err| { - if (std.debug.runtime_safety) { - std.debug.panic("unexpected error: {}\n", err); - } - }, - } - } - return GetQueuedCompletionStatusResult.Normal; -} - -/// Used to convert a slice to a null terminated slice on the stack. -/// TODO https://github.com/ziglang/zig/issues/287 -pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 { - var path_with_null: [PATH_MAX]u8 = undefined; - // >= rather than > to make room for the null byte - if (file_path.len >= PATH_MAX) return error.NameTooLong; - mem.copy(u8, &path_with_null, file_path); - path_with_null[file_path.len] = 0; - return path_with_null; -} - -/// Call this when you made a syscall or something that sets errno -/// and you get an unexpected error. -pub fn unexpectedErrno(errno: usize) os.UnexpectedError { - if (os.unexpected_error_tracing) { - std.debug.warn("unexpected errno: {}\n", errno); - std.debug.dumpCurrentStackTrace(null); - } - return error.Unexpected; -} diff --git a/std/os/test.zig b/std/os/test.zig index 0ee6fc1f2..35824001a 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -1,5 +1,6 @@ const std = @import("../std.zig"); const os = std.os; +const testing = std.testing; const expect = std.testing.expect; const io = std.io; const mem = std.mem; @@ -127,3 +128,24 @@ fn testTls(context: void) void { x += 1; if (x != 1235) @panic("bad end value"); } + +test "getrandom" { + var buf_a: [50]u8 = undefined; + var buf_b: [50]u8 = undefined; + try os.getrandom(&buf_a); + try os.getrandom(&buf_b); + // If this test fails the chance is significantly higher that there is a bug than + // that two sets of 50 bytes were equal. + expect(!mem.eql(u8, buf_a, buf_b)); +} + +test "getcwd" { + // at least call it so it gets compiled + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + _ = os.getcwd(&buf) catch {}; +} + +test "realpath" { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + testing.expectError(error.FileNotFound, os.realpath("definitely_bogus_does_not_exist1234", &buf)); +} diff --git a/std/os/windows.zig b/std/os/windows.zig index c8ee63f33..d1b8c81ed 100644 --- a/std/os/windows.zig +++ b/std/os/windows.zig @@ -535,7 +535,7 @@ pub const COINIT = extern enum { /// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation pub const PATH_MAX_WIDE = 32767; -pub const OpenError = error{ +pub const CreateFileError = error{ SharingViolation, PathAlreadyExists, @@ -565,7 +565,7 @@ pub fn CreateFile( share_mode: DWORD, creation_disposition: DWORD, flags_and_attrs: DWORD, -) OpenError!fd_t { +) CreateFileError!fd_t { const file_path_w = try sliceToPrefixedFileW(file_path); return CreateFileW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs); } @@ -576,7 +576,7 @@ pub fn CreateFileW( share_mode: DWORD, creation_disposition: DWORD, flags_and_attrs: DWORD, -) OpenError!HANDLE { +) CreateFileError!HANDLE { const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); if (result == INVALID_HANDLE_VALUE) { @@ -588,6 +588,7 @@ pub fn CreateFileW( ERROR.PATH_NOT_FOUND => return error.FileNotFound, ERROR.ACCESS_DENIED => return error.AccessDenied, ERROR.PIPE_BUSY => return error.PipeBusy, + ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, else => |err| return unexpectedErrorWindows(err), } } @@ -615,6 +616,451 @@ fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformati } } +pub const RtlGenRandomError = error{Unexpected}; + +/// Call RtlGenRandom() instead of CryptGetRandom() on Windows +/// https://github.com/rust-lang-nursery/rand/issues/111 +/// https://bugzilla.mozilla.org/show_bug.cgi?id=504270 +pub fn RtlGenRandom(output: []u8) RtlGenRandomError!void { + if (advapi32.RtlGenRandom(output.ptr, output.len) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} + +pub const WaitForSingleObjectError = error{ + WaitAbandoned, + WaitTimeOut, + Unexpected, +}; + +pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void { + switch (kernel32.WaitForSingleObject(handle, milliseconds)) { + WAIT_ABANDONED => return error.WaitAbandoned, + WAIT_OBJECT_0 => return, + WAIT_TIMEOUT => return error.WaitTimeOut, + WAIT_FAILED => switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + }, + else => return error.Unexpected, + } +} + +pub const FindFirstFileError = error{ + FileNotFound, + Unexpected, +}; + +pub fn FindFirstFile(dir_path: []const u8, find_file_data: *WIN32_FIND_DATAW) FindFirstFileError!HANDLE { + const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{ '\\', '*', 0 }); + const handle = kernel32.FindFirstFileW(&dir_path_w, find_file_data); + + if (handle == INVALID_HANDLE_VALUE) { + switch (kernel32.GetLastError()) { + ERROR.FILE_NOT_FOUND => return error.FileNotFound, + ERROR.PATH_NOT_FOUND => return error.FileNotFound, + else => |err| return unexpectedError(err), + } + } + + return handle; +} + +pub const FindNextFileError = error{Unexpected}; + +/// Returns `true` if there was another file, `false` otherwise. +pub fn FindNextFile(handle: HANDLE, find_file_data: *WIN32_FIND_DATAW) FindNextFileError!bool { + if (kernel32.FindNextFileW(handle, find_file_data) == 0) { + switch (kernel32.GetLastError()) { + ERROR.NO_MORE_FILES => return false, + else => |err| return unexpectedError(err), + } + } + return true; +} + +pub const CreateIoCompletionPortError = error{Unexpected}; + +pub fn CreateIoCompletionPort( + file_handle: HANDLE, + existing_completion_port: ?HANDLE, + completion_key: usize, + concurrent_thread_count: DWORD, +) CreateIoCompletionPortError!HANDLE { + const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { + switch (kernel32.GetLastError()) { + ERROR.INVALID_PARAMETER => unreachable, + else => |err| return unexpectedError(err), + } + }; + return handle; +} + +pub const PostQueuedCompletionStatusError = error{Unexpected}; + +pub fn PostQueuedCompletionStatus( + completion_port: HANDLE, + bytes_transferred_count: DWORD, + completion_key: usize, + lpOverlapped: ?*OVERLAPPED, +) PostQueuedCompletionStatusError!void { + if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) { + switch (kernel32.GetLastError()) { + else => return unexpectedError(err), + } + } +} + +pub const GetQueuedCompletionStatusResult = enum { + Normal, + Aborted, + Cancelled, + EOF, +}; + +pub fn GetQueuedCompletionStatus( + completion_port: HANDLE, + bytes_transferred_count: *DWORD, + lpCompletionKey: *usize, + lpOverlapped: *?*OVERLAPPED, + dwMilliseconds: DWORD, +) GetQueuedCompletionStatusResult { + if (kernel32.GetQueuedCompletionStatus( + completion_port, + bytes_transferred_count, + lpCompletionKey, + lpOverlapped, + dwMilliseconds, + ) == FALSE) { + switch (kernel32.GetLastError()) { + ERROR.ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted, + ERROR.OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Cancelled, + ERROR.HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF, + else => |err| { + if (std.debug.runtime_safety) { + std.debug.panic("unexpected error: {}\n", err); + } + }, + } + } + return GetQueuedCompletionStatusResult.Normal; +} + +pub fn CloseHandle(hObject: HANDLE) void { + assert(kernel32.CloseHandle(hObject) != 0); +} + +pub const ReadFileError = error{Unexpected}; + +pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize { + var index: usize = 0; + while (index < buffer.len) { + const want_read_count = @intCast(DWORD, math.min(DWORD(maxInt(DWORD)), buffer.len - index)); + var amt_read: DWORD = undefined; + if (kernel32.ReadFile(fd, buffer.ptr + index, want_read_count, &amt_read, null) == 0) { + switch (kernel32.GetLastError()) { + ERROR.OPERATION_ABORTED => continue, + ERROR.BROKEN_PIPE => return index, + else => |err| return unexpectedError(err), + } + } + if (amt_read == 0) return index; + index += amt_read; + } + return index; +} + +pub const WriteFileError = error{ + SystemResources, + OperationAborted, + BrokenPipe, + Unexpected, +}; + +/// This function is for blocking file descriptors only. For non-blocking, see +/// `WriteFileAsync`. +pub fn WriteFile(in_hFile: HANDLE, bytes: []const u8) WriteFileError!void { + var bytes_written: DWORD = undefined; + // TODO replace this @intCast with a loop that writes all the bytes + if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) { + switch (kernel32.GetLastError()) { + ERROR.INVALID_USER_BUFFER => return error.SystemResources, + ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, + ERROR.OPERATION_ABORTED => return error.OperationAborted, + ERROR.NOT_ENOUGH_QUOTA => return error.SystemResources, + ERROR.IO_PENDING => unreachable, // this function is for blocking files only + ERROR.BROKEN_PIPE => return error.BrokenPipe, + else => |err| return unexpectedError(err), + } + } +} + +pub const GetCurrentDirectoryError = error{ + NameTooLong, + Unexpected, +}; + +/// The result is a slice of out_buffer, indexed from 0. +pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { + var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined; + const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf); + if (result == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } + assert(result <= utf16le_buf.len); + const utf16le_slice = utf16le_buf[0..result]; + // Trust that Windows gives us valid UTF-16LE. + var end_index: usize = 0; + var it = std.unicode.Utf16LeIterator.init(utf16le); + while (it.nextCodepoint() catch unreachable) |codepoint| { + if (end_index + std.unicode.utf8CodepointSequenceLength(codepoint) >= out_buffer.len) + return error.NameTooLong; + end_index += utf8Encode(codepoint, out_buffer[end_index..]) catch unreachable; + } + return out_buffer[0..end_index]; +} + +pub const CreateSymbolicLinkError = error{Unexpected}; + +pub fn CreateSymbolicLink( + sym_link_path: []const u8, + target_path: []const u8, + flags: DWORD, +) CreateSymbolicLinkError!void { + const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); + const target_path_w = try sliceToPrefixedFileW(target_path); + return CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, flags); +} + +pub fn CreateSymbolicLinkW( + sym_link_path: [*]const u16, + target_path: [*]const u16, + flags: DWORD, +) CreateSymbolicLinkError!void { + if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return kernel32.unexpectedError(err), + } + } +} + +pub const DeleteFileError = error{ + FileNotFound, + AccessDenied, + NameTooLong, + Unexpected, +}; + +pub fn DeleteFile(filename: []const u8) DeleteFileError!void { + const filename_w = try sliceToPrefixedFileW(filename); + return DeleteFileW(&filename_w); +} + +pub fn DeleteFileW(filename: [*]const u16) DeleteFileError!void { + if (kernel32.DeleteFileW(file_path) == 0) { + switch (kernel32.GetLastError()) { + ERROR.FILE_NOT_FOUND => return error.FileNotFound, + ERROR.ACCESS_DENIED => return error.AccessDenied, + ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + ERROR.INVALID_PARAMETER => return error.NameTooLong, + else => |err| return unexpectedError(err), + } + } +} + +pub const MoveFileError = error{Unexpected}; + +pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { + const old_path_w = try sliceToPrefixedFileW(old_path); + const new_path_w = try sliceToPrefixedFileW(new_path); + return MoveFileExW(&old_path_w, &new_path_w, flags); +} + +pub fn MoveFileExW(old_path: [*]const u16, new_path: [*]const u16, flags: DWORD) MoveFileError!void { + if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} + +pub const CreateDirectoryError = error{ + PathAlreadyExists, + FileNotFound, + Unexpected, +}; + +pub fn CreateDirectory(pathname: []const u8, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { + const pathname_w = try sliceToPrefixedFileW(pathname); + return CreateDirectoryW(&pathname_w, attrs); +} + +pub fn CreateDirectoryW(pathname: [*]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { + if (kernel32.CreateDirectoryW(pathname, attrs) == 0) { + switch (kernel32.GetLastError()) { + ERROR.ALREADY_EXISTS => return error.PathAlreadyExists, + ERROR.PATH_NOT_FOUND => return error.FileNotFound, + else => |err| return unexpectedError(err), + } + } +} + +pub const RemoveDirectoryError = error{ + FileNotFound, + DirNotEmpty, + Unexpected, +}; + +pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void { + const dir_path_w = try sliceToPrefixedFileW(dir_path); + return RemoveDirectoryW(&dir_path_w); +} + +pub fn RemoveDirectoryW(dir_path_w: [*]const u16) RemoveDirectoryError!void { + if (kernel32.RemoveDirectoryW(dir_path_w) == 0) { + switch (kernel32.GetLastError()) { + ERROR.PATH_NOT_FOUND => return error.FileNotFound, + ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty, + else => |err| return unexpectedError(err), + } + } +} + +pub const GetStdHandleError = error{ + NoStandardHandleAttached, + Unexpected, +}; + +pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!fd_t { + const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached; + if (handle == INVALID_HANDLE_VALUE) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } + return handle; +} + +pub const SetFilePointerError = error{Unexpected}; + +/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`. +pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void { + // "The starting point is zero or the beginning of the file. If [FILE_BEGIN] + // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value." + // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex + const ipos = @bitCast(LARGE_INTEGER, offset); + if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) { + switch (kernel32.GetLastError()) { + ERROR.INVALID_PARAMETER => unreachable, + ERROR.INVALID_HANDLE => unreachable, + else => |err| return unexpectedError(err), + } + } +} + +/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`. +pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void { + if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) { + switch (kernel32.GetLastError()) { + ERROR.INVALID_PARAMETER => unreachable, + ERROR.INVALID_HANDLE => unreachable, + else => |err| return unexpectedError(err), + } + } +} + +/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`. +pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void { + if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) { + switch (kernel32.GetLastError()) { + ERROR.INVALID_PARAMETER => unreachable, + ERROR.INVALID_HANDLE => unreachable, + else => |err| return unexpectedError(err), + } + } +} + +/// The SetFilePointerEx function with parameters to get the current offset. +pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { + var result: LARGE_INTEGER = undefined; + if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) { + switch (kernel32.GetLastError()) { + ERROR.INVALID_PARAMETER => unreachable, + ERROR.INVALID_HANDLE => unreachable, + else => |err| return unexpectedError(err), + } + } + // Based on the docs for FILE_BEGIN, it seems that the returned signed integer + // should be interpreted as an unsigned integer. + return @bitCast(u64, result); +} + +pub const GetFinalPathNameByHandleError = error{ + FileNotFound, + SystemResources, + NameTooLong, + Unexpected, +}; + +pub fn GetFinalPathNameByHandleW( + hFile: HANDLE, + buf_ptr: [*]u16, + buf_len: DWORD, + flags: DWORD, +) GetFinalPathNameByHandleError!DWORD { + const rc = kernel32.GetFinalPathNameByHandleW(h_file, buf_ptr, buf.len, flags); + if (rc == 0) { + switch (kernel32.GetLastError()) { + ERROR.FILE_NOT_FOUND => return error.FileNotFound, + ERROR.PATH_NOT_FOUND => return error.FileNotFound, + ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, + ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + ERROR.INVALID_PARAMETER => unreachable, + else => |err| return unexpectedError(err), + } + } + return rc; +} + +pub const GetFileSizeError = error{Unexpected}; + +pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 { + var file_size: LARGE_INTEGER = undefined; + if (kernel32.GetFileSizeEx(hFile, &file_size) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } + return @bitCast(u64, file_size); +} + +pub const GetFileAttributesError = error{ + FileNotFound, + PermissionDenied, + Unexpected, +}; + +pub fn GetFileAttributes(filename: []const u8) GetFileAttributesError!DWORD { + const filename_w = try sliceToPrefixedFileW(filename); + return GetFileAttributesW(&filename_w); +} + +pub fn GetFileAttributesW(lpFileName: [*]const u16) GetFileAttributesError!DWORD { + const rc = kernel32.GetFileAttributesW(path); + if (rc == INVALID_FILE_ATTRIBUTES) { + switch (kernel32.GetLastError()) { + ERROR.FILE_NOT_FOUND => return error.FileNotFound, + ERROR.PATH_NOT_FOUND => return error.FileNotFound, + ERROR.ACCESS_DENIED => return error.PermissionDenied, + else => |err| return unexpectedError(err), + } + } + return rc; +} + pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 { return sliceToPrefixedFileW(mem.toSliceConst(u8, s)); } diff --git a/std/packed_int_array.zig b/std/packed_int_array.zig index 065f1becd..35cffec21 100644 --- a/std/packed_int_array.zig +++ b/std/packed_int_array.zig @@ -619,7 +619,7 @@ test "PackedIntArray at end of available memory" { const PackedArray = PackedIntArray(u3, 8); const Padded = struct { - _: [std.os.page_size - @sizeOf(PackedArray)]u8, + _: [std.mem.page_size - @sizeOf(PackedArray)]u8, p: PackedArray, }; @@ -641,9 +641,9 @@ test "PackedIntSlice at end of available memory" { var da = std.heap.DirectAllocator.init(); const allocator = &da.allocator; - var page = try allocator.alloc(u8, std.os.page_size); + var page = try allocator.alloc(u8, std.mem.page_size); defer allocator.free(page); - var p = PackedSlice.init(page[std.os.page_size - 2 ..], 1); + var p = PackedSlice.init(page[std.mem.page_size - 2 ..], 1); p.set(0, std.math.maxInt(u11)); } diff --git a/std/pdb.zig b/std/pdb.zig index f3b73663e..0f3a6d00b 100644 --- a/std/pdb.zig +++ b/std/pdb.zig @@ -660,8 +660,7 @@ const MsfStream = struct { return size; } - // XXX: The `len` parameter should be signed - fn seekForward(self: *MsfStream, len: u64) !void { + fn seekBy(self: *MsfStream, len: i64) !void { self.pos += len; if (self.pos >= self.blocks.len * self.block_size) return error.EOF; diff --git a/std/process.zig b/std/process.zig new file mode 100644 index 000000000..f2f1dbf4f --- /dev/null +++ b/std/process.zig @@ -0,0 +1,583 @@ +const std = @import("std.zig"); +const posix = std.os.posix; +const BufMap = std.BufMap; +const mem = std.mem; +const Allocator = mem.Allocator; +const assert = std.debug.assert; +const testing = std.testing; + +pub const abort = posix.abort; +pub const exit = posix.exit; + +/// Caller must free result when done. +/// TODO make this go through libc when we have it +pub fn getEnvMap(allocator: *Allocator) !BufMap { + var result = BufMap.init(allocator); + errdefer result.deinit(); + + if (is_windows) { + const ptr = windows.GetEnvironmentStringsW() orelse return error.OutOfMemory; + defer assert(windows.FreeEnvironmentStringsW(ptr) != 0); + + var i: usize = 0; + while (true) { + if (ptr[i] == 0) return result; + + const key_start = i; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); + errdefer allocator.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); + errdefer allocator.free(value); + + i += 1; // skip over null byte + + try result.setMove(key, value); + } + } else if (builtin.os == Os.wasi) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + if (environ_sizes_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(environ_sizes_get_ret); + } + + // TODO: Verify that the documentation is incorrect + // https://github.com/WebAssembly/WASI/issues/27 + var environ = try allocator.alloc(?[*]u8, environ_count + 1); + defer allocator.free(environ); + var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size); + defer allocator.free(environ_buf); + + const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); + if (environ_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(environ_get_ret); + } + + for (environ) |env| { + if (env) |ptr| { + const pair = mem.toSlice(u8, ptr); + var parts = mem.separate(pair, "="); + const key = parts.next().?; + const value = parts.next().?; + try result.set(key, value); + } + } + return result; + } else { + for (posix.environ) |ptr| { + var line_i: usize = 0; + while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} + const key = ptr[0..line_i]; + + var end_i: usize = line_i; + while (ptr[end_i] != 0) : (end_i += 1) {} + const value = ptr[line_i + 1 .. end_i]; + + try result.set(key, value); + } + return result; + } +} + +test "os.getEnvMap" { + var env = try getEnvMap(std.debug.global_allocator); + defer env.deinit(); +} + +pub const GetEnvVarOwnedError = error{ + OutOfMemory, + EnvironmentVariableNotFound, + + /// See https://github.com/ziglang/zig/issues/1774 + InvalidUtf8, +}; + +/// Caller must free returned memory. +/// TODO make this go through libc when we have it +pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { + if (is_windows) { + const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); + defer allocator.free(key_with_null); + + var buf = try allocator.alloc(u16, 256); + defer allocator.free(buf); + + while (true) { + const windows_buf_len = math.cast(windows.DWORD, buf.len) catch return error.OutOfMemory; + const result = windows.GetEnvironmentVariableW(key_with_null.ptr, buf.ptr, windows_buf_len); + + if (result == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound, + else => { + windows.unexpectedError(err) catch {}; + return error.EnvironmentVariableNotFound; + }, + }; + } + + if (result > buf.len) { + buf = try allocator.realloc(buf, result); + continue; + } + + return std.unicode.utf16leToUtf8Alloc(allocator, buf) catch |err| switch (err) { + error.DanglingSurrogateHalf => return error.InvalidUtf8, + error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8, + error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8, + error.OutOfMemory => return error.OutOfMemory, + }; + } + } else { + const result = getEnvPosix(key) orelse return error.EnvironmentVariableNotFound; + return mem.dupe(allocator, u8, result); + } +} + +test "os.getEnvVarOwned" { + var ga = std.debug.global_allocator; + testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); +} + +pub const ArgIteratorPosix = struct { + index: usize, + count: usize, + + pub fn init() ArgIteratorPosix { + return ArgIteratorPosix{ + .index = 0, + .count = raw.len, + }; + } + + pub fn next(self: *ArgIteratorPosix) ?[]const u8 { + if (self.index == self.count) return null; + + const s = raw[self.index]; + self.index += 1; + return cstr.toSlice(s); + } + + pub fn skip(self: *ArgIteratorPosix) bool { + if (self.index == self.count) return false; + + self.index += 1; + return true; + } + + /// This is marked as public but actually it's only meant to be used + /// internally by zig's startup code. + pub var raw: [][*]u8 = undefined; +}; + +pub const ArgIteratorWindows = struct { + index: usize, + cmd_line: [*]const u8, + in_quote: bool, + quote_count: usize, + seen_quote_count: usize, + + pub const NextError = error{OutOfMemory}; + + pub fn init() ArgIteratorWindows { + return initWithCmdLine(windows.GetCommandLineA()); + } + + pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows { + return ArgIteratorWindows{ + .index = 0, + .cmd_line = cmd_line, + .in_quote = false, + .quote_count = countQuotes(cmd_line), + .seen_quote_count = 0, + }; + } + + /// You must free the returned memory when done. + pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return null, + ' ', '\t' => continue, + else => break, + } + } + + return self.internalNext(allocator); + } + + pub fn skip(self: *ArgIteratorWindows) bool { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return false, + ' ', '\t' => continue, + else => break, + } + } + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return true, + '"' => { + const quote_is_real = backslash_count % 2 == 0; + if (quote_is_real) { + self.seen_quote_count += 1; + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { + return true; + } + backslash_count = 0; + }, + else => { + backslash_count = 0; + continue; + }, + } + } + } + + fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 { + var buf = try Buffer.initSize(allocator, 0); + defer buf.deinit(); + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return buf.toOwnedSlice(), + '"' => { + const quote_is_real = backslash_count % 2 == 0; + try self.emitBackslashes(&buf, backslash_count / 2); + backslash_count = 0; + + if (quote_is_real) { + self.seen_quote_count += 1; + if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { + try buf.appendByte('"'); + } + } else { + try buf.appendByte('"'); + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { + try buf.appendByte(byte); + } else { + return buf.toOwnedSlice(); + } + }, + else => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + try buf.appendByte(byte); + }, + } + } + } + + fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void { + var i: usize = 0; + while (i < emit_count) : (i += 1) { + try buf.appendByte('\\'); + } + } + + fn countQuotes(cmd_line: [*]const u8) usize { + var result: usize = 0; + var backslash_count: usize = 0; + var index: usize = 0; + while (true) : (index += 1) { + const byte = cmd_line[index]; + switch (byte) { + 0 => return result, + '\\' => backslash_count += 1, + '"' => { + result += 1 - (backslash_count % 2); + backslash_count = 0; + }, + else => { + backslash_count = 0; + }, + } + } + } +}; + +pub const ArgIterator = struct { + const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix; + + inner: InnerType, + + pub fn init() ArgIterator { + if (builtin.os == Os.wasi) { + // TODO: Figure out a compatible interface accomodating WASI + @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead."); + } + + return ArgIterator{ .inner = InnerType.init() }; + } + + pub const NextError = ArgIteratorWindows.NextError; + + /// You must free the returned memory when done. + pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) { + if (builtin.os == Os.windows) { + return self.inner.next(allocator); + } else { + return mem.dupe(allocator, u8, self.inner.next() orelse return null); + } + } + + /// If you only are targeting posix you can call this and not need an allocator. + pub fn nextPosix(self: *ArgIterator) ?[]const u8 { + return self.inner.next(); + } + + /// Parse past 1 argument without capturing it. + /// Returns `true` if skipped an arg, `false` if we are at the end. + pub fn skip(self: *ArgIterator) bool { + return self.inner.skip(); + } +}; + +pub fn args() ArgIterator { + return ArgIterator.init(); +} + +/// Caller must call argsFree on result. +pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 { + if (builtin.os == Os.wasi) { + var count: usize = undefined; + var buf_size: usize = undefined; + + const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size); + if (args_sizes_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(args_sizes_get_ret); + } + + var argv = try allocator.alloc([*]u8, count); + defer allocator.free(argv); + + var argv_buf = try allocator.alloc(u8, buf_size); + const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr); + if (args_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(args_get_ret); + } + + var result_slice = try allocator.alloc([]u8, count); + + var i: usize = 0; + while (i < count) : (i += 1) { + result_slice[i] = mem.toSlice(u8, argv[i]); + } + + return result_slice; + } + + // TODO refactor to only make 1 allocation. + var it = args(); + var contents = try Buffer.initSize(allocator, 0); + defer contents.deinit(); + + var slice_list = ArrayList(usize).init(allocator); + defer slice_list.deinit(); + + while (it.next(allocator)) |arg_or_err| { + const arg = try arg_or_err; + defer allocator.free(arg); + try contents.append(arg); + try slice_list.append(arg.len); + } + + const contents_slice = contents.toSliceConst(); + const slice_sizes = slice_list.toSliceConst(); + const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); + const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); + const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); + errdefer allocator.free(buf); + + const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + mem.copy(u8, result_contents, contents_slice); + + var contents_index: usize = 0; + for (slice_sizes) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index]; + contents_index = new_index; + } + + return result_slice_list; +} + +pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { + if (builtin.os == Os.wasi) { + const last_item = args_alloc[args_alloc.len - 1]; + const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated + const first_item_ptr = args_alloc[0].ptr; + const len = last_byte_addr - @ptrToInt(first_item_ptr); + allocator.free(first_item_ptr[0..len]); + + return allocator.free(args_alloc); + } + + var total_bytes: usize = 0; + for (args_alloc) |arg| { + total_bytes += @sizeOf([]u8) + arg.len; + } + const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes]; + const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); + return allocator.free(aligned_allocated_buf); +} + +test "windows arg parsing" { + testWindowsCmdLine(c"a b\tc d", [][]const u8{ "a", "b", "c", "d" }); + testWindowsCmdLine(c"\"abc\" d e", [][]const u8{ "abc", "d", "e" }); + testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{ "a\\\\\\b", "de fg", "h" }); + testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{ "a\\\"b", "c", "d" }); + testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{ "a\\\\b c", "d", "e" }); + testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{ "a", "b", "c", "\"d", "f" }); + + testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8{ + ".\\..\\zig-cache\\build", + "bin\\zig.exe", + ".\\..", + ".\\..\\zig-cache", + "--help", + }); +} + +fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void { + var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); + for (expected_args) |expected_arg| { + const arg = it.next(std.debug.global_allocator).? catch unreachable; + testing.expectEqualSlices(u8, expected_arg, arg); + } + testing.expect(it.next(std.debug.global_allocator) == null); +} + +pub const UserInfo = struct { + uid: u32, + gid: u32, +}; + +/// POSIX function which gets a uid from username. +pub fn getUserInfo(name: []const u8) !UserInfo { + return switch (builtin.os) { + .linux, .macosx, .ios, .freebsd, .netbsd => posixGetUserInfo(name), + else => @compileError("Unsupported OS"), + }; +} + +/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else +/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. +pub fn posixGetUserInfo(name: []const u8) !UserInfo { + var in_stream = try io.InStream.open("/etc/passwd", null); + defer in_stream.close(); + + const State = enum { + Start, + WaitForNextLine, + SkipPassword, + ReadUserId, + ReadGroupId, + }; + + var buf: [std.mem.page_size]u8 = undefined; + var name_index: usize = 0; + var state = State.Start; + var uid: u32 = 0; + var gid: u32 = 0; + + while (true) { + const amt_read = try in_stream.read(buf[0..]); + for (buf[0..amt_read]) |byte| { + switch (state) { + .Start => switch (byte) { + ':' => { + state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine; + }, + '\n' => return error.CorruptPasswordFile, + else => { + if (name_index == name.len or name[name_index] != byte) { + state = .WaitForNextLine; + } + name_index += 1; + }, + }, + .WaitForNextLine => switch (byte) { + '\n' => { + name_index = 0; + state = .Start; + }, + else => continue, + }, + .SkipPassword => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => { + state = .ReadUserId; + }, + else => continue, + }, + .ReadUserId => switch (byte) { + ':' => { + state = .ReadGroupId; + }, + '\n' => return error.CorruptPasswordFile, + else => { + const digit = switch (byte) { + '0'...'9' => byte - '0', + else => return error.CorruptPasswordFile, + }; + if (@mulWithOverflow(u32, uid, 10, *uid)) return error.CorruptPasswordFile; + if (@addWithOverflow(u32, uid, digit, *uid)) return error.CorruptPasswordFile; + }, + }, + .ReadGroupId => switch (byte) { + '\n', ':' => { + return UserInfo{ + .uid = uid, + .gid = gid, + }; + }, + else => { + const digit = switch (byte) { + '0'...'9' => byte - '0', + else => return error.CorruptPasswordFile, + }; + if (@mulWithOverflow(u32, gid, 10, *gid)) return error.CorruptPasswordFile; + if (@addWithOverflow(u32, gid, digit, *gid)) return error.CorruptPasswordFile; + }, + }, + } + } + if (amt_read < buf.len) return error.UserNotFound; + } +} diff --git a/std/std.zig b/std/std.zig index 8ec042fdb..a621af1e8 100644 --- a/std/std.zig +++ b/std/std.zig @@ -17,6 +17,8 @@ pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex; pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SpinLock = @import("spinlock.zig").SpinLock; +pub const ChildProcess = @import("child_process.zig").ChildProcess; +pub const Thread = @import("thread.zig").Thread; pub const atomic = @import("atomic.zig"); pub const base64 = @import("base64.zig"); @@ -30,6 +32,7 @@ pub const dwarf = @import("dwarf.zig"); pub const elf = @import("elf.zig"); pub const event = @import("event.zig"); pub const fmt = @import("fmt.zig"); +pub const fs = @import("fs.zig"); pub const hash = @import("hash.zig"); pub const hash_map = @import("hash_map.zig"); pub const heap = @import("heap.zig"); @@ -43,11 +46,13 @@ pub const meta = @import("meta.zig"); pub const net = @import("net.zig"); pub const os = @import("os.zig"); pub const pdb = @import("pdb.zig"); +pub const process = @import("process.zig"); pub const rand = @import("rand.zig"); pub const rb = @import("rb.zig"); pub const sort = @import("sort.zig"); pub const ascii = @import("ascii.zig"); pub const testing = @import("testing.zig"); +pub const time = @import("time.zig"); pub const unicode = @import("unicode.zig"); pub const valgrind = @import("valgrind.zig"); pub const zig = @import("zig.zig"); @@ -65,6 +70,7 @@ test "std" { _ = @import("statically_initialized_mutex.zig"); _ = @import("segmented_list.zig"); _ = @import("spinlock.zig"); + _ = @import("child_process.zig"); _ = @import("ascii.zig"); _ = @import("base64.zig"); @@ -79,6 +85,7 @@ test "std" { _ = @import("elf.zig"); _ = @import("event.zig"); _ = @import("fmt.zig"); + _ = @import("fs.zig"); _ = @import("hash.zig"); _ = @import("heap.zig"); _ = @import("io.zig"); @@ -91,11 +98,14 @@ test "std" { _ = @import("net.zig"); _ = @import("os.zig"); _ = @import("pdb.zig"); + _ = @import("process.zig"); _ = @import("packed_int_array.zig"); _ = @import("priority_queue.zig"); _ = @import("rand.zig"); _ = @import("sort.zig"); _ = @import("testing.zig"); + _ = @import("thread.zig"); + _ = @import("time.zig"); _ = @import("unicode.zig"); _ = @import("valgrind.zig"); _ = @import("zig.zig"); diff --git a/std/thread.zig b/std/thread.zig new file mode 100644 index 000000000..729d4aff6 --- /dev/null +++ b/std/thread.zig @@ -0,0 +1,365 @@ +const builtin = @import("builtin"); +const std = @import("std.zig"); +const windows = std.os.windows; + +pub const Thread = struct { + data: Data, + + pub const use_pthreads = !windows.is_the_target and builtin.link_libc; + + /// Represents a kernel thread handle. + /// May be an integer or a pointer depending on the platform. + /// On Linux and POSIX, this is the same as Id. + pub const Handle = if (use_pthreads) + c.pthread_t + else switch (builtin.os) { + builtin.Os.linux => i32, + builtin.Os.windows => windows.HANDLE, + else => @compileError("Unsupported OS"), + }; + + /// Represents a unique ID per thread. + /// May be an integer or pointer depending on the platform. + /// On Linux and POSIX, this is the same as Handle. + pub const Id = switch (builtin.os) { + builtin.Os.windows => windows.DWORD, + else => Handle, + }; + + pub const Data = if (use_pthreads) + struct { + handle: Thread.Handle, + mmap_addr: usize, + mmap_len: usize, + } + else switch (builtin.os) { + builtin.Os.linux => struct { + handle: Thread.Handle, + mmap_addr: usize, + mmap_len: usize, + }, + builtin.Os.windows => struct { + handle: Thread.Handle, + alloc_start: *c_void, + heap_handle: windows.HANDLE, + }, + else => @compileError("Unsupported OS"), + }; + + /// Returns the ID of the calling thread. + /// Makes a syscall every time the function is called. + /// On Linux and POSIX, this Id is the same as a Handle. + pub fn getCurrentId() Id { + if (use_pthreads) { + return c.pthread_self(); + } else + return switch (builtin.os) { + builtin.Os.linux => linux.gettid(), + builtin.Os.windows => windows.GetCurrentThreadId(), + else => @compileError("Unsupported OS"), + }; + } + + /// Returns the handle of this thread. + /// On Linux and POSIX, this is the same as Id. + /// On Linux, it is possible that the thread spawned with `spawn` + /// finishes executing entirely before the clone syscall completes. In this + /// case, this function will return 0 rather than the no-longer-existing thread's + /// pid. + pub fn handle(self: Thread) Handle { + return self.data.handle; + } + + pub fn wait(self: *const Thread) void { + if (use_pthreads) { + const err = c.pthread_join(self.data.handle, null); + switch (err) { + 0 => {}, + posix.EINVAL => unreachable, + posix.ESRCH => unreachable, + posix.EDEADLK => unreachable, + else => unreachable, + } + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); + } else switch (builtin.os) { + builtin.Os.linux => { + while (true) { + const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); + if (pid_value == 0) break; + const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null); + switch (linux.getErrno(rc)) { + 0 => continue, + posix.EINTR => continue, + posix.EAGAIN => continue, + else => unreachable, + } + } + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); + }, + builtin.Os.windows => { + assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); + assert(windows.CloseHandle(self.data.handle) != 0); + assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0); + }, + else => @compileError("Unsupported OS"), + } + } + + pub const SpawnError = error{ + /// A system-imposed limit on the number of threads was encountered. + /// There are a number of limits that may trigger this error: + /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), + /// which limits the number of processes and threads for a real + /// user ID, was reached; + /// * the kernel's system-wide limit on the number of processes and + /// threads, /proc/sys/kernel/threads-max, was reached (see + /// proc(5)); + /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was + /// reached (see proc(5)); or + /// * the PID limit (pids.max) imposed by the cgroup "process num‐ + /// ber" (PIDs) controller was reached. + ThreadQuotaExceeded, + + /// The kernel cannot allocate sufficient memory to allocate a task structure + /// for the child, or to copy those parts of the caller's context that need to + /// be copied. + SystemResources, + + /// Not enough userland memory to spawn the thread. + OutOfMemory, + + Unexpected, + }; + + /// caller must call wait on the returned thread + /// fn startFn(@typeOf(context)) T + /// where T is u8, noreturn, void, or !void + /// caller must call wait on the returned thread + pub fn spawn(context: var, comptime startFn: var) SpawnError!*Thread { + if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); + // TODO compile-time call graph analysis to determine stack upper bound + // https://github.com/ziglang/zig/issues/157 + const default_stack_size = 8 * 1024 * 1024; + + const Context = @typeOf(context); + comptime assert(@ArgType(@typeOf(startFn), 0) == Context); + + if (builtin.os == builtin.Os.windows) { + const WinThread = struct { + const OuterContext = struct { + thread: Thread, + inner: Context, + }; + extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD { + const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; + switch (@typeId(@typeOf(startFn).ReturnType)) { + builtin.TypeId.Int => { + return startFn(arg); + }, + builtin.TypeId.Void => { + startFn(arg); + return 0; + }, + else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), + } + } + }; + + const heap_handle = windows.GetProcessHeap() orelse return error.OutOfMemory; + const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); + const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; + errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0); + const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; + const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; + outer_context.* = WinThread.OuterContext{ + .thread = Thread{ + .data = Thread.Data{ + .heap_handle = heap_handle, + .alloc_start = bytes_ptr, + .handle = undefined, + }, + }, + .inner = context, + }; + + const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); + outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { + switch (windows.GetLastError()) { + else => |err| windows.unexpectedError(err), + } + }; + return &outer_context.thread; + } + + const MainFuncs = struct { + extern fn linuxThreadMain(ctx_addr: usize) u8 { + const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; + + switch (@typeId(@typeOf(startFn).ReturnType)) { + builtin.TypeId.Int => { + return startFn(arg); + }, + builtin.TypeId.Void => { + startFn(arg); + return 0; + }, + else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), + } + } + extern fn posixThreadMain(ctx: ?*c_void) ?*c_void { + if (@sizeOf(Context) == 0) { + _ = startFn({}); + return null; + } else { + _ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*); + return null; + } + } + }; + + const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; + + var stack_end_offset: usize = undefined; + var thread_start_offset: usize = undefined; + var context_start_offset: usize = undefined; + var tls_start_offset: usize = undefined; + const mmap_len = blk: { + // First in memory will be the stack, which grows downwards. + var l: usize = mem.alignForward(default_stack_size, os.page_size); + stack_end_offset = l; + // Above the stack, so that it can be in the same mmap call, put the Thread object. + l = mem.alignForward(l, @alignOf(Thread)); + thread_start_offset = l; + l += @sizeOf(Thread); + // Next, the Context object. + if (@sizeOf(Context) != 0) { + l = mem.alignForward(l, @alignOf(Context)); + context_start_offset = l; + l += @sizeOf(Context); + } + // Finally, the Thread Local Storage, if any. + if (!Thread.use_pthreads) { + if (linux.tls.tls_image) |tls_img| { + l = mem.alignForward(l, @alignOf(usize)); + tls_start_offset = l; + l += tls_img.alloc_size; + } + } + break :blk l; + }; + const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); + if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory; + errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0); + + const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); + thread_ptr.data.mmap_addr = mmap_addr; + thread_ptr.data.mmap_len = mmap_len; + + var arg: usize = undefined; + if (@sizeOf(Context) != 0) { + arg = mmap_addr + context_start_offset; + const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); + context_ptr.* = context; + } + + if (Thread.use_pthreads) { + // use pthreads + var attr: c.pthread_attr_t = undefined; + if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; + defer assert(c.pthread_attr_destroy(&attr) == 0); + + assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0); + + const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg)); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return error.SystemResources, + posix.EPERM => unreachable, + posix.EINVAL => unreachable, + else => return unexpectedErrorPosix(@intCast(usize, err)), + } + } else if (builtin.os == builtin.Os.linux) { + var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | + posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | + posix.CLONE_DETACHED; + var newtls: usize = undefined; + if (linux.tls.tls_image) |tls_img| { + newtls = linux.tls.copyTLS(mmap_addr + tls_start_offset); + flags |= posix.CLONE_SETTLS; + } + const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); + const err = posix.getErrno(rc); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return error.ThreadQuotaExceeded, + posix.EINVAL => unreachable, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => unreachable, + posix.EPERM => unreachable, + posix.EUSERS => unreachable, + else => return unexpectedErrorPosix(err), + } + } else { + @compileError("Unsupported OS"); + } + } + + pub const CpuCountError = error{ + OutOfMemory, + PermissionDenied, + Unexpected, + }; + + pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize { + switch (builtin.os) { + .macosx, .freebsd, .netbsd => { + var count: c_int = undefined; + var count_len: usize = @sizeOf(c_int); + const name = switch (builtin.os) { + builtin.Os.macosx => c"hw.logicalcpu", + else => c"hw.ncpu", + }; + try posix.sysctlbyname(name, @ptrCast(*c_void, &count), &count_len, null, 0); + return @intCast(usize, count); + }, + .linux => { + const usize_count = 16; + const allocator = std.heap.stackFallback(usize_count * @sizeOf(usize), fallback_allocator).get(); + + var set = try allocator.alloc(usize, usize_count); + defer allocator.free(set); + + while (true) { + const rc = posix.sched_getaffinity(0, set); + const err = posix.getErrno(rc); + switch (err) { + 0 => { + if (rc < set.len * @sizeOf(usize)) { + const result = set[0 .. rc / @sizeOf(usize)]; + var sum: usize = 0; + for (result) |x| { + sum += @popCount(usize, x); + } + return sum; + } else { + set = try allocator.realloc(set, set.len * 2); + continue; + } + }, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EPERM => return CpuCountError.PermissionDenied, + posix.ESRCH => unreachable, + else => return os.unexpectedErrorPosix(err), + } + } + }, + .windows => { + var system_info: windows.SYSTEM_INFO = undefined; + windows.GetSystemInfo(&system_info); + return @intCast(usize, system_info.dwNumberOfProcessors); + }, + else => @compileError("unsupported OS"), + } + } +}; diff --git a/std/os/time.zig b/std/time.zig similarity index 100% rename from std/os/time.zig rename to std/time.zig