diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2018-08-25 21:57:28 -0400 |
|---|---|---|
| committer | Andrew Kelley <superjoe30@gmail.com> | 2018-08-25 21:57:28 -0400 |
| commit | 7109035b78ee05302bbdaadc52013b430a030b69 (patch) | |
| tree | ae6d7202dc75f2c799f5fbcad72ccf8b02a954a4 /std/os | |
| parent | 6cf248ec0824c746fc796905144c8077ccab99cf (diff) | |
| parent | 526338b00fbe1cac19f64832176af3bdf2108a56 (diff) | |
| download | zig-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.zig | 14 | ||||
| -rw-r--r-- | std/os/darwin.zig | 209 | ||||
| -rw-r--r-- | std/os/file.zig | 172 | ||||
| -rw-r--r-- | std/os/get_app_data_dir.zig | 3 | ||||
| -rw-r--r-- | std/os/index.zig | 811 | ||||
| -rw-r--r-- | std/os/linux/index.zig | 72 | ||||
| -rw-r--r-- | std/os/path.zig | 237 | ||||
| -rw-r--r-- | std/os/test.zig | 33 | ||||
| -rw-r--r-- | std/os/windows/index.zig | 17 | ||||
| -rw-r--r-- | std/os/windows/kernel32.zig | 102 | ||||
| -rw-r--r-- | std/os/windows/util.zig | 113 | ||||
| -rw-r--r-- | std/os/zen.zig | 126 |
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 //// ///////////////////////// |
