aboutsummaryrefslogtreecommitdiff
path: root/std/os
diff options
context:
space:
mode:
authorAndrew Kelley <superjoe30@gmail.com>2018-08-25 21:57:28 -0400
committerAndrew Kelley <superjoe30@gmail.com>2018-08-25 21:57:28 -0400
commit7109035b78ee05302bbdaadc52013b430a030b69 (patch)
treeae6d7202dc75f2c799f5fbcad72ccf8b02a954a4 /std/os
parent6cf248ec0824c746fc796905144c8077ccab99cf (diff)
parent526338b00fbe1cac19f64832176af3bdf2108a56 (diff)
downloadzig-7109035b78ee05302bbdaadc52013b430a030b69.tar.gz
zig-7109035b78ee05302bbdaadc52013b430a030b69.zip
Merge remote-tracking branch 'origin/master' into llvm7
Diffstat (limited to 'std/os')
-rw-r--r--std/os/child_process.zig14
-rw-r--r--std/os/darwin.zig209
-rw-r--r--std/os/file.zig172
-rw-r--r--std/os/get_app_data_dir.zig3
-rw-r--r--std/os/index.zig811
-rw-r--r--std/os/linux/index.zig72
-rw-r--r--std/os/path.zig237
-rw-r--r--std/os/test.zig33
-rw-r--r--std/os/windows/index.zig17
-rw-r--r--std/os/windows/kernel32.zig102
-rw-r--r--std/os/windows/util.zig113
-rw-r--r--std/os/zen.zig126
12 files changed, 1269 insertions, 640 deletions
diff --git a/std/os/child_process.zig b/std/os/child_process.zig
index 693129eea8..b79a8de16f 100644
--- a/std/os/child_process.zig
+++ b/std/os/child_process.zig
@@ -349,14 +349,7 @@ pub const ChildProcess = struct {
};
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
- const dev_null_fd = if (any_ignore) blk: {
- const dev_null_path = "/dev/null";
- var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined;
- var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
- break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0);
- } else blk: {
- break :blk undefined;
- };
+ const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
defer {
if (any_ignore) os.close(dev_null_fd);
}
@@ -453,10 +446,7 @@ pub const ChildProcess = struct {
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
const nul_handle = if (any_ignore) blk: {
- const nul_file_path = "NUL";
- var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined;
- var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
- break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
+ break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
} else blk: {
break :blk undefined;
};
diff --git a/std/os/darwin.zig b/std/os/darwin.zig
index cf67b01d5a..935d28d6f1 100644
--- a/std/os/darwin.zig
+++ b/std/os/darwin.zig
@@ -482,91 +482,98 @@ pub const NOTE_MACH_CONTINUOUS_TIME = 0x00000080;
/// data is mach absolute time units
pub const NOTE_MACHTIME = 0x00000100;
-pub const AF_UNSPEC: c_int = 0;
-pub const AF_LOCAL: c_int = 1;
-pub const AF_UNIX: c_int = AF_LOCAL;
-pub const AF_INET: c_int = 2;
-pub const AF_SYS_CONTROL: c_int = 2;
-pub const AF_IMPLINK: c_int = 3;
-pub const AF_PUP: c_int = 4;
-pub const AF_CHAOS: c_int = 5;
-pub const AF_NS: c_int = 6;
-pub const AF_ISO: c_int = 7;
-pub const AF_OSI: c_int = AF_ISO;
-pub const AF_ECMA: c_int = 8;
-pub const AF_DATAKIT: c_int = 9;
-pub const AF_CCITT: c_int = 10;
-pub const AF_SNA: c_int = 11;
-pub const AF_DECnet: c_int = 12;
-pub const AF_DLI: c_int = 13;
-pub const AF_LAT: c_int = 14;
-pub const AF_HYLINK: c_int = 15;
-pub const AF_APPLETALK: c_int = 16;
-pub const AF_ROUTE: c_int = 17;
-pub const AF_LINK: c_int = 18;
-pub const AF_XTP: c_int = 19;
-pub const AF_COIP: c_int = 20;
-pub const AF_CNT: c_int = 21;
-pub const AF_RTIP: c_int = 22;
-pub const AF_IPX: c_int = 23;
-pub const AF_SIP: c_int = 24;
-pub const AF_PIP: c_int = 25;
-pub const AF_ISDN: c_int = 28;
-pub const AF_E164: c_int = AF_ISDN;
-pub const AF_KEY: c_int = 29;
-pub const AF_INET6: c_int = 30;
-pub const AF_NATM: c_int = 31;
-pub const AF_SYSTEM: c_int = 32;
-pub const AF_NETBIOS: c_int = 33;
-pub const AF_PPP: c_int = 34;
-pub const AF_MAX: c_int = 40;
-
-pub const PF_UNSPEC: c_int = AF_UNSPEC;
-pub const PF_LOCAL: c_int = AF_LOCAL;
-pub const PF_UNIX: c_int = PF_LOCAL;
-pub const PF_INET: c_int = AF_INET;
-pub const PF_IMPLINK: c_int = AF_IMPLINK;
-pub const PF_PUP: c_int = AF_PUP;
-pub const PF_CHAOS: c_int = AF_CHAOS;
-pub const PF_NS: c_int = AF_NS;
-pub const PF_ISO: c_int = AF_ISO;
-pub const PF_OSI: c_int = AF_ISO;
-pub const PF_ECMA: c_int = AF_ECMA;
-pub const PF_DATAKIT: c_int = AF_DATAKIT;
-pub const PF_CCITT: c_int = AF_CCITT;
-pub const PF_SNA: c_int = AF_SNA;
-pub const PF_DECnet: c_int = AF_DECnet;
-pub const PF_DLI: c_int = AF_DLI;
-pub const PF_LAT: c_int = AF_LAT;
-pub const PF_HYLINK: c_int = AF_HYLINK;
-pub const PF_APPLETALK: c_int = AF_APPLETALK;
-pub const PF_ROUTE: c_int = AF_ROUTE;
-pub const PF_LINK: c_int = AF_LINK;
-pub const PF_XTP: c_int = AF_XTP;
-pub const PF_COIP: c_int = AF_COIP;
-pub const PF_CNT: c_int = AF_CNT;
-pub const PF_SIP: c_int = AF_SIP;
-pub const PF_IPX: c_int = AF_IPX;
-pub const PF_RTIP: c_int = AF_RTIP;
-pub const PF_PIP: c_int = AF_PIP;
-pub const PF_ISDN: c_int = AF_ISDN;
-pub const PF_KEY: c_int = AF_KEY;
-pub const PF_INET6: c_int = AF_INET6;
-pub const PF_NATM: c_int = AF_NATM;
-pub const PF_SYSTEM: c_int = AF_SYSTEM;
-pub const PF_NETBIOS: c_int = AF_NETBIOS;
-pub const PF_PPP: c_int = AF_PPP;
-pub const PF_MAX: c_int = AF_MAX;
-
-pub const SYSPROTO_EVENT: c_int = 1;
-pub const SYSPROTO_CONTROL: c_int = 2;
-
-pub const SOCK_STREAM: c_int = 1;
-pub const SOCK_DGRAM: c_int = 2;
-pub const SOCK_RAW: c_int = 3;
-pub const SOCK_RDM: c_int = 4;
-pub const SOCK_SEQPACKET: c_int = 5;
-pub const SOCK_MAXADDRLEN: c_int = 255;
+pub const AF_UNSPEC = 0;
+pub const AF_LOCAL = 1;
+pub const AF_UNIX = AF_LOCAL;
+pub const AF_INET = 2;
+pub const AF_SYS_CONTROL = 2;
+pub const AF_IMPLINK = 3;
+pub const AF_PUP = 4;
+pub const AF_CHAOS = 5;
+pub const AF_NS = 6;
+pub const AF_ISO = 7;
+pub const AF_OSI = AF_ISO;
+pub const AF_ECMA = 8;
+pub const AF_DATAKIT = 9;
+pub const AF_CCITT = 10;
+pub const AF_SNA = 11;
+pub const AF_DECnet = 12;
+pub const AF_DLI = 13;
+pub const AF_LAT = 14;
+pub const AF_HYLINK = 15;
+pub const AF_APPLETALK = 16;
+pub const AF_ROUTE = 17;
+pub const AF_LINK = 18;
+pub const AF_XTP = 19;
+pub const AF_COIP = 20;
+pub const AF_CNT = 21;
+pub const AF_RTIP = 22;
+pub const AF_IPX = 23;
+pub const AF_SIP = 24;
+pub const AF_PIP = 25;
+pub const AF_ISDN = 28;
+pub const AF_E164 = AF_ISDN;
+pub const AF_KEY = 29;
+pub const AF_INET6 = 30;
+pub const AF_NATM = 31;
+pub const AF_SYSTEM = 32;
+pub const AF_NETBIOS = 33;
+pub const AF_PPP = 34;
+pub const AF_MAX = 40;
+
+pub const PF_UNSPEC = AF_UNSPEC;
+pub const PF_LOCAL = AF_LOCAL;
+pub const PF_UNIX = PF_LOCAL;
+pub const PF_INET = AF_INET;
+pub const PF_IMPLINK = AF_IMPLINK;
+pub const PF_PUP = AF_PUP;
+pub const PF_CHAOS = AF_CHAOS;
+pub const PF_NS = AF_NS;
+pub const PF_ISO = AF_ISO;
+pub const PF_OSI = AF_ISO;
+pub const PF_ECMA = AF_ECMA;
+pub const PF_DATAKIT = AF_DATAKIT;
+pub const PF_CCITT = AF_CCITT;
+pub const PF_SNA = AF_SNA;
+pub const PF_DECnet = AF_DECnet;
+pub const PF_DLI = AF_DLI;
+pub const PF_LAT = AF_LAT;
+pub const PF_HYLINK = AF_HYLINK;
+pub const PF_APPLETALK = AF_APPLETALK;
+pub const PF_ROUTE = AF_ROUTE;
+pub const PF_LINK = AF_LINK;
+pub const PF_XTP = AF_XTP;
+pub const PF_COIP = AF_COIP;
+pub const PF_CNT = AF_CNT;
+pub const PF_SIP = AF_SIP;
+pub const PF_IPX = AF_IPX;
+pub const PF_RTIP = AF_RTIP;
+pub const PF_PIP = AF_PIP;
+pub const PF_ISDN = AF_ISDN;
+pub const PF_KEY = AF_KEY;
+pub const PF_INET6 = AF_INET6;
+pub const PF_NATM = AF_NATM;
+pub const PF_SYSTEM = AF_SYSTEM;
+pub const PF_NETBIOS = AF_NETBIOS;
+pub const PF_PPP = AF_PPP;
+pub const PF_MAX = AF_MAX;
+
+pub const SYSPROTO_EVENT = 1;
+pub const SYSPROTO_CONTROL = 2;
+
+pub const SOCK_STREAM = 1;
+pub const SOCK_DGRAM = 2;
+pub const SOCK_RAW = 3;
+pub const SOCK_RDM = 4;
+pub const SOCK_SEQPACKET = 5;
+pub const SOCK_MAXADDRLEN = 255;
+
+pub const IPPROTO_ICMP = 1;
+pub const IPPROTO_ICMPV6 = 58;
+pub const IPPROTO_TCP = 6;
+pub const IPPROTO_UDP = 17;
+pub const IPPROTO_IP = 0;
+pub const IPPROTO_IPV6 = 41;
fn wstatus(x: i32) i32 {
return x & 0o177;
@@ -605,6 +612,11 @@ pub fn abort() noreturn {
c.abort();
}
+// bind(int socket, const struct sockaddr *address, socklen_t address_len)
+pub fn bind(fd: i32, addr: *const sockaddr, len: socklen_t) usize {
+ return errnoWrap(c.bind(@bitCast(c_int, fd), addr, len));
+}
+
pub fn exit(code: i32) noreturn {
c.exit(code);
}
@@ -634,6 +646,10 @@ pub fn read(fd: i32, buf: [*]u8, nbyte: usize) usize {
return errnoWrap(c.read(fd, @ptrCast(*c_void, buf), nbyte));
}
+pub fn pread(fd: i32, buf: [*]u8, nbyte: usize, offset: u64) usize {
+ return errnoWrap(c.pread(fd, @ptrCast(*c_void, buf), nbyte, offset));
+}
+
pub fn stat(noalias path: [*]const u8, noalias buf: *stat) usize {
return errnoWrap(c.stat(path, buf));
}
@@ -642,6 +658,10 @@ pub fn write(fd: i32, buf: [*]const u8, nbyte: usize) usize {
return errnoWrap(c.write(fd, @ptrCast(*const c_void, buf), nbyte));
}
+pub fn pwrite(fd: i32, buf: [*]const u8, nbyte: usize, offset: u64) usize {
+ return errnoWrap(c.pwrite(fd, @ptrCast(*const c_void, buf), nbyte, offset));
+}
+
pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize {
const ptr_result = c.mmap(
@ptrCast(*c_void, address),
@@ -805,6 +825,20 @@ pub fn sigaction(sig: u5, noalias act: *const Sigaction, noalias oact: ?*Sigacti
return result;
}
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) usize {
+ return errnoWrap(c.socket(@bitCast(c_int, domain), @bitCast(c_int, socket_type), @bitCast(c_int, protocol)));
+}
+
+pub const iovec = extern struct {
+ iov_base: [*]u8,
+ iov_len: usize,
+};
+
+pub const iovec_const = extern struct {
+ iov_base: [*]const u8,
+ iov_len: usize,
+};
+
pub const sigset_t = c.sigset_t;
pub const empty_sigset = sigset_t(0);
@@ -812,8 +846,13 @@ pub const timespec = c.timespec;
pub const Stat = c.Stat;
pub const dirent = c.dirent;
+pub const in_port_t = c.in_port_t;
pub const sa_family_t = c.sa_family_t;
+pub const socklen_t = c.socklen_t;
+
pub const sockaddr = c.sockaddr;
+pub const sockaddr_in = c.sockaddr_in;
+pub const sockaddr_in6 = c.sockaddr_in6;
/// Renamed from `kevent` to `Kevent` to avoid conflict with the syscall.
pub const Kevent = c.Kevent;
diff --git a/std/os/file.zig b/std/os/file.zig
index 6998ba00d1..1f5ce7cf9d 100644
--- a/std/os/file.zig
+++ b/std/os/file.zig
@@ -7,6 +7,7 @@ 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 is_posix = builtin.os != builtin.Os.windows;
const is_windows = builtin.os == builtin.Os.windows;
@@ -15,18 +16,39 @@ pub const File = struct {
/// The OS-specific file descriptor or file handle.
handle: os.FileHandle,
+ 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;
- /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
- /// Call close to clean up.
- pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
+ /// `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.posixOpen(allocator, path, flags, 0);
+ const fd = try os.posixOpenC(path, flags, 0);
return openHandle(fd);
- } else if (is_windows) {
+ }
+ 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 handle = try os.windowsOpen(
- allocator,
path,
windows.GENERIC_READ,
windows.FILE_SHARE_READ,
@@ -34,28 +56,25 @@ pub const File = struct {
windows.FILE_ATTRIBUTE_NORMAL,
);
return openHandle(handle);
- } else {
- @compileError("TODO implement openRead for this OS");
}
+ @compileError("Unsupported OS");
}
- /// Calls `openWriteMode` with os.default_file_mode for the mode.
- pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
- return openWriteMode(allocator, path, os.default_file_mode);
+ /// 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.
- /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up.
- pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ 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(allocator, path, flags, file_mode);
+ const fd = try os.posixOpen(path, flags, file_mode);
return openHandle(fd);
} else if (is_windows) {
const handle = try os.windowsOpen(
- allocator,
path,
windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -70,16 +89,14 @@ pub const File = struct {
/// If the path does not exist it will be created.
/// If a file already exists in the destination this returns OpenError.PathAlreadyExists
- /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up.
- pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ pub fn openWriteNoClobber(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.posixOpen(allocator, path, flags, file_mode);
+ const fd = try os.posixOpen(path, flags, file_mode);
return openHandle(fd);
} else if (is_windows) {
const handle = try os.windowsOpen(
- allocator,
path,
windows.GENERIC_WRITE,
windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -98,23 +115,43 @@ pub const File = struct {
pub const AccessError = error{
PermissionDenied,
- NotFound,
+ FileNotFound,
NameTooLong,
- BadMode,
- BadPathName,
- Io,
+ InputOutput,
SystemResources,
- OutOfMemory,
+ BadPathName,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
Unexpected,
};
- pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {
- const path_with_null = try std.cstr.addNullByte(allocator, path);
- defer allocator.free(path_with_null);
+ /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
+ /// Otherwise use `access` or `accessC`.
+ pub fn accessW(path: [*]const u16) AccessError!void {
+ if (os.windows.GetFileAttributesW(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
+ return;
+ }
+
+ const err = windows.GetLastError();
+ switch (err) {
+ windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+ windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
+ windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
+ else => return os.unexpectedErrorWindows(err),
+ }
+ }
+ /// Call if you have a UTF-8 encoded, null-terminated string.
+ /// Otherwise use `access` or `accessW`.
+ pub fn accessC(path: [*]const u8) AccessError!void {
+ if (is_windows) {
+ const path_w = try windows_util.cStrToPrefixedFileW(path);
+ return accessW(&path_w);
+ }
if (is_posix) {
- const result = posix.access(path_with_null.ptr, posix.F_OK);
+ const result = posix.access(path, posix.F_OK);
const err = posix.getErrno(result);
switch (err) {
0 => return,
@@ -122,32 +159,33 @@ pub const File = struct {
posix.EROFS => return error.PermissionDenied,
posix.ELOOP => return error.PermissionDenied,
posix.ETXTBSY => return error.PermissionDenied,
- posix.ENOTDIR => return error.NotFound,
- posix.ENOENT => return error.NotFound,
+ posix.ENOTDIR => return error.FileNotFound,
+ posix.ENOENT => return error.FileNotFound,
posix.ENAMETOOLONG => return error.NameTooLong,
posix.EINVAL => unreachable,
- posix.EFAULT => return error.BadPathName,
- posix.EIO => return error.Io,
+ posix.EFAULT => unreachable,
+ posix.EIO => return error.InputOutput,
posix.ENOMEM => return error.SystemResources,
else => return os.unexpectedErrorPosix(err),
}
- } else if (is_windows) {
- if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
- return;
- }
+ }
+ @compileError("Unsupported OS");
+ }
- const err = windows.GetLastError();
- switch (err) {
- windows.ERROR.FILE_NOT_FOUND,
- windows.ERROR.PATH_NOT_FOUND,
- => return error.NotFound,
- windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
- else => return os.unexpectedErrorWindows(err),
- }
- } else {
- @compileError("TODO implement access for this OS");
+ pub fn access(path: []const u8) AccessError!void {
+ if (is_windows) {
+ const path_w = try windows_util.sliceToPrefixedFileW(path);
+ return accessW(&path_w);
+ }
+ if (is_posix) {
+ var path_with_null: [posix.PATH_MAX]u8 = undefined;
+ if (path.len >= posix.PATH_MAX) return error.NameTooLong;
+ mem.copy(u8, path_with_null[0..], path);
+ path_with_null[path.len] = 0;
+ return accessC(&path_with_null);
}
+ @compileError("Unsupported OS");
}
/// Upon success, the stream is in an uninitialized state. To continue using it,
@@ -169,7 +207,9 @@ pub const File = struct {
const err = posix.getErrno(result);
if (err > 0) {
return switch (err) {
- posix.EBADF => error.BadFd,
+ // 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,
@@ -182,7 +222,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
const err = windows.GetLastError();
return switch (err) {
- windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err),
};
}
@@ -199,7 +239,9 @@ pub const File = struct {
const err = posix.getErrno(result);
if (err > 0) {
return switch (err) {
- posix.EBADF => error.BadFd,
+ // 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,
@@ -213,7 +255,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
const err = windows.GetLastError();
return switch (err) {
- windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err),
};
}
@@ -229,7 +271,9 @@ pub const File = struct {
const err = posix.getErrno(result);
if (err > 0) {
return switch (err) {
- posix.EBADF => error.BadFd,
+ // 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,
@@ -244,7 +288,7 @@ pub const File = struct {
if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
const err = windows.GetLastError();
return switch (err) {
- windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ windows.ERROR.INVALID_PARAMETER => unreachable,
else => os.unexpectedErrorWindows(err),
};
}
@@ -277,18 +321,19 @@ pub const File = struct {
}
pub const ModeError = error{
- BadFd,
SystemResources,
Unexpected,
};
- pub fn mode(self: *File) ModeError!os.FileMode {
+ 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) {
- posix.EBADF => error.BadFd,
+ // 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),
};
@@ -296,7 +341,7 @@ pub const File = struct {
// TODO: we should be able to cast u16 to ModeError!u32, making this
// explicit cast not necessary
- return os.FileMode(stat.mode);
+ return Mode(stat.mode);
} else if (is_windows) {
return {};
} else {
@@ -305,9 +350,11 @@ pub const File = struct {
}
pub const ReadError = error{
- BadFd,
- Io,
+ FileClosed,
+ InputOutput,
IsDir,
+ WouldBlock,
+ SystemResources,
Unexpected,
};
@@ -323,9 +370,12 @@ pub const File = struct {
posix.EINTR => continue,
posix.EINVAL => unreachable,
posix.EFAULT => unreachable,
- posix.EBADF => return error.BadFd,
- posix.EIO => return error.Io,
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
else => return os.unexpectedErrorPosix(read_err),
}
}
@@ -338,7 +388,7 @@ pub const File = struct {
while (index < buffer.len) {
const want_read_count = @intCast(windows.DWORD, math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index));
var amt_read: windows.DWORD = undefined;
- if (windows.ReadFile(self.handle, @ptrCast(*c_void, buffer.ptr + index), want_read_count, &amt_read, null) == 0) {
+ if (windows.ReadFile(self.handle, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.OPERATION_ABORTED => continue,
diff --git a/std/os/get_app_data_dir.zig b/std/os/get_app_data_dir.zig
index e8ae5dd490..da9c6c3cb4 100644
--- a/std/os/get_app_data_dir.zig
+++ b/std/os/get_app_data_dir.zig
@@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{
};
/// Caller owns returned memory.
+/// 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 => {
@@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
)) {
os.windows.S_OK => {
defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
- const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
+ const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
diff --git a/std/os/index.zig b/std/os/index.zig
index 425a900a71..29d887e214 100644
--- a/std/os/index.zig
+++ b/std/os/index.zig
@@ -38,17 +38,16 @@ pub const path = @import("path.zig");
pub const File = @import("file.zig").File;
pub const time = @import("time.zig");
-pub const FileMode = switch (builtin.os) {
- Os.windows => void,
- else => u32,
-};
-
-pub const default_file_mode = switch (builtin.os) {
- Os.windows => {},
- else => 0o666,
-};
-
pub const page_size = 4 * 1024;
+pub const MAX_PATH_BYTES = switch (builtin.os) {
+ Os.linux, Os.macosx, Os.ios => 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.
+ Os.windows => windows_util.PATH_MAX_WIDE * 3 + 1,
+ else => @compileError("Unsupported OS"),
+};
pub const UserInfo = @import("get_user_id.zig").UserInfo;
pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
@@ -160,7 +159,7 @@ test "os.getRandomBytes" {
try getRandomBytes(buf_b[0..]);
// Check if random (not 100% conclusive)
- assert( !mem.eql(u8, buf_a, buf_b) );
+ assert(!mem.eql(u8, buf_a, buf_b));
}
/// Raises a signal in the current kernel thread, ending its execution.
@@ -256,6 +255,67 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
}
}
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+pub fn posix_preadv(fd: i32, iov: [*]const posix.iovec, count: usize, offset: u64) !usize {
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ // 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;
+ },
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.ESPIPE => unreachable, // fd is not seekable
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
+ posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+ }
+ },
+ builtin.Os.linux, builtin.Os.freebsd => while (true) {
+ const rc = posix.preadv(fd, iov, count, offset);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return rc,
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
+ posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
pub const PosixWriteError = error{
WouldBlock,
FileClosed,
@@ -266,6 +326,8 @@ pub const PosixWriteError = error{
NoSpaceLeft,
AccessDenied,
BrokenPipe,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -300,8 +362,72 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void {
}
}
+pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, offset: u64) PosixWriteError!void {
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ // 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;
+ },
+ posix.EINTR => continue,
+ posix.ESPIPE => unreachable, // fd is not seekable
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return PosixWriteError.WouldBlock,
+ posix.EBADF => return PosixWriteError.FileClosed,
+ posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired,
+ posix.EDQUOT => return PosixWriteError.DiskQuota,
+ posix.EFBIG => return PosixWriteError.FileTooBig,
+ posix.EIO => return PosixWriteError.InputOutput,
+ posix.ENOSPC => return PosixWriteError.NoSpaceLeft,
+ posix.EPERM => return PosixWriteError.AccessDenied,
+ posix.EPIPE => return PosixWriteError.BrokenPipe,
+ else => return unexpectedErrorPosix(err),
+ }
+ }
+ },
+ builtin.Os.linux => while (true) {
+ const rc = posix.pwritev(fd, iov, count, offset);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return,
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return PosixWriteError.WouldBlock,
+ posix.EBADF => return PosixWriteError.FileClosed,
+ posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired,
+ posix.EDQUOT => return PosixWriteError.DiskQuota,
+ posix.EFBIG => return PosixWriteError.FileTooBig,
+ posix.EIO => return PosixWriteError.InputOutput,
+ posix.ENOSPC => return PosixWriteError.NoSpaceLeft,
+ posix.EPERM => return PosixWriteError.AccessDenied,
+ posix.EPIPE => return PosixWriteError.BrokenPipe,
+ else => return unexpectedErrorPosix(err),
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
pub const PosixOpenError = error{
- OutOfMemory,
AccessDenied,
FileTooBig,
IsDir,
@@ -310,22 +436,22 @@ pub const PosixOpenError = error{
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
- PathNotFound,
+ FileNotFound,
SystemResources,
NoSpaceLeft,
NotDir,
PathAlreadyExists,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
/// ::file_path needs to be copied in memory to add a null terminating byte.
/// Calls POSIX open, keeps trying if it gets interrupted, and translates
/// the return value into zig errors.
-pub fn posixOpen(allocator: *Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
- const path_with_null = try cstr.addNullByte(allocator, file_path);
- defer allocator.free(path_with_null);
-
- return posixOpenC(path_with_null.ptr, flags, perm);
+pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
+ const file_path_c = try toPosixPath(file_path);
+ return posixOpenC(&file_path_c, flags, perm);
}
// TODO https://github.com/ziglang/zig/issues/265
@@ -347,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
posix.ENODEV => return PosixOpenError.NoDevice,
- posix.ENOENT => return PosixOpenError.PathNotFound,
+ posix.ENOENT => return PosixOpenError.FileNotFound,
posix.ENOMEM => return PosixOpenError.SystemResources,
posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
posix.ENOTDIR => return PosixOpenError.NotDir,
@@ -360,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
}
}
+/// Used to convert a slice to a null terminated slice on the stack.
+/// TODO well defined copy elision
+pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 {
+ var path_with_null: [posix.PATH_MAX]u8 = undefined;
+ if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
+ mem.copy(u8, path_with_null[0..], file_path);
+ path_with_null[file_path.len] = 0;
+ return path_with_null;
+}
+
pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
while (true) {
const err = posix.getErrno(posix.dup2(old_fd, new_fd));
@@ -475,6 +611,8 @@ pub const PosixExecveError = error{
FileNotFound,
NotDir,
FileBusy,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -497,6 +635,35 @@ fn posixExecveErrnoToErr(err: usize) PosixExecveError {
pub var linux_aux_raw = []usize{0} ** 38;
pub var posix_environ_raw: [][*]u8 = undefined;
+/// See std.elf for the constants.
+pub fn linuxGetAuxVal(index: usize) usize {
+ if (builtin.link_libc) {
+ return usize(std.c.getauxval(index));
+ } else {
+ return linux_aux_raw[index];
+ }
+}
+
+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);
+ const ElfHeader = switch (@sizeOf(usize)) {
+ 4 => std.elf.Elf32_Ehdr,
+ 8 => std.elf.Elf64_Ehdr,
+ else => @compileError("Unsupported architecture"),
+ };
+ return phdr - @sizeOf(ElfHeader);
+ },
+ builtin.Os.macosx => return @ptrToInt(&std.c._mh_execute_header),
+ 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 {
@@ -603,43 +770,39 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
}
/// Caller must free the returned memory.
-pub fn getCwd(allocator: *Allocator) ![]u8 {
- switch (builtin.os) {
- Os.windows => {
- var buf = try allocator.alloc(u8, 256);
- errdefer allocator.free(buf);
-
- while (true) {
- const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr);
+pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
+ var buf: [MAX_PATH_BYTES]u8 = undefined;
+ return mem.dupe(allocator, u8, try getCwd(&buf));
+}
- if (result == 0) {
- const err = windows.GetLastError();
- return switch (err) {
- else => unexpectedErrorWindows(err),
- };
- }
+pub const GetCwdError = error{Unexpected};
- if (result > buf.len) {
- buf = try allocator.realloc(u8, buf, result);
- continue;
+/// The result is a slice of out_buffer.
+pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
+ switch (builtin.os) {
+ Os.windows => {
+ 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 casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast
+ const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr);
+ if (result == 0) {
+ const err = windows.GetLastError();
+ switch (err) {
+ else => return unexpectedErrorWindows(err),
}
-
- return allocator.shrink(u8, buf, result);
}
+ assert(result <= utf16le_buf.len);
+ const utf16le_slice = utf16le_buf[0..result];
+ // 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];
},
else => {
- var buf = try allocator.alloc(u8, 1024);
- errdefer allocator.free(buf);
- while (true) {
- const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
- if (err == posix.ERANGE) {
- buf = try allocator.realloc(u8, buf, buf.len * 2);
- continue;
- } else if (err > 0) {
- return unexpectedErrorPosix(err);
- }
-
- return allocator.shrink(u8, buf, cstr.len(buf.ptr));
+ const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
+ switch (err) {
+ 0 => return cstr.toSlice(out_buffer),
+ posix.ERANGE => unreachable,
+ else => return unexpectedErrorPosix(err),
}
},
}
@@ -647,7 +810,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 {
test "os.getCwd" {
// at least call it so it gets compiled
- _ = getCwd(debug.global_allocator);
+ _ = getCwdAlloc(debug.global_allocator);
+ var buf: [MAX_PATH_BYTES]u8 = undefined;
+ _ = getCwd(&buf);
}
pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
@@ -662,6 +827,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con
pub const WindowsSymLinkError = error{
OutOfMemory,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -692,6 +859,8 @@ pub const PosixSymLinkError = error{
NoSpaceLeft,
ReadOnlyFileSystem,
NotDir,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -750,7 +919,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
if (symLink(allocator, existing_path, tmp_path)) {
- return rename(allocator, tmp_path, new_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
@@ -769,70 +938,75 @@ pub const DeleteFileError = error{
NotDir,
SystemResources,
ReadOnlyFileSystem,
- OutOfMemory,
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
-pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {
+pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
if (builtin.os == Os.windows) {
- return deleteFileWindows(allocator, file_path);
+ return deleteFileWindows(file_path);
} else {
- return deleteFilePosix(allocator, file_path);
+ return deleteFilePosix(file_path);
}
}
-pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void {
- const buf = try allocator.alloc(u8, file_path.len + 1);
- defer allocator.free(buf);
+pub fn deleteFileWindows(file_path: []const u8) !void {
+ const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
- mem.copy(u8, buf, file_path);
- buf[file_path.len] = 0;
-
- if (windows.DeleteFileA(buf.ptr) == 0) {
+ if (windows.DeleteFileW(&file_path_w) == 0) {
const err = windows.GetLastError();
- return switch (err) {
- windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
- windows.ERROR.ACCESS_DENIED => error.AccessDenied,
- windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
- else => unexpectedErrorWindows(err),
- };
+ 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,
+ windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
+ else => return unexpectedErrorWindows(err),
+ }
}
}
-pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {
- const buf = try allocator.alloc(u8, file_path.len + 1);
- defer allocator.free(buf);
-
- mem.copy(u8, buf, file_path);
- buf[file_path.len] = 0;
-
- const err = posix.getErrno(posix.unlink(buf.ptr));
- if (err > 0) {
- return switch (err) {
- posix.EACCES, posix.EPERM => error.AccessDenied,
- posix.EBUSY => error.FileBusy,
- posix.EFAULT, posix.EINVAL => unreachable,
- posix.EIO => error.FileSystem,
- posix.EISDIR => error.IsDir,
- posix.ELOOP => error.SymLinkLoop,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ENOENT => error.FileNotFound,
- posix.ENOTDIR => error.NotDir,
- posix.ENOMEM => error.SystemResources,
- posix.EROFS => error.ReadOnlyFileSystem,
- else => unexpectedErrorPosix(err),
- };
+pub fn deleteFilePosixC(file_path: [*]const u8) !void {
+ const err = posix.getErrno(posix.unlink(file_path));
+ switch (err) {
+ 0 => return,
+ posix.EACCES => return error.AccessDenied,
+ posix.EPERM => return error.AccessDenied,
+ posix.EBUSY => return error.FileBusy,
+ posix.EFAULT => unreachable,
+ posix.EINVAL => unreachable,
+ posix.EIO => return error.FileSystem,
+ posix.EISDIR => return error.IsDir,
+ posix.ELOOP => return error.SymLinkLoop,
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOTDIR => return error.NotDir,
+ posix.ENOMEM => return error.SystemResources,
+ posix.EROFS => return error.ReadOnlyFileSystem,
+ else => return unexpectedErrorPosix(err),
}
}
+pub fn deleteFilePosix(file_path: []const u8) !void {
+ const file_path_c = try toPosixPath(file_path);
+ return deleteFilePosixC(&file_path_c);
+}
+
/// 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.
+/// TODO investigate if this can work with no allocator
pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
- var in_file = try os.File.openRead(allocator, source_path);
+ var in_file = try os.File.openRead(source_path);
defer in_file.close();
const mode = try in_file.mode();
@@ -853,8 +1027,9 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
/// 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(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
- var in_file = try os.File.openRead(allocator, source_path);
+/// TODO investigate if this can work with no allocator
+pub fn copyFileMode(allocator: *Allocator, 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(allocator, dest_path, mode);
@@ -871,6 +1046,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
}
pub const AtomicFile = struct {
+ /// TODO investigate if we can make this work with no allocator
allocator: *Allocator,
file: os.File,
tmp_path: []u8,
@@ -879,7 +1055,7 @@ pub const AtomicFile = struct {
/// dest_path must remain valid for the lifetime of AtomicFile
/// call finish to atomically replace dest_path with contents
- pub fn init(allocator: *Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
+ pub fn init(allocator: *Allocator, dest_path: []const u8, mode: File.Mode) !AtomicFile {
const dirname = os.path.dirname(dest_path);
var rand_buf: [12]u8 = undefined;
@@ -898,7 +1074,7 @@ pub const AtomicFile = struct {
try getRandomBytes(rand_buf[0..]);
b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
- const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {
+ const file = os.File.openWriteNoClobber(tmp_path, 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
@@ -919,7 +1095,7 @@ pub const AtomicFile = struct {
pub fn deinit(self: *AtomicFile) void {
if (!self.finished) {
self.file.close();
- deleteFile(self.allocator, self.tmp_path) catch {};
+ deleteFile(self.tmp_path) catch {};
self.allocator.free(self.tmp_path);
self.finished = true;
}
@@ -928,70 +1104,72 @@ pub const AtomicFile = struct {
pub fn finish(self: *AtomicFile) !void {
assert(!self.finished);
self.file.close();
- try rename(self.allocator, self.tmp_path, self.dest_path);
+ try rename(self.tmp_path, self.dest_path);
self.allocator.free(self.tmp_path);
self.finished = true;
}
};
-pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void {
- const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
- defer allocator.free(full_buf);
-
- const old_buf = full_buf;
- mem.copy(u8, old_buf, old_path);
- old_buf[old_path.len] = 0;
-
- const new_buf = full_buf[old_path.len + 1 ..];
- mem.copy(u8, new_buf, new_path);
- new_buf[new_path.len] = 0;
+pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
+ if (is_windows) {
+ @compileError("TODO implement for windows");
+ } else {
+ const err = posix.getErrno(posix.rename(old_path, new_path));
+ switch (err) {
+ 0 => return,
+ posix.EACCES => return error.AccessDenied,
+ posix.EPERM => return error.AccessDenied,
+ posix.EBUSY => return error.FileBusy,
+ posix.EDQUOT => return error.DiskQuota,
+ posix.EFAULT => unreachable,
+ posix.EINVAL => unreachable,
+ posix.EISDIR => return error.IsDir,
+ posix.ELOOP => return error.SymLinkLoop,
+ posix.EMLINK => return error.LinkQuotaExceeded,
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOTDIR => return error.NotDir,
+ posix.ENOMEM => return error.SystemResources,
+ posix.ENOSPC => return error.NoSpaceLeft,
+ posix.EEXIST => return error.PathAlreadyExists,
+ posix.ENOTEMPTY => return error.PathAlreadyExists,
+ posix.EROFS => return error.ReadOnlyFileSystem,
+ posix.EXDEV => return error.RenameAcrossMountPoints,
+ else => return unexpectedErrorPosix(err),
+ }
+ }
+}
+pub fn rename(old_path: []const u8, new_path: []const u8) !void {
if (is_windows) {
const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
- if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) {
+ const old_path_w = try windows_util.sliceToPrefixedFileW(old_path);
+ const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
+ if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) {
const err = windows.GetLastError();
- return switch (err) {
- else => unexpectedErrorWindows(err),
- };
+ switch (err) {
+ else => return unexpectedErrorWindows(err),
+ }
}
} else {
- const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
- if (err > 0) {
- return switch (err) {
- posix.EACCES, posix.EPERM => error.AccessDenied,
- posix.EBUSY => error.FileBusy,
- posix.EDQUOT => error.DiskQuota,
- posix.EFAULT, posix.EINVAL => unreachable,
- posix.EISDIR => error.IsDir,
- posix.ELOOP => error.SymLinkLoop,
- posix.EMLINK => error.LinkQuotaExceeded,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ENOENT => error.FileNotFound,
- posix.ENOTDIR => error.NotDir,
- posix.ENOMEM => error.SystemResources,
- posix.ENOSPC => error.NoSpaceLeft,
- posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
- posix.EROFS => error.ReadOnlyFileSystem,
- posix.EXDEV => error.RenameAcrossMountPoints,
- else => unexpectedErrorPosix(err),
- };
- }
+ const old_path_c = try toPosixPath(old_path);
+ const new_path_c = try toPosixPath(new_path);
+ return renameC(&old_path_c, &new_path_c);
}
}
-pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void {
+pub fn makeDir(dir_path: []const u8) !void {
if (is_windows) {
- return makeDirWindows(allocator, dir_path);
+ return makeDirWindows(dir_path);
} else {
- return makeDirPosix(allocator, dir_path);
+ return makeDirPosix(dir_path);
}
}
-pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
- const path_buf = try cstr.addNullByte(allocator, dir_path);
- defer allocator.free(path_buf);
+pub fn makeDirWindows(dir_path: []const u8) !void {
+ const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
- if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) {
+ if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
@@ -1001,54 +1179,57 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
}
}
-pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void {
- const path_buf = try cstr.addNullByte(allocator, dir_path);
- defer allocator.free(path_buf);
-
- const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
- if (err > 0) {
- return switch (err) {
- posix.EACCES, posix.EPERM => error.AccessDenied,
- posix.EDQUOT => error.DiskQuota,
- posix.EEXIST => error.PathAlreadyExists,
- posix.EFAULT => unreachable,
- posix.ELOOP => error.SymLinkLoop,
- posix.EMLINK => error.LinkQuotaExceeded,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ENOENT => error.FileNotFound,
- posix.ENOMEM => error.SystemResources,
- posix.ENOSPC => error.NoSpaceLeft,
- posix.ENOTDIR => error.NotDir,
- posix.EROFS => error.ReadOnlyFileSystem,
- else => unexpectedErrorPosix(err),
- };
+pub fn makeDirPosixC(dir_path: [*]const u8) !void {
+ const err = posix.getErrno(posix.mkdir(dir_path, 0o755));
+ switch (err) {
+ 0 => return,
+ posix.EACCES => return error.AccessDenied,
+ posix.EPERM => return error.AccessDenied,
+ posix.EDQUOT => return error.DiskQuota,
+ posix.EEXIST => return error.PathAlreadyExists,
+ posix.EFAULT => unreachable,
+ posix.ELOOP => return error.SymLinkLoop,
+ posix.EMLINK => return error.LinkQuotaExceeded,
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOMEM => return error.SystemResources,
+ posix.ENOSPC => return error.NoSpaceLeft,
+ posix.ENOTDIR => return error.NotDir,
+ posix.EROFS => return error.ReadOnlyFileSystem,
+ else => return unexpectedErrorPosix(err),
}
}
+pub fn makeDirPosix(dir_path: []const u8) !void {
+ const dir_path_c = try toPosixPath(dir_path);
+ return makeDirPosixC(&dir_path_c);
+}
+
/// Calls makeDir recursively to make an entire path. Returns success if the path
/// already exists and is a directory.
+/// 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, full_path);
defer allocator.free(resolved_path);
var end_index: usize = resolved_path.len;
while (true) {
- makeDir(allocator, resolved_path[0..end_index]) catch |err| {
- if (err == error.PathAlreadyExists) {
+ 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;
- } else if (err == error.FileNotFound) {
+ },
+ 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;
- }
+ },
+ else => return err,
};
if (end_index == resolved_path.len) return;
// march end_index forward until next path component
@@ -1071,6 +1252,7 @@ pub const DeleteDirError = error{
ReadOnlyFileSystem,
OutOfMemory,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -1129,7 +1311,6 @@ const DeleteTreeError = error{
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
- PathNotFound,
SystemResources,
NoSpaceLeft,
PathAlreadyExists,
@@ -1139,20 +1320,30 @@ const DeleteTreeError = error{
FileSystem,
FileBusy,
DirNotEmpty,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+
+ /// See https://github.com/ziglang/zig/issues/1396
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(allocator, full_path)) {
+ if (deleteFile(full_path)) {
return;
} else |err| switch (err) {
error.FileNotFound => return,
error.IsDir => {},
error.AccessDenied => got_access_denied = true,
- error.OutOfMemory,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
@@ -1160,6 +1351,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
error.NotDir,
error.FileSystem,
error.FileBusy,
+ error.InvalidUtf8,
+ error.BadPathName,
error.Unexpected,
=> return err,
}
@@ -1181,7 +1374,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
- error.PathNotFound,
+ error.FileNotFound,
error.SystemResources,
error.NoSpaceLeft,
error.PathAlreadyExists,
@@ -1251,7 +1444,7 @@ pub const Dir = struct {
};
pub const OpenError = error{
- PathNotFound,
+ FileNotFound,
NotDir,
AccessDenied,
FileTooBig,
@@ -1266,9 +1459,11 @@ pub const Dir = struct {
PathAlreadyExists,
OutOfMemory,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
+ /// TODO remove the allocator requirement from this API
pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
return Dir{
.allocator = allocator,
@@ -1284,7 +1479,6 @@ pub const Dir = struct {
},
Os.macosx, Os.ios => Handle{
.fd = try posixOpen(
- allocator,
dir_path,
posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
0,
@@ -1296,7 +1490,6 @@ pub const Dir = struct {
},
Os.linux => Handle{
.fd = try posixOpen(
- allocator,
dir_path,
posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
0,
@@ -1493,39 +1686,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
}
/// Read value of a symbolic link.
-pub fn readLink(allocator: *Allocator, pathname: []const u8) ![]u8 {
- const path_buf = try allocator.alloc(u8, pathname.len + 1);
- defer allocator.free(path_buf);
-
- mem.copy(u8, path_buf, pathname);
- path_buf[pathname.len] = 0;
-
- var result_buf = try allocator.alloc(u8, 1024);
- errdefer allocator.free(result_buf);
- while (true) {
- const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len);
- const err = posix.getErrno(ret_val);
- if (err > 0) {
- return switch (err) {
- posix.EACCES => error.AccessDenied,
- posix.EFAULT, posix.EINVAL => unreachable,
- posix.EIO => error.FileSystem,
- posix.ELOOP => error.SymLinkLoop,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ENOENT => error.FileNotFound,
- posix.ENOMEM => error.SystemResources,
- posix.ENOTDIR => error.NotDir,
- else => unexpectedErrorPosix(err),
- };
- }
- if (ret_val == result_buf.len) {
- result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
- continue;
- }
- return allocator.shrink(u8, result_buf, ret_val);
+/// The return value is a slice of out_buffer.
+pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
+ const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return out_buffer[0..rc],
+ posix.EACCES => return error.AccessDenied,
+ posix.EFAULT => unreachable,
+ posix.EINVAL => unreachable,
+ posix.EIO => return error.FileSystem,
+ posix.ELOOP => return error.SymLinkLoop,
+ posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOMEM => return error.SystemResources,
+ posix.ENOTDIR => return error.NotDir,
+ else => return unexpectedErrorPosix(err),
}
}
+/// Read value of a symbolic link.
+/// The return value is a slice of out_buffer.
+pub fn readLink(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 {
+ const file_path_c = try toPosixPath(file_path);
+ return readLinkC(out_buffer, &file_path_c);
+}
+
pub fn posix_setuid(uid: u32) !void {
const err = posix.getErrno(posix.setuid(uid));
if (err == 0) return;
@@ -1572,6 +1758,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {
pub const WindowsGetStdHandleErrs = error{
NoStdHandles,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -1899,7 +2087,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
/// Call this when you made a windows DLL call or something that does SetLastError
/// and you get an unexpected error.
pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
- if (unexpected_error_tracing) {
+ if (true) {
debug.warn("unexpected GetLastError(): {}\n", err);
debug.dumpCurrentStackTrace(null);
}
@@ -1908,17 +2096,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
pub fn openSelfExe() !os.File {
switch (builtin.os) {
- Os.linux => {
- const proc_file_path = "/proc/self/exe";
- var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
- var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
- return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
- },
+ Os.linux => return os.File.openReadC(c"/proc/self/exe"),
Os.macosx, Os.ios => {
- var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
- var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
- const self_exe_path = try selfExePath(&fixed_allocator.allocator);
- return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
+ 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);
},
else => @compileError("Unsupported OS"),
}
@@ -1927,7 +2110,7 @@ pub fn openSelfExe() !os.File {
test "openSelfExe" {
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
- else => return, // Unsupported OS.
+ else => return error.SkipZigTest, // Unsupported OS
}
}
@@ -1936,69 +2119,68 @@ test "openSelfExe" {
/// If you only want an open file handle, use openSelfExe.
/// This function may return an error if the current executable
/// was deleted after spawning.
-/// Caller owns returned memory.
-pub fn selfExePath(allocator: *mem.Allocator) ![]u8 {
+/// 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 => {
- // If the currently executing binary has been deleted,
- // the file path looks something like `/a/b/c/exe (deleted)`
- return readLink(allocator, "/proc/self/exe");
- },
+ Os.linux => return readLink(out_buffer, "/proc/self/exe"),
Os.windows => {
- var out_path = try Buffer.initSize(allocator, 0xff);
- errdefer out_path.deinit();
- while (true) {
- const dword_len = try math.cast(windows.DWORD, out_path.len());
- const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len);
- if (copied_amt <= 0) {
- const err = windows.GetLastError();
- return switch (err) {
- else => unexpectedErrorWindows(err),
- };
- }
- if (copied_amt < out_path.len()) {
- out_path.shrink(copied_amt);
- return out_path.toOwnedSlice();
+ 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 rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len);
+ assert(rc <= utf16le_buf.len);
+ if (rc == 0) {
+ const err = windows.GetLastError();
+ switch (err) {
+ else => return unexpectedErrorWindows(err),
}
- const new_len = (out_path.len() << 1) | 0b1;
- try out_path.resize(new_len);
}
+ const utf16le_slice = utf16le_buf[0..rc];
+ // 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 = 0;
- const ret1 = c._NSGetExecutablePath(undefined, &u32_len);
- assert(ret1 != 0);
- const bytes = try allocator.alloc(u8, u32_len);
- errdefer allocator.free(bytes);
- const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
- assert(ret2 == 0);
- return bytes;
+ 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"),
}
}
-/// Get the directory path that contains the current executable.
+/// `selfExeDirPath` except allocates the result on the heap.
/// Caller owns returned memory.
-pub fn selfExeDirPath(allocator: *mem.Allocator) ![]u8 {
+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 readLink(allocator, "/proc/self/exe");
- errdefer allocator.free(full_exe_path);
- const dir = path.dirname(full_exe_path) orelse ".";
- return allocator.shrink(u8, full_exe_path, dir.len);
+ 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 => {
- const self_exe_path = try selfExePath(allocator);
- errdefer allocator.free(self_exe_path);
- const dirname = os.path.dirname(self_exe_path) orelse ".";
- return allocator.shrink(u8, self_exe_path, dirname.len);
+ 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("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),
+ else => @compileError("Unsupported OS"),
}
}
@@ -2102,6 +2284,7 @@ pub const PosixBindError = error{
/// The socket inode would reside on a read-only filesystem.
ReadOnlyFileSystem,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2145,6 +2328,7 @@ const PosixListenError = error{
/// The socket is not of a type that supports the listen() operation.
OperationNotSupported,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2198,6 +2382,7 @@ pub const PosixAcceptError = error{
/// Firewall rules forbid connection.
BlockedByFirewall,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2243,6 +2428,7 @@ pub const LinuxEpollCreateError = error{
/// There was insufficient memory to create the kernel object.
SystemResources,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2297,6 +2483,7 @@ pub const LinuxEpollCtlError = error{
/// for example, a regular file or a directory.
FileDescriptorIncompatibleWithEpoll,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2339,6 +2526,7 @@ pub const LinuxEventFdError = error{
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2361,6 +2549,7 @@ pub const PosixGetSockNameError = error{
/// Insufficient resources were available in the system to perform the operation.
SystemResources,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2414,6 +2603,7 @@ pub const PosixConnectError = error{
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
ConnectionTimedOut,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2516,26 +2706,66 @@ 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: c.pthread_t,
+ handle: Thread.Handle,
stack_addr: usize,
stack_len: usize,
}
else switch (builtin.os) {
builtin.Os.linux => struct {
- pid: i32,
+ handle: Thread.Handle,
stack_addr: usize,
stack_len: usize,
},
builtin.Os.windows => struct {
- handle: windows.HANDLE,
+ 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.
+ 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);
@@ -2550,9 +2780,9 @@ pub const Thread = struct {
} else switch (builtin.os) {
builtin.Os.linux => {
while (true) {
- const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst);
+ const pid_value = @atomicLoad(i32, &self.data.handle, builtin.AtomicOrder.SeqCst);
if (pid_value == 0) break;
- const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null);
+ const rc = linux.futex_wait(@ptrToInt(&self.data.handle), linux.FUTEX_WAIT, pid_value, null);
switch (linux.getErrno(rc)) {
0 => continue,
posix.EINTR => continue,
@@ -2595,6 +2825,7 @@ pub const SpawnThreadError = error{
/// Not enough userland memory to spawn the thread.
OutOfMemory,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2734,7 +2965,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread
// use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly
const flags = 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;
const newtls: usize = 0;
- const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid);
+ const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
const err = posix.getErrno(rc);
switch (err) {
0 => return thread_ptr,
@@ -2770,7 +3001,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {
const err = posix.getErrno(posix.fstat(fd, &stat));
if (err > 0) {
return switch (err) {
- posix.EBADF => error.BadFd,
+ // 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),
};
@@ -2782,6 +3015,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {
pub const CpuCountError = error{
OutOfMemory,
PermissionDenied,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2852,6 +3087,7 @@ pub const BsdKQueueError = error{
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -2903,3 +3139,44 @@ pub fn bsdKEvent(
}
}
}
+
+pub fn linuxINotifyInit1(flags: u32) !i32 {
+ const rc = linux.inotify_init1(flags);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return @intCast(i32, rc),
+ posix.EINVAL => unreachable,
+ posix.EMFILE => return error.ProcessFdQuotaExceeded,
+ posix.ENFILE => return error.SystemFdQuotaExceeded,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+}
+
+pub fn linuxINotifyAddWatchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) !i32 {
+ const rc = linux.inotify_add_watch(inotify_fd, pathname, mask);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return @intCast(i32, rc),
+ posix.EACCES => return error.AccessDenied,
+ posix.EBADF => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EINVAL => unreachable,
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOMEM => return error.SystemResources,
+ posix.ENOSPC => return error.UserResourceLimitReached,
+ else => return unexpectedErrorPosix(err),
+ }
+}
+
+pub fn linuxINotifyRmWatch(inotify_fd: i32, wd: i32) !void {
+ const rc = linux.inotify_rm_watch(inotify_fd, wd);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return rc,
+ posix.EBADF => unreachable,
+ posix.EINVAL => unreachable,
+ else => unreachable,
+ }
+}
diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig
index 15607ea6c0..c369921e14 100644
--- a/std/os/linux/index.zig
+++ b/std/os/linux/index.zig
@@ -567,6 +567,37 @@ pub const MNT_DETACH = 2;
pub const MNT_EXPIRE = 4;
pub const UMOUNT_NOFOLLOW = 8;
+pub const IN_CLOEXEC = O_CLOEXEC;
+pub const IN_NONBLOCK = O_NONBLOCK;
+
+pub const IN_ACCESS = 0x00000001;
+pub const IN_MODIFY = 0x00000002;
+pub const IN_ATTRIB = 0x00000004;
+pub const IN_CLOSE_WRITE = 0x00000008;
+pub const IN_CLOSE_NOWRITE = 0x00000010;
+pub const IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
+pub const IN_OPEN = 0x00000020;
+pub const IN_MOVED_FROM = 0x00000040;
+pub const IN_MOVED_TO = 0x00000080;
+pub const IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO;
+pub const IN_CREATE = 0x00000100;
+pub const IN_DELETE = 0x00000200;
+pub const IN_DELETE_SELF = 0x00000400;
+pub const IN_MOVE_SELF = 0x00000800;
+pub const IN_ALL_EVENTS = 0x00000fff;
+
+pub const IN_UNMOUNT = 0x00002000;
+pub const IN_Q_OVERFLOW = 0x00004000;
+pub const IN_IGNORED = 0x00008000;
+
+pub const IN_ONLYDIR = 0x01000000;
+pub const IN_DONT_FOLLOW = 0x02000000;
+pub const IN_EXCL_UNLINK = 0x04000000;
+pub const IN_MASK_ADD = 0x20000000;
+
+pub const IN_ISDIR = 0x40000000;
+pub const IN_ONESHOT = 0x80000000;
+
pub const S_IFMT = 0o170000;
pub const S_IFDIR = 0o040000;
@@ -692,6 +723,10 @@ pub fn futex_wait(uaddr: usize, futex_op: u32, val: i32, timeout: ?*timespec) us
return syscall4(SYS_futex, uaddr, futex_op, @bitCast(u32, val), @ptrToInt(timeout));
}
+pub fn futex_wake(uaddr: usize, futex_op: u32, val: i32) usize {
+ return syscall3(SYS_futex, uaddr, futex_op, @bitCast(u32, val));
+}
+
pub fn getcwd(buf: [*]u8, size: usize) usize {
return syscall2(SYS_getcwd, @ptrToInt(buf), size);
}
@@ -700,6 +735,18 @@ pub fn getdents(fd: i32, dirp: [*]u8, count: usize) usize {
return syscall3(SYS_getdents, @intCast(usize, fd), @ptrToInt(dirp), count);
}
+pub fn inotify_init1(flags: u32) usize {
+ return syscall1(SYS_inotify_init1, flags);
+}
+
+pub fn inotify_add_watch(fd: i32, pathname: [*]const u8, mask: u32) usize {
+ return syscall3(SYS_inotify_add_watch, @intCast(usize, fd), @ptrToInt(pathname), mask);
+}
+
+pub fn inotify_rm_watch(fd: i32, wd: i32) usize {
+ return syscall2(SYS_inotify_rm_watch, @intCast(usize, fd), @intCast(usize, wd));
+}
+
pub fn isatty(fd: i32) bool {
var wsz: winsize = undefined;
return syscall3(SYS_ioctl, @intCast(usize, fd), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
@@ -742,6 +789,14 @@ pub fn read(fd: i32, buf: [*]u8, count: usize) usize {
return syscall3(SYS_read, @intCast(usize, fd), @ptrToInt(buf), count);
}
+pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize {
+ return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset);
+}
+
+pub fn pwritev(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64) usize {
+ return syscall4(SYS_pwritev, @intCast(usize, fd), @ptrToInt(iov), count, offset);
+}
+
// TODO https://github.com/ziglang/zig/issues/265
pub fn rmdir(path: [*]const u8) usize {
return syscall1(SYS_rmdir, @ptrToInt(path));
@@ -947,6 +1002,10 @@ pub fn getpid() i32 {
return @bitCast(i32, @truncate(u32, syscall0(SYS_getpid)));
}
+pub fn gettid() i32 {
+ return @bitCast(i32, @truncate(u32, syscall0(SYS_gettid)));
+}
+
pub fn sigprocmask(flags: u32, noalias set: *const sigset_t, noalias oldset: ?*sigset_t) usize {
return syscall4(SYS_rt_sigprocmask, flags, @ptrToInt(set), @ptrToInt(oldset), NSIG / 8);
}
@@ -1060,6 +1119,11 @@ pub const iovec = extern struct {
iov_len: usize,
};
+pub const iovec_const = extern struct {
+ iov_base: [*]const u8,
+ iov_len: usize,
+};
+
pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize {
return syscall3(SYS_getsockname, @intCast(usize, fd), @ptrToInt(addr), @ptrToInt(len));
}
@@ -1368,6 +1432,14 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize {
return syscall2(SYS_capset, @ptrToInt(hdrp), @ptrToInt(datap));
}
+pub const inotify_event = extern struct {
+ wd: i32,
+ mask: u32,
+ cookie: u32,
+ len: u32,
+ //name: [?]u8,
+};
+
test "import" {
if (builtin.os == builtin.Os.linux) {
_ = @import("test.zig");
diff --git a/std/os/path.zig b/std/os/path.zig
index d3ab0c519f..b3cfec1a3a 100644
--- a/std/os/path.zig
+++ b/std/os/path.zig
@@ -11,11 +11,14 @@ 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 = '/';
pub const sep = if (is_windows) sep_windows else sep_posix;
+pub const sep_str = [1]u8{sep};
+
pub const delimiter_windows = ';';
pub const delimiter_posix = ':';
pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
@@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {
pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) {
assert(is_windows); // resolveWindows called on non windows can't use getCwd
- return os.getCwd(allocator);
+ return os.getCwdAlloc(allocator);
}
// determine which disk designator we will result with, if any
@@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
},
WindowsPath.Kind.None => {
assert(is_windows); // resolveWindows called on non windows can't use getCwd
- const cwd = try os.getCwd(allocator);
+ const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd);
const parsed_cwd = windowsParsePath(cwd);
result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
@@ -448,7 +451,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
} else {
assert(is_windows); // resolveWindows called on non windows can't use getCwd
// TODO call get cwd for the result_disk_designator instead of the global one
- const cwd = try os.getCwd(allocator);
+ const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1);
@@ -506,7 +509,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result_index += 1;
}
- return result[0..result_index];
+ return allocator.shrink(u8, result, result_index);
}
/// This function is like a series of `cd` statements executed one after another.
@@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) {
assert(!is_windows); // resolvePosix called on windows can't use getCwd
- return os.getCwd(allocator);
+ return os.getCwdAlloc(allocator);
}
var first_index: usize = 0;
@@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result = try allocator.alloc(u8, max_size);
} else {
assert(!is_windows); // resolvePosix called on windows can't use getCwd
- const cwd = try os.getCwd(allocator);
+ const cwd = try os.getCwdAlloc(allocator);
defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1);
mem.copy(u8, result, cwd);
@@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result_index += 1;
}
- return result[0..result_index];
+ return allocator.shrink(u8, result, result_index);
}
test "os.path.resolve" {
- const cwd = try os.getCwd(debug.global_allocator);
+ const cwd = try os.getCwdAlloc(debug.global_allocator);
if (is_windows) {
if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
cwd[0] = asciiUpper(cwd[0]);
@@ -591,7 +594,7 @@ test "os.path.resolve" {
test "os.path.resolveWindows" {
if (is_windows) {
- const cwd = try os.getCwd(debug.global_allocator);
+ const cwd = try os.getCwdAlloc(debug.global_allocator);
const parsed_cwd = windowsParsePath(cwd);
{
const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
@@ -1073,112 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
assert(mem.eql(u8, result, expected_output));
}
-/// Return the canonicalized absolute pathname.
-/// Expands all symbolic links and resolves references to `.`, `..`, and
-/// extra `/` characters in ::pathname.
-/// Caller must deallocate result.
-pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
- switch (builtin.os) {
- Os.windows => {
- const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
- defer allocator.free(pathname_buf);
-
- mem.copy(u8, pathname_buf, pathname);
- pathname_buf[pathname.len] = 0;
-
- const h_file = windows.CreateFileA(pathname_buf.ptr, 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();
- return switch (err) {
- windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
- windows.ERROR.ACCESS_DENIED => error.AccessDenied,
- windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,
- else => os.unexpectedErrorWindows(err),
- };
- }
- defer os.close(h_file);
- var buf = try allocator.alloc(u8, 256);
- errdefer allocator.free(buf);
- while (true) {
- const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong;
- const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS);
-
- if (result == 0) {
- const err = windows.GetLastError();
- return switch (err) {
- windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
- windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory,
- windows.ERROR.INVALID_PARAMETER => unreachable,
- else => os.unexpectedErrorWindows(err),
- };
- }
+pub const RealError = error{
+ FileNotFound,
+ AccessDenied,
+ NameTooLong,
+ NotSupported,
+ NotDir,
+ SymLinkLoop,
+ InputOutput,
+ FileTooBig,
+ IsDir,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ NoSpaceLeft,
+ FileSystem,
+ BadPathName,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// TODO remove this possibility
+ PathAlreadyExists,
+
+ /// TODO remove this possibility
+ Unexpected,
+};
- if (result > buf.len) {
- buf = try allocator.realloc(u8, buf, result);
- continue;
- }
+/// 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 final_len = x: {
- if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
- var i: usize = 4;
- while (i < result) : (i += 1) {
- buf[i - 4] = buf[i];
- }
- break :x result - 4;
- } else {
- break :x result;
- }
- };
-
- return allocator.shrink(u8, buf, final_len);
- }
+ // 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.macosx, Os.ios => {
- // TODO instead of calling the libc function here, port the implementation
- // to Zig, and then remove the NameTooLong error possibility.
- const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
- defer allocator.free(pathname_buf);
-
- const result_buf = try allocator.alloc(u8, posix.PATH_MAX);
- errdefer allocator.free(result_buf);
-
- mem.copy(u8, pathname_buf, pathname);
- pathname_buf[pathname.len] = 0;
-
- const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr));
- if (err > 0) {
- return switch (err) {
- posix.EINVAL => unreachable,
- posix.EBADF => unreachable,
- posix.EFAULT => unreachable,
- posix.EACCES => error.AccessDenied,
- posix.ENOENT => error.FileNotFound,
- posix.ENOTSUP => error.NotSupported,
- posix.ENOTDIR => error.NotDir,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ELOOP => error.SymLinkLoop,
- posix.EIO => error.InputOutput,
- else => os.unexpectedErrorPosix(err),
- };
+ // 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),
}
- return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
},
Os.linux => {
- const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
+ 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".len]u8 = undefined;
- const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable;
+ const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
- return os.readLink(allocator, proc_path);
+ 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 => {
+ 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
- _ = real(debug.global_allocator, "some_path");
+ var buf: [os.MAX_PATH_BYTES]u8 = undefined;
+ std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound);
}
diff --git a/std/os/test.zig b/std/os/test.zig
index 9e795e8ad2..653ab13fd8 100644
--- a/std/os/test.zig
+++ b/std/os/test.zig
@@ -10,30 +10,47 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
test "makePath, put some files in it, deleteTree" {
- try os.makePath(a, "os_test_tmp/b/c");
- try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");
- try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");
+ try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c");
+ try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c" ++ os.path.sep_str ++ "file.txt", "nonsense");
+ try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah");
try os.deleteTree(a, "os_test_tmp");
if (os.Dir.open(a, "os_test_tmp")) |dir| {
@panic("expected error");
} else |err| {
- assert(err == error.PathNotFound);
+ assert(err == error.FileNotFound);
}
}
test "access file" {
try os.makePath(a, "os_test_tmp");
- if (os.File.access(a, "os_test_tmp/file.txt")) |ok| {
+ if (os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt")) |ok| {
@panic("expected error");
} else |err| {
- assert(err == error.NotFound);
+ assert(err == error.FileNotFound);
}
- try io.writeFile(a, "os_test_tmp/file.txt", "");
- try os.File.access(a, "os_test_tmp/file.txt");
+ try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", "");
+ try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt");
try os.deleteTree(a, "os_test_tmp");
}
+fn testThreadIdFn(thread_id: *os.Thread.Id) void {
+ thread_id.* = os.Thread.getCurrentId();
+}
+
+test "std.os.Thread.getCurrentId" {
+ var thread_current_id: os.Thread.Id = undefined;
+ const thread = try os.spawnThread(&thread_current_id, testThreadIdFn);
+ const thread_id = thread.handle();
+ thread.wait();
+ switch (builtin.os) {
+ builtin.Os.windows => assert(os.Thread.getCurrentId() != thread_current_id),
+ else => {
+ assert(thread_current_id == thread_id);
+ },
+ }
+}
+
test "spawn threads" {
var shared_ctx: i32 = 1;
diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig
index 90ccfaf6c5..bb055468a5 100644
--- a/std/os/windows/index.zig
+++ b/std/os/windows/index.zig
@@ -67,8 +67,9 @@ pub const INVALID_FILE_ATTRIBUTES = DWORD(@maxValue(DWORD));
pub const OVERLAPPED = extern struct {
Internal: ULONG_PTR,
InternalHigh: ULONG_PTR,
- Pointer: PVOID,
- hEvent: HANDLE,
+ Offset: DWORD,
+ OffsetHigh: DWORD,
+ hEvent: ?HANDLE,
};
pub const LPOVERLAPPED = *OVERLAPPED;
@@ -350,3 +351,15 @@ pub const E_ACCESSDENIED = @bitCast(c_long, c_ulong(0x80070005));
pub const E_HANDLE = @bitCast(c_long, c_ulong(0x80070006));
pub const E_OUTOFMEMORY = @bitCast(c_long, c_ulong(0x8007000E));
pub const E_INVALIDARG = @bitCast(c_long, c_ulong(0x80070057));
+
+pub const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+pub const FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
+pub const FILE_FLAG_NO_BUFFERING = 0x20000000;
+pub const FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
+pub const FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
+pub const FILE_FLAG_OVERLAPPED = 0x40000000;
+pub const FILE_FLAG_POSIX_SEMANTICS = 0x0100000;
+pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000;
+pub const FILE_FLAG_SESSION_AWARE = 0x00800000;
+pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
+pub const FILE_FLAG_WRITE_THROUGH = 0x80000000;
diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig
index fa3473ad05..66b5291189 100644
--- a/std/os/windows/kernel32.zig
+++ b/std/os/windows/kernel32.zig
@@ -1,14 +1,24 @@
use @import("index.zig");
+pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
+
pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
-pub extern "kernel32" stdcallcc fn CreateDirectoryA(
- lpPathName: LPCSTR,
- lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
-) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
pub extern "kernel32" stdcallcc fn CreateFileA(
- lpFileName: LPCSTR,
+ lpFileName: [*]const u8, // TODO null terminated pointer type
+ dwDesiredAccess: DWORD,
+ dwShareMode: DWORD,
+ lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
+ dwCreationDisposition: DWORD,
+ dwFlagsAndAttributes: DWORD,
+ hTemplateFile: ?HANDLE,
+) HANDLE;
+
+pub extern "kernel32" stdcallcc fn CreateFileW(
+ lpFileName: [*]const u16, // TODO null terminated pointer type
dwDesiredAccess: DWORD,
dwShareMode: DWORD,
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
@@ -47,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex
pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
-pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
+pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL;
+pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
@@ -61,7 +72,11 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
+pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
pub extern "kernel32" stdcallcc fn GetEnvironmentStringsA() ?[*]u8;
@@ -71,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
-pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;
+pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
-pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
+pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD;
+pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
@@ -91,6 +108,15 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
dwFlags: DWORD,
) DWORD;
+pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
+ hFile: HANDLE,
+ lpszFilePath: [*]u16,
+ cchFilePath: DWORD,
+ dwFlags: DWORD,
+) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
+
pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL;
@@ -101,7 +127,6 @@ pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: S
pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL;
pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void;
pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T;
-pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL;
pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T;
pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL;
@@ -111,9 +136,17 @@ pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBy
pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL;
+pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
+
pub extern "kernel32" stdcallcc fn MoveFileExA(
- lpExistingFileName: LPCSTR,
- lpNewFileName: LPCSTR,
+ lpExistingFileName: [*]const u8,
+ lpNewFileName: [*]const u8,
+ dwFlags: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn MoveFileExW(
+ lpExistingFileName: [*]const u16,
+ lpNewFileName: [*]const u16,
dwFlags: DWORD,
) BOOL;
@@ -123,11 +156,22 @@ pub extern "kernel32" stdcallcc fn QueryPerformanceCounter(lpPerformanceCount: *
pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL;
+pub extern "kernel32" stdcallcc fn ReadDirectoryChangesW(
+ hDirectory: HANDLE,
+ lpBuffer: [*]align(@alignOf(FILE_NOTIFY_INFORMATION)) u8,
+ nBufferLength: DWORD,
+ bWatchSubtree: BOOL,
+ dwNotifyFilter: DWORD,
+ lpBytesReturned: ?*DWORD,
+ lpOverlapped: ?*OVERLAPPED,
+ lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE,
+) BOOL;
+
pub extern "kernel32" stdcallcc fn ReadFile(
in_hFile: HANDLE,
- out_lpBuffer: *c_void,
+ out_lpBuffer: [*]u8,
in_nNumberOfBytesToRead: DWORD,
- out_lpNumberOfBytesRead: *DWORD,
+ out_lpNumberOfBytesRead: ?*DWORD,
in_out_lpOverlapped: ?*OVERLAPPED,
) BOOL;
@@ -150,13 +194,41 @@ pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMillis
pub extern "kernel32" stdcallcc fn WriteFile(
in_hFile: HANDLE,
- in_lpBuffer: *const c_void,
+ in_lpBuffer: [*]const u8,
in_nNumberOfBytesToWrite: DWORD,
out_lpNumberOfBytesWritten: ?*DWORD,
in_out_lpOverlapped: ?*OVERLAPPED,
) BOOL;
+pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL;
+
//TODO: call unicode versions instead of relying on ANSI code page
pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
+
+pub const FILE_NOTIFY_INFORMATION = extern struct {
+ NextEntryOffset: DWORD,
+ Action: DWORD,
+ FileNameLength: DWORD,
+ FileName: [1]WCHAR,
+};
+
+pub const FILE_ACTION_ADDED = 0x00000001;
+pub const FILE_ACTION_REMOVED = 0x00000002;
+pub const FILE_ACTION_MODIFIED = 0x00000003;
+pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
+pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
+
+pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn (DWORD, DWORD, *OVERLAPPED) void;
+
+pub const FILE_LIST_DIRECTORY = 1;
+
+pub const FILE_NOTIFY_CHANGE_CREATION = 64;
+pub const FILE_NOTIFY_CHANGE_SIZE = 8;
+pub const FILE_NOTIFY_CHANGE_SECURITY = 256;
+pub const FILE_NOTIFY_CHANGE_LAST_ACCESS = 32;
+pub const FILE_NOTIFY_CHANGE_LAST_WRITE = 16;
+pub const FILE_NOTIFY_CHANGE_DIR_NAME = 2;
+pub const FILE_NOTIFY_CHANGE_FILE_NAME = 1;
+pub const FILE_NOTIFY_CHANGE_ATTRIBUTES = 4;
diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig
index c9d2c3c3e6..72de896996 100644
--- a/std/os/windows/util.zig
+++ b/std/os/windows/util.zig
@@ -7,9 +7,17 @@ const mem = std.mem;
const BufMap = std.BufMap;
const cstr = std.cstr;
+// > The maximum path of 32,767 characters is approximate, because the "\\?\"
+// > prefix may be expanded to a longer string by the system at run time, and
+// > this expansion applies to the total length.
+// 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 WaitError = error{
WaitAbandoned,
WaitTimeOut,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
@@ -36,20 +44,21 @@ pub fn windowsClose(handle: windows.HANDLE) void {
pub const WriteError = error{
SystemResources,
OperationAborted,
- IoPending,
BrokenPipe,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
};
pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) WriteError!void {
- if (windows.WriteFile(handle, @ptrCast(*const c_void, bytes.ptr), @intCast(u32, bytes.len), null, null) == 0) {
+ if (windows.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), null, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.INVALID_USER_BUFFER => WriteError.SystemResources,
windows.ERROR.NOT_ENOUGH_MEMORY => WriteError.SystemResources,
windows.ERROR.OPERATION_ABORTED => WriteError.OperationAborted,
windows.ERROR.NOT_ENOUGH_QUOTA => WriteError.SystemResources,
- windows.ERROR.IO_PENDING => WriteError.IoPending,
+ windows.ERROR.IO_PENDING => unreachable,
windows.ERROR.BROKEN_PIPE => WriteError.BrokenPipe,
else => os.unexpectedErrorWindows(err),
};
@@ -87,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
pub const OpenError = error{
SharingViolation,
PathAlreadyExists,
+
+ /// When any of the path components can not be found or the file component can not
+ /// be found. Some operating systems distinguish between path components not found and
+ /// file components not found, but they are collapsed into FileNotFound to gain
+ /// consistency across operating systems.
FileNotFound,
+
AccessDenied,
PipeBusy,
+ NameTooLong,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+
+ /// See https://github.com/ziglang/zig/issues/1396
Unexpected,
- OutOfMemory,
};
-/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
pub fn windowsOpen(
- allocator: *mem.Allocator,
file_path: []const u8,
desired_access: windows.DWORD,
share_mode: windows.DWORD,
creation_disposition: windows.DWORD,
flags_and_attrs: windows.DWORD,
) OpenError!windows.HANDLE {
- const path_with_null = try cstr.addNullByte(allocator, file_path);
- defer allocator.free(path_with_null);
+ const file_path_w = try sliceToPrefixedFileW(file_path);
- const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
+ const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
if (result == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
- return switch (err) {
- windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,
- windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,
- windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,
- windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,
- windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,
- else => os.unexpectedErrorWindows(err),
- };
+ switch (err) {
+ windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation,
+ windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
+ windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
+ windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
+ windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound,
+ windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
+ windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
+ else => return os.unexpectedErrorWindows(err),
+ }
}
return result;
@@ -193,9 +216,8 @@ pub fn windowsFindFirstFile(
if (handle == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
switch (err) {
- windows.ERROR.FILE_NOT_FOUND,
- windows.ERROR.PATH_NOT_FOUND,
- => return error.PathNotFound,
+ windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+ windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
else => return os.unexpectedErrorWindows(err),
}
}
@@ -221,6 +243,7 @@ pub fn windowsCreateIoCompletionPort(file_handle: windows.HANDLE, existing_compl
const handle = windows.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse {
const err = windows.GetLastError();
switch (err) {
+ windows.ERROR.INVALID_PARAMETER => unreachable,
else => return os.unexpectedErrorWindows(err),
}
};
@@ -238,21 +261,55 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
}
}
-pub const WindowsWaitResult = error{
+pub const WindowsWaitResult = enum {
Normal,
Aborted,
+ Cancelled,
};
pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_transferred_count: *windows.DWORD, lpCompletionKey: *usize, lpOverlapped: *?*windows.OVERLAPPED, dwMilliseconds: windows.DWORD) WindowsWaitResult {
if (windows.GetQueuedCompletionStatus(completion_port, bytes_transferred_count, lpCompletionKey, lpOverlapped, dwMilliseconds) == windows.FALSE) {
- if (std.debug.runtime_safety) {
- const err = windows.GetLastError();
- if (err != windows.ERROR.ABANDONED_WAIT_0) {
- std.debug.warn("err: {}\n", err);
- }
- assert(err == windows.ERROR.ABANDONED_WAIT_0);
+ const err = windows.GetLastError();
+ switch (err) {
+ windows.ERROR.ABANDONED_WAIT_0 => return WindowsWaitResult.Aborted,
+ windows.ERROR.OPERATION_ABORTED => return WindowsWaitResult.Cancelled,
+ else => {
+ if (std.debug.runtime_safety) {
+ std.debug.panic("unexpected error: {}\n", err);
+ }
+ },
}
- return WindowsWaitResult.Aborted;
}
return WindowsWaitResult.Normal;
}
+
+pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
+ return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
+}
+
+pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
+ // TODO well defined copy elision
+ var result: [PATH_MAX_WIDE + 1]u16 = undefined;
+
+ // > File I/O functions in the Windows API convert "/" to "\" as part of
+ // > converting the name to an NT-style name, except when using the "\\?\"
+ // > prefix as detailed in the following sections.
+ // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
+ // Because we want the larger maximum path length for absolute paths, we
+ // disallow forward slashes in zig std lib file functions on Windows.
+ for (s) |byte|
+ switch (byte) {
+ '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
+ else => {},
+ };
+ const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
+ const prefix = []u16{ '\\', '\\', '?', '\\' };
+ mem.copy(u16, result[0..], prefix);
+ break :blk prefix.len;
+ };
+ const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
+ assert(end_index <= result.len);
+ if (end_index == result.len) return error.NameTooLong;
+ result[end_index] = 0;
+ return result;
+}
diff --git a/std/os/zen.zig b/std/os/zen.zig
index 2312b36dea..55b6d91128 100644
--- a/std/os/zen.zig
+++ b/std/os/zen.zig
@@ -1,38 +1,55 @@
+const std = @import("../index.zig");
+const assert = std.debug.assert;
+
//////////////////////////
//// IPC structures ////
//////////////////////////
pub const Message = struct {
- sender: MailboxId,
+sender: MailboxId,
receiver: MailboxId,
- type: usize,
- payload: usize,
+ code: usize,
+ args: [5]usize,
+ payload: ?[]const u8,
pub fn from(mailbox_id: *const MailboxId) Message {
- return Message{
- .sender = MailboxId.Undefined,
- .receiver = *mailbox_id,
- .type = 0,
- .payload = 0,
+ return Message {
+ .sender = MailboxId.Undefined,
+ .receiver = mailbox_id.*,
+ .code = undefined,
+ .args = undefined,
+ .payload = null,
};
}
- pub fn to(mailbox_id: *const MailboxId, msg_type: usize) Message {
- return Message{
- .sender = MailboxId.This,
- .receiver = *mailbox_id,
- .type = msg_type,
- .payload = 0,
+ pub fn to(mailbox_id: *const MailboxId, msg_code: usize, args: ...) Message {
+ var message = Message {
+ .sender = MailboxId.This,
+ .receiver = mailbox_id.*,
+ .code = msg_code,
+ .args = undefined,
+ .payload = null,
};
+
+ assert (args.len <= message.args.len);
+ comptime var i = 0;
+ inline while (i < args.len) : (i += 1) {
+ message.args[i] = args[i];
+ }
+
+ return message;
}
- pub fn withData(mailbox_id: *const MailboxId, msg_type: usize, payload: usize) Message {
- return Message{
- .sender = MailboxId.This,
- .receiver = *mailbox_id,
- .type = msg_type,
- .payload = payload,
- };
+ pub fn as(self: *const Message, sender: *const MailboxId) Message {
+ var message = self.*;
+ message.sender = sender.*;
+ return message;
+ }
+
+ pub fn withPayload(self: *const Message, payload: []const u8) Message {
+ var message = self.*;
+ message.payload = payload;
+ return message;
}
};
@@ -63,21 +80,26 @@ pub const STDOUT_FILENO = 1;
pub const STDERR_FILENO = 2;
// FIXME: let's borrow Linux's error numbers for now.
-pub const getErrno = @import("linux/index.zig").getErrno;
use @import("linux/errno.zig");
+// Get the errno from a syscall return value, or 0 for no error.
+pub fn getErrno(r: usize) usize {
+ const signed_r = @bitCast(isize, r);
+ return if (signed_r > -4096 and signed_r < 0) @intCast(usize, -signed_r) else 0;
+}
// TODO: implement this correctly.
-pub fn read(fd: i32, buf: *u8, count: usize) usize {
+pub fn read(fd: i32, buf: [*]u8, count: usize) usize {
switch (fd) {
STDIN_FILENO => {
var i: usize = 0;
while (i < count) : (i += 1) {
send(Message.to(Server.Keyboard, 0));
+ // FIXME: we should be certain that we are receiving from Keyboard.
var message = Message.from(MailboxId.This);
- receive(*message);
+ receive(&message);
- buf[i] = u8(message.payload);
+ buf[i] = @intCast(u8, message.args[0]);
}
},
else => unreachable,
@@ -86,13 +108,11 @@ pub fn read(fd: i32, buf: *u8, count: usize) usize {
}
// TODO: implement this correctly.
-pub fn write(fd: i32, buf: *const u8, count: usize) usize {
+pub fn write(fd: i32, buf: [*]const u8, count: usize) usize {
switch (fd) {
STDOUT_FILENO, STDERR_FILENO => {
- var i: usize = 0;
- while (i < count) : (i += 1) {
- send(Message.withData(Server.Terminal, 1, buf[i]));
- }
+ send(Message.to(Server.Terminal, 1)
+ .withPayload(buf[0..count]));
},
else => unreachable,
}
@@ -104,17 +124,14 @@ pub fn write(fd: i32, buf: *const u8, count: usize) usize {
///////////////////////////
pub const Syscall = enum(usize) {
- exit = 0,
- createPort = 1,
- send = 2,
- receive = 3,
- subscribeIRQ = 4,
- inb = 5,
- map = 6,
- createThread = 7,
- createProcess = 8,
- wait = 9,
- portReady = 10,
+ exit = 0,
+ send = 1,
+ receive = 2,
+ subscribeIRQ = 3,
+ inb = 4,
+ outb = 5,
+ map = 6,
+ createThread = 7,
};
////////////////////
@@ -126,13 +143,6 @@ pub fn exit(status: i32) noreturn {
unreachable;
}
-pub fn createPort(mailbox_id: *const MailboxId) void {
- _ = switch (*mailbox_id) {
- MailboxId.Port => |id| syscall1(Syscall.createPort, id),
- else => unreachable,
- };
-}
-
pub fn send(message: *const Message) void {
_ = syscall1(Syscall.send, @ptrToInt(message));
}
@@ -146,29 +156,21 @@ pub fn subscribeIRQ(irq: u8, mailbox_id: *const MailboxId) void {
}
pub fn inb(port: u16) u8 {
- return u8(syscall1(Syscall.inb, port));
+ return @intCast(u8, syscall1(Syscall.inb, port));
+}
+
+pub fn outb(port: u16, value: u8) void {
+ _ = syscall2(Syscall.outb, port, value);
}
pub fn map(v_addr: usize, p_addr: usize, size: usize, writable: bool) bool {
- return syscall4(Syscall.map, v_addr, p_addr, size, usize(writable)) != 0;
+ return syscall4(Syscall.map, v_addr, p_addr, size, @boolToInt(writable)) != 0;
}
pub fn createThread(function: fn () void) u16 {
return u16(syscall1(Syscall.createThread, @ptrToInt(function)));
}
-pub fn createProcess(elf_addr: usize) u16 {
- return u16(syscall1(Syscall.createProcess, elf_addr));
-}
-
-pub fn wait(tid: u16) void {
- _ = syscall1(Syscall.wait, tid);
-}
-
-pub fn portReady(port: u16) bool {
- return syscall1(Syscall.portReady, port) != 0;
-}
-
/////////////////////////
//// Syscall stubs ////
/////////////////////////