do Jay's suggestion with posix/os API naming & layout

This commit is contained in:
Andrew Kelley
2019-05-24 18:27:18 -04:00
parent 2def23063f
commit 17b0166e00
27 changed files with 4904 additions and 4732 deletions
@@ -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,
+1 -1
View File
@@ -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)
+3
View File
@@ -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");
+4 -4
View File
@@ -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);
},
}
}
+1 -1
View File
@@ -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),
+2 -2
View File
@@ -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)) {
+1 -1
View File
@@ -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);
+840
View File
@@ -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");
}
+334
View File
@@ -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);
}
};
@@ -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;
+11 -157
View File
@@ -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"));
}
+3 -3
View File
@@ -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 {
+5 -5
View File
@@ -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) {
+23
View File
@@ -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"),
}
}
+2222 -1680
View File
@@ -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
/// protocol 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");
}
-450
View File
@@ -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);
}
};
-104
View File
@@ -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;
}
}
+15 -2
View File
@@ -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 {
-2307
View File
@@ -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
/// protocol 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;
}
+22
View File
@@ -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));
}
+449 -3
View File
@@ -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));
}
+3 -3
View File
@@ -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));
}
+1 -2
View File
@@ -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;
+583
View File
@@ -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;
}
}
+10
View File
@@ -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");
+365
View File
@@ -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"),
}
}
};
View File