diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-09-30 04:28:19 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-30 04:28:19 -0400 |
| commit | fe117d9961c3622fda5c359733d01de686509af0 (patch) | |
| tree | f4c3c9282049dff85dcc417f831414cb5ab7c524 /src/stage1/os.cpp | |
| parent | bd449b184a0c9fb824184672b12f90ed2698b77a (diff) | |
| parent | 3249e5d952cfcecca999391ffc02cce92ff8fcc4 (diff) | |
| download | zig-fe117d9961c3622fda5c359733d01de686509af0.tar.gz zig-fe117d9961c3622fda5c359733d01de686509af0.zip | |
Merge pull request #6250 from ziglang/stage2-zig-cc
move `zig cc`, `zig translate-c`, `zig libc`, main(), and linking from stage1 to stage2
Diffstat (limited to 'src/stage1/os.cpp')
| -rw-r--r-- | src/stage1/os.cpp | 2345 |
1 files changed, 2345 insertions, 0 deletions
diff --git a/src/stage1/os.cpp b/src/stage1/os.cpp new file mode 100644 index 0000000000..33d98fd416 --- /dev/null +++ b/src/stage1/os.cpp @@ -0,0 +1,2345 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "os.hpp" +#include "buffer.hpp" +#include "heap.hpp" +#include "util.hpp" +#include "error.hpp" +#include "util_base.hpp" +#include <stdint.h> +#include <stdio.h> + +#if defined(_WIN32) + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif + +#if !defined(VC_EXTRALEAN) +#define VC_EXTRALEAN +#endif + +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif + +#if !defined(_WIN32_WINNT) +#define _WIN32_WINNT 0x600 +#endif + +#if !defined(NTDDI_VERSION) +#define NTDDI_VERSION 0x06000000 +#endif + +#include <windows.h> +#include <shlobj.h> +#include <io.h> +#include <fcntl.h> +#include <ntsecapi.h> + +#if defined(_MSC_VER) +typedef SSIZE_T ssize_t; +#endif +#else +#define ZIG_OS_POSIX + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/resource.h> +#include <fcntl.h> +#include <limits.h> +#include <spawn.h> + +#endif + +#if defined(ZIG_OS_LINUX) || defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) +#include <link.h> +#endif + +#if defined(ZIG_OS_LINUX) +#include <sys/auxv.h> +#endif + +#if defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) +#include <sys/sysctl.h> +#endif + +#if defined(__MACH__) +#include <mach/clock.h> +#include <mach/mach.h> +#include <mach-o/dyld.h> +#endif + +#if defined(ZIG_OS_WINDOWS) +static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le); +static size_t utf8_to_utf16le(WCHAR *utf16_le, Slice<uint8_t> utf8); +static uint64_t windows_perf_freq; +#elif defined(__MACH__) +static clock_serv_t macos_calendar_clock; +static clock_serv_t macos_monotonic_clock; +#endif + +#include <stdlib.h> +#include <errno.h> +#include <time.h> + +#if !defined(environ) +extern char **environ; +#endif + +#if defined(ZIG_OS_POSIX) +static void populate_termination(Termination *term, int status) { + if (WIFEXITED(status)) { + term->how = TerminationIdClean; + term->code = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + term->how = TerminationIdSignaled; + term->code = WTERMSIG(status); + } else if (WIFSTOPPED(status)) { + term->how = TerminationIdStopped; + term->code = WSTOPSIG(status); + } else { + term->how = TerminationIdUnknown; + term->code = status; + } +} + +static void os_spawn_process_posix(ZigList<const char *> &args, Termination *term) { + const char **argv = heap::c_allocator.allocate<const char *>(args.length + 1); + for (size_t i = 0; i < args.length; i += 1) { + argv[i] = args.at(i); + } + argv[args.length] = nullptr; + + pid_t pid; + int rc = posix_spawnp(&pid, args.at(0), nullptr, nullptr, const_cast<char *const*>(argv), environ); + if (rc != 0) { + zig_panic("unable to spawn %s: %s", args.at(0), strerror(rc)); + } + + int status; + waitpid(pid, &status, 0); + populate_termination(term, status); +} +#endif + +#if defined(ZIG_OS_WINDOWS) + +static void os_windows_create_command_line(Buf *command_line, ZigList<const char *> &args) { + buf_resize(command_line, 0); + const char *prefix = "\""; + for (size_t arg_i = 0; arg_i < args.length; arg_i += 1) { + const char *arg = args.at(arg_i); + buf_append_str(command_line, prefix); + prefix = " \""; + size_t arg_len = strlen(arg); + for (size_t c_i = 0; c_i < arg_len; c_i += 1) { + if (arg[c_i] == '\"') { + zig_panic("TODO"); + } + buf_append_char(command_line, arg[c_i]); + } + buf_append_char(command_line, '\"'); + } +} + +static void os_spawn_process_windows(ZigList<const char *> &args, Termination *term) { + Buf command_line = BUF_INIT; + os_windows_create_command_line(&command_line, args); + + PROCESS_INFORMATION piProcInfo = {0}; + STARTUPINFOW siStartInfo = {0}; + siStartInfo.cb = sizeof(STARTUPINFOW); + + Slice<uint8_t> exe_slice = str(args.at(0)); + auto exe_utf16_slice = Slice<WCHAR>::alloc(exe_slice.len + 1); + exe_utf16_slice.ptr[utf8_to_utf16le(exe_utf16_slice.ptr, exe_slice)] = 0; + + auto command_line_utf16 = Slice<WCHAR>::alloc(buf_len(&command_line) + 1); + command_line_utf16.ptr[utf8_to_utf16le(command_line_utf16.ptr, buf_to_slice(&command_line))] = 0; + + BOOL success = CreateProcessW(exe_utf16_slice.ptr, command_line_utf16.ptr, nullptr, nullptr, TRUE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, + &siStartInfo, &piProcInfo); + + if (!success) { + zig_panic("CreateProcess failed. exe: %s command_line: %s", args.at(0), buf_ptr(&command_line)); + } + + WaitForSingleObject(piProcInfo.hProcess, INFINITE); + + DWORD exit_code; + if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) { + zig_panic("GetExitCodeProcess failed"); + } + term->how = TerminationIdClean; + term->code = exit_code; +} +#endif + +void os_spawn_process(ZigList<const char *> &args, Termination *term) { +#if defined(ZIG_OS_WINDOWS) + os_spawn_process_windows(args, term); +#elif defined(ZIG_OS_POSIX) + os_spawn_process_posix(args, term); +#else +#error "missing os_spawn_process implementation" +#endif +} + +void os_path_dirname(Buf *full_path, Buf *out_dirname) { + return os_path_split(full_path, out_dirname, nullptr); +} + +bool os_is_sep(uint8_t c) { +#if defined(ZIG_OS_WINDOWS) + return c == '\\' || c == '/'; +#else + return c == '/'; +#endif +} + +void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename) { + size_t len = buf_len(full_path); + if (len != 0) { + size_t last_index = len - 1; + char last_char = buf_ptr(full_path)[last_index]; + if (os_is_sep(last_char)) { + if (last_index == 0) { + if (out_dirname) buf_init_from_mem(out_dirname, &last_char, 1); + if (out_basename) buf_init_from_str(out_basename, ""); + return; + } + last_index -= 1; + } + for (size_t i = last_index;;) { + uint8_t c = buf_ptr(full_path)[i]; + if (os_is_sep(c)) { + if (out_dirname) { + buf_init_from_mem(out_dirname, buf_ptr(full_path), (i == 0) ? 1 : i); + } + if (out_basename) { + buf_init_from_mem(out_basename, buf_ptr(full_path) + i + 1, buf_len(full_path) - (i + 1)); + } + return; + } + if (i == 0) break; + i -= 1; + } + } + if (out_dirname) buf_init_from_mem(out_dirname, ".", 1); + if (out_basename) buf_init_from_buf(out_basename, full_path); +} + +void os_path_extname(Buf *full_path, Buf *out_basename, Buf *out_extname) { + if (buf_len(full_path) == 0) { + if (out_basename) buf_init_from_str(out_basename, ""); + if (out_extname) buf_init_from_str(out_extname, ""); + return; + } + size_t i = buf_len(full_path) - 1; + while (true) { + if (buf_ptr(full_path)[i] == '.') { + if (out_basename) { + buf_resize(out_basename, 0); + buf_append_mem(out_basename, buf_ptr(full_path), i); + } + + if (out_extname) { + buf_resize(out_extname, 0); + buf_append_mem(out_extname, buf_ptr(full_path) + i, buf_len(full_path) - i); + } + return; + } + + if (i == 0) { + if (out_basename) buf_init_from_buf(out_basename, full_path); + if (out_extname) buf_init_from_str(out_extname, ""); + return; + } + i -= 1; + } +} + +void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path) { + if (buf_len(dirname) == 0) { + buf_init_from_buf(out_full_path, basename); + return; + } + + buf_init_from_buf(out_full_path, dirname); + uint8_t c = *(buf_ptr(out_full_path) + buf_len(out_full_path) - 1); + if (!os_is_sep(c)) + buf_append_char(out_full_path, ZIG_OS_SEP_CHAR); + buf_append_buf(out_full_path, basename); +} + +Error os_path_real(Buf *rel_path, Buf *out_abs_path) { +#if defined(ZIG_OS_WINDOWS) + PathSpace rel_path_space = slice_to_prefixed_file_w(buf_to_slice(rel_path)); + PathSpace out_abs_path_space; + + if (_wfullpath(&out_abs_path_space.data.items[0], &rel_path_space.data.items[0], PATH_MAX_WIDE) == nullptr) { + zig_panic("_wfullpath failed"); + } + utf16le_ptr_to_utf8(out_abs_path, &out_abs_path_space.data.items[0]); + return ErrorNone; +#elif defined(ZIG_OS_POSIX) + buf_resize(out_abs_path, PATH_MAX + 1); + char *result = realpath(buf_ptr(rel_path), buf_ptr(out_abs_path)); + if (!result) { + int err = errno; + if (err == EACCES) { + return ErrorAccess; + } else if (err == ENOENT) { + return ErrorFileNotFound; + } else if (err == ENOMEM) { + return ErrorNoMem; + } else { + return ErrorFileSystem; + } + } + buf_resize(out_abs_path, strlen(buf_ptr(out_abs_path))); + return ErrorNone; +#else +#error "missing os_path_real implementation" +#endif +} + +#if defined(ZIG_OS_WINDOWS) +// Ported from std/os/path.zig +static bool isAbsoluteWindows(Slice<uint8_t> path) { + if (path.ptr[0] == '/') + return true; + + if (path.ptr[0] == '\\') { + return true; + } + if (path.len < 3) { + return false; + } + if (path.ptr[1] == ':') { + if (path.ptr[2] == '/') + return true; + if (path.ptr[2] == '\\') + return true; + } + return false; +} +#endif + +bool os_path_is_absolute(Buf *path) { +#if defined(ZIG_OS_WINDOWS) + return isAbsoluteWindows(buf_to_slice(path)); +#elif defined(ZIG_OS_POSIX) + return buf_ptr(path)[0] == '/'; +#else +#error "missing os_path_is_absolute implementation" +#endif +} + +#if defined(ZIG_OS_WINDOWS) + +enum WindowsPathKind { + WindowsPathKindNone, + WindowsPathKindDrive, + WindowsPathKindNetworkShare, +}; + +struct WindowsPath { + Slice<uint8_t> disk_designator; + WindowsPathKind kind; + bool is_abs; +}; + + +// Ported from std/os/path.zig +static WindowsPath windowsParsePath(Slice<uint8_t> path) { + if (path.len >= 2 && path.ptr[1] == ':') { + return WindowsPath{ + path.slice(0, 2), + WindowsPathKindDrive, + isAbsoluteWindows(path), + }; + } + if (path.len >= 1 && (path.ptr[0] == '/' || path.ptr[0] == '\\') && + (path.len == 1 || (path.ptr[1] != '/' && path.ptr[1] != '\\'))) + { + return WindowsPath{ + path.slice(0, 0), + WindowsPathKindNone, + true, + }; + } + WindowsPath relative_path = { + str(""), + WindowsPathKindNone, + false, + }; + if (path.len < strlen("//a/b")) { + return relative_path; + } + + { + if (memStartsWith(path, str("//"))) { + if (path.ptr[2] == '/') { + return relative_path; + } + + SplitIterator it = memSplit(path, str("/")); + { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return relative_path; + } + { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return relative_path; + } + return WindowsPath{ + path.slice(0, it.index), + WindowsPathKindNetworkShare, + isAbsoluteWindows(path), + }; + } + } + { + if (memStartsWith(path, str("\\\\"))) { + if (path.ptr[2] == '\\') { + return relative_path; + } + + SplitIterator it = memSplit(path, str("\\")); + { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return relative_path; + } + { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return relative_path; + } + return WindowsPath{ + path.slice(0, it.index), + WindowsPathKindNetworkShare, + isAbsoluteWindows(path), + }; + } + } + return relative_path; +} + +// Ported from std/os/path.zig +static uint8_t asciiUpper(uint8_t byte) { + if (byte >= 'a' && byte <= 'z') { + return 'A' + (byte - 'a'); + } + return byte; +} + +// Ported from std/os/path.zig +static bool asciiEqlIgnoreCase(Slice<uint8_t> s1, Slice<uint8_t> s2) { + if (s1.len != s2.len) + return false; + for (size_t i = 0; i < s1.len; i += 1) { + if (asciiUpper(s1.ptr[i]) != asciiUpper(s2.ptr[i])) + return false; + } + return true; +} + +// Ported from std/os/path.zig +static bool compareDiskDesignators(WindowsPathKind kind, Slice<uint8_t> p1, Slice<uint8_t> p2) { + switch (kind) { + case WindowsPathKindNone: + assert(p1.len == 0); + assert(p2.len == 0); + return true; + case WindowsPathKindDrive: + return asciiUpper(p1.ptr[0]) == asciiUpper(p2.ptr[0]); + case WindowsPathKindNetworkShare: + uint8_t sep1 = p1.ptr[0]; + uint8_t sep2 = p2.ptr[0]; + + SplitIterator it1 = memSplit(p1, {&sep1, 1}); + SplitIterator it2 = memSplit(p2, {&sep2, 1}); + + // TODO ASCII is wrong, we actually need full unicode support to compare paths. + return asciiEqlIgnoreCase(SplitIterator_next(&it1).value, SplitIterator_next(&it2).value) && + asciiEqlIgnoreCase(SplitIterator_next(&it1).value, SplitIterator_next(&it2).value); + } + zig_unreachable(); +} + +// Ported from std/os/path.zig +static Buf os_path_resolve_windows(Buf **paths_ptr, size_t paths_len) { + if (paths_len == 0) { + Buf cwd = BUF_INIT; + int err; + if ((err = os_get_cwd(&cwd))) { + zig_panic("get cwd failed"); + } + return cwd; + } + + // determine which disk designator we will result with, if any + char result_drive_buf[3] = {'_', ':', '\0'}; // 0 needed for strlen later + Slice<uint8_t> result_disk_designator = str(""); + WindowsPathKind have_drive_kind = WindowsPathKindNone; + bool have_abs_path = false; + size_t first_index = 0; + size_t max_size = 0; + for (size_t i = 0; i < paths_len; i += 1) { + Slice<uint8_t> p = buf_to_slice(paths_ptr[i]); + WindowsPath parsed = windowsParsePath(p); + if (parsed.is_abs) { + have_abs_path = true; + first_index = i; + max_size = result_disk_designator.len; + } + switch (parsed.kind) { + case WindowsPathKindDrive: + result_drive_buf[0] = asciiUpper(parsed.disk_designator.ptr[0]); + result_disk_designator = str(result_drive_buf); + have_drive_kind = WindowsPathKindDrive; + break; + case WindowsPathKindNetworkShare: + result_disk_designator = parsed.disk_designator; + have_drive_kind = WindowsPathKindNetworkShare; + break; + case WindowsPathKindNone: + break; + } + max_size += p.len + 1; + } + + // if we will result with a disk designator, loop again to determine + // which is the last time the disk designator is absolutely specified, if any + // and count up the max bytes for paths related to this disk designator + if (have_drive_kind != WindowsPathKindNone) { + have_abs_path = false; + first_index = 0; + max_size = result_disk_designator.len; + bool correct_disk_designator = false; + + for (size_t i = 0; i < paths_len; i += 1) { + Slice<uint8_t> p = buf_to_slice(paths_ptr[i]); + WindowsPath parsed = windowsParsePath(p); + if (parsed.kind != WindowsPathKindNone) { + if (parsed.kind == have_drive_kind) { + correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); + } else { + continue; + } + } + if (!correct_disk_designator) { + continue; + } + if (parsed.is_abs) { + first_index = i; + max_size = result_disk_designator.len; + have_abs_path = true; + } + max_size += p.len + 1; + } + } + + // Allocate result and fill in the disk designator, calling getCwd if we have to. + Slice<uint8_t> result; + size_t result_index = 0; + + if (have_abs_path) { + switch (have_drive_kind) { + case WindowsPathKindDrive: { + result = Slice<uint8_t>::alloc(max_size); + + memCopy(result, result_disk_designator); + result_index += result_disk_designator.len; + break; + } + case WindowsPathKindNetworkShare: { + result = Slice<uint8_t>::alloc(max_size); + SplitIterator it = memSplit(buf_to_slice(paths_ptr[first_index]), str("/\\")); + Slice<uint8_t> server_name = SplitIterator_next(&it).value; + Slice<uint8_t> other_name = SplitIterator_next(&it).value; + + result.ptr[result_index] = '\\'; + result_index += 1; + result.ptr[result_index] = '\\'; + result_index += 1; + memCopy(result.sliceFrom(result_index), server_name); + result_index += server_name.len; + result.ptr[result_index] = '\\'; + result_index += 1; + memCopy(result.sliceFrom(result_index), other_name); + result_index += other_name.len; + + result_disk_designator = result.slice(0, result_index); + break; + } + case WindowsPathKindNone: { + Buf cwd = BUF_INIT; + int err; + if ((err = os_get_cwd(&cwd))) { + zig_panic("get cwd failed"); + } + WindowsPath parsed_cwd = windowsParsePath(buf_to_slice(&cwd)); + result = Slice<uint8_t>::alloc(max_size + parsed_cwd.disk_designator.len + 1); + memCopy(result, parsed_cwd.disk_designator); + result_index += parsed_cwd.disk_designator.len; + result_disk_designator = result.slice(0, parsed_cwd.disk_designator.len); + if (parsed_cwd.kind == WindowsPathKindDrive) { + result.ptr[0] = asciiUpper(result.ptr[0]); + } + have_drive_kind = parsed_cwd.kind; + break; + } + } + } else { + // TODO call get cwd for the result_disk_designator instead of the global one + Buf cwd = BUF_INIT; + int err; + if ((err = os_get_cwd(&cwd))) { + zig_panic("get cwd failed"); + } + result = Slice<uint8_t>::alloc(max_size + buf_len(&cwd) + 1); + + memCopy(result, buf_to_slice(&cwd)); + result_index += buf_len(&cwd); + WindowsPath parsed_cwd = windowsParsePath(result.slice(0, result_index)); + result_disk_designator = parsed_cwd.disk_designator; + if (parsed_cwd.kind == WindowsPathKindDrive) { + result.ptr[0] = asciiUpper(result.ptr[0]); + } + have_drive_kind = parsed_cwd.kind; + } + + // Now we know the disk designator to use, if any, and what kind it is. And our result + // is big enough to append all the paths to. + bool correct_disk_designator = true; + for (size_t i = 0; i < paths_len; i += 1) { + Slice<uint8_t> p = buf_to_slice(paths_ptr[i]); + WindowsPath parsed = windowsParsePath(p); + + if (parsed.kind != WindowsPathKindNone) { + if (parsed.kind == have_drive_kind) { + correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); + } else { + continue; + } + } + if (!correct_disk_designator) { + continue; + } + SplitIterator it = memSplit(p.sliceFrom(parsed.disk_designator.len), str("/\\")); + while (true) { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) break; + Slice<uint8_t> component = opt_component.value; + if (memEql(component, str("."))) { + continue; + } else if (memEql(component, str(".."))) { + while (true) { + if (result_index == 0 || result_index == result_disk_designator.len) + break; + result_index -= 1; + if (result.ptr[result_index] == '\\' || result.ptr[result_index] == '/') + break; + } + } else { + result.ptr[result_index] = '\\'; + result_index += 1; + memCopy(result.sliceFrom(result_index), component); + result_index += component.len; + } + } + } + + if (result_index == result_disk_designator.len) { + result.ptr[result_index] = '\\'; + result_index += 1; + } + + Buf return_value = BUF_INIT; + buf_init_from_mem(&return_value, (char *)result.ptr, result_index); + return return_value; +} +#endif + +#if defined(ZIG_OS_POSIX) +// Ported from std/os/path.zig +static Buf os_path_resolve_posix(Buf **paths_ptr, size_t paths_len) { + if (paths_len == 0) { + Buf cwd = BUF_INIT; + int err; + if ((err = os_get_cwd(&cwd))) { + zig_panic("get cwd failed"); + } + return cwd; + } + + size_t first_index = 0; + bool have_abs = false; + size_t max_size = 0; + for (size_t i = 0; i < paths_len; i += 1) { + Buf *p = paths_ptr[i]; + if (os_path_is_absolute(p)) { + first_index = i; + have_abs = true; + max_size = 0; + } + max_size += buf_len(p) + 1; + } + + uint8_t *result_ptr; + size_t result_len; + size_t result_index = 0; + + if (have_abs) { + result_len = max_size; + result_ptr = heap::c_allocator.allocate_nonzero<uint8_t>(result_len); + } else { + Buf cwd = BUF_INIT; + int err; + if ((err = os_get_cwd(&cwd))) { + zig_panic("get cwd failed"); + } + result_len = max_size + buf_len(&cwd) + 1; + result_ptr = heap::c_allocator.allocate_nonzero<uint8_t>(result_len); + memcpy(result_ptr, buf_ptr(&cwd), buf_len(&cwd)); + result_index += buf_len(&cwd); + } + + for (size_t i = first_index; i < paths_len; i += 1) { + Buf *p = paths_ptr[i]; + SplitIterator it = memSplit(buf_to_slice(p), str("/")); + while (true) { + Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) break; + Slice<uint8_t> component = opt_component.value; + + if (memEql<uint8_t>(component, str("."))) { + continue; + } else if (memEql<uint8_t>(component, str(".."))) { + while (true) { + if (result_index == 0) + break; + result_index -= 1; + if (result_ptr[result_index] == '/') + break; + } + } else { + result_ptr[result_index] = '/'; + result_index += 1; + memcpy(result_ptr + result_index, component.ptr, component.len); + result_index += component.len; + } + } + } + + if (result_index == 0) { + result_ptr[0] = '/'; + result_index += 1; + } + + Buf return_value = BUF_INIT; + buf_init_from_mem(&return_value, (char *)result_ptr, result_index); + return return_value; +} +#endif + +// Ported from std/os/path.zig +Buf os_path_resolve(Buf **paths_ptr, size_t paths_len) { +#if defined(ZIG_OS_WINDOWS) + return os_path_resolve_windows(paths_ptr, paths_len); +#elif defined(ZIG_OS_POSIX) + return os_path_resolve_posix(paths_ptr, paths_len); +#else +#error "missing os_path_resolve implementation" +#endif +} + +Error os_fetch_file(FILE *f, Buf *out_buf) { + static const ssize_t buf_size = 0x2000; + buf_resize(out_buf, buf_size); + ssize_t actual_buf_len = 0; + + for (;;) { + size_t amt_read = fread(buf_ptr(out_buf) + actual_buf_len, 1, buf_size, f); + actual_buf_len += amt_read; + + if (amt_read != buf_size) { + if (feof(f)) { + buf_resize(out_buf, actual_buf_len); + return ErrorNone; + } else { + return ErrorFileSystem; + } + } + + buf_resize(out_buf, actual_buf_len + buf_size); + } + zig_unreachable(); +} + +Error os_file_exists(Buf *full_path, bool *result) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + *result = GetFileAttributesW(&path_space.data.items[0]) != INVALID_FILE_ATTRIBUTES; + return ErrorNone; +#else + *result = access(buf_ptr(full_path), F_OK) != -1; + return ErrorNone; +#endif +} + +#if defined(ZIG_OS_POSIX) +static Error os_exec_process_posix(ZigList<const char *> &args, + Termination *term, Buf *out_stderr, Buf *out_stdout) +{ + int stdin_pipe[2]; + int stdout_pipe[2]; + int stderr_pipe[2]; + int err_pipe[2]; + + int err; + if ((err = pipe(stdin_pipe))) + zig_panic("pipe failed"); + if ((err = pipe(stdout_pipe))) + zig_panic("pipe failed"); + if ((err = pipe(stderr_pipe))) + zig_panic("pipe failed"); + if ((err = pipe(err_pipe))) + zig_panic("pipe failed"); + + pid_t pid = fork(); + if (pid == -1) + zig_panic("fork failed: %s", strerror(errno)); + if (pid == 0) { + // child + if (dup2(stdin_pipe[0], STDIN_FILENO) == -1) + zig_panic("dup2 failed"); + + if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) + zig_panic("dup2 failed"); + + if (dup2(stderr_pipe[1], STDERR_FILENO) == -1) + zig_panic("dup2 failed"); + + const char **argv = heap::c_allocator.allocate<const char *>(args.length + 1); + argv[args.length] = nullptr; + for (size_t i = 0; i < args.length; i += 1) { + argv[i] = args.at(i); + } + execvp(argv[0], const_cast<char * const *>(argv)); + Error report_err = ErrorUnexpected; + if (errno == ENOENT) { + report_err = ErrorFileNotFound; + } + if (write(err_pipe[1], &report_err, sizeof(Error)) == -1) { + zig_panic("write failed"); + } + exit(1); + } else { + // parent + close(stdin_pipe[0]); + close(stdin_pipe[1]); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + int status; + waitpid(pid, &status, 0); + populate_termination(term, status); + + FILE *stdout_f = fdopen(stdout_pipe[0], "rb"); + FILE *stderr_f = fdopen(stderr_pipe[0], "rb"); + Error err1 = os_fetch_file(stdout_f, out_stdout); + Error err2 = os_fetch_file(stderr_f, out_stderr); + + fclose(stdout_f); + fclose(stderr_f); + + if (err1) return err1; + if (err2) return err2; + + Error child_err = ErrorNone; + if (write(err_pipe[1], &child_err, sizeof(Error)) == -1) { + zig_panic("write failed"); + } + close(err_pipe[1]); + if (read(err_pipe[0], &child_err, sizeof(Error)) == -1) { + zig_panic("write failed"); + } + close(err_pipe[0]); + return child_err; + } +} +#endif + +#if defined(ZIG_OS_WINDOWS) + +//static void win32_panic(const char *str) { +// DWORD err = GetLastError(); +// LPSTR messageBuffer = nullptr; +// FormatMessageA( +// FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, +// NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); +// zig_panic(str, messageBuffer); +// LocalFree(messageBuffer); +//} + +static Error os_exec_process_windows(ZigList<const char *> &args, + Termination *term, Buf *out_stderr, Buf *out_stdout) +{ + Buf command_line = BUF_INIT; + os_windows_create_command_line(&command_line, args); + + HANDLE g_hChildStd_IN_Rd = NULL; + HANDLE g_hChildStd_IN_Wr = NULL; + HANDLE g_hChildStd_OUT_Rd = NULL; + HANDLE g_hChildStd_OUT_Wr = NULL; + HANDLE g_hChildStd_ERR_Rd = NULL; + HANDLE g_hChildStd_ERR_Wr = NULL; + + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { + zig_panic("StdoutRd CreatePipe"); + } + + if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + zig_panic("Stdout SetHandleInformation"); + } + + if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0)) { + zig_panic("stderr CreatePipe"); + } + + if (!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { + zig_panic("stderr SetHandleInformation"); + } + + if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) { + zig_panic("Stdin CreatePipe"); + } + + if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) { + zig_panic("Stdin SetHandleInformation"); + } + + + PROCESS_INFORMATION piProcInfo = {0}; + STARTUPINFO siStartInfo = {0}; + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = g_hChildStd_ERR_Wr; + siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; + siStartInfo.hStdInput = g_hChildStd_IN_Rd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + const char *exe = args.at(0); + BOOL success = CreateProcess(exe, buf_ptr(&command_line), nullptr, nullptr, TRUE, 0, nullptr, nullptr, + &siStartInfo, &piProcInfo); + + if (!success) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + return ErrorFileNotFound; + } + zig_panic("CreateProcess failed. exe: %s command_line: %s", exe, buf_ptr(&command_line)); + } + + if (!CloseHandle(g_hChildStd_IN_Wr)) { + zig_panic("stdinwr closehandle"); + } + + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_ERR_Wr); + CloseHandle(g_hChildStd_OUT_Wr); + + static const size_t BUF_SIZE = 4 * 1024; + { + DWORD dwRead; + char chBuf[BUF_SIZE]; + + buf_resize(out_stdout, 0); + for (;;) { + success = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUF_SIZE, &dwRead, NULL); + if (!success || dwRead == 0) break; + + buf_append_mem(out_stdout, chBuf, dwRead); + } + CloseHandle(g_hChildStd_OUT_Rd); + } + { + DWORD dwRead; + char chBuf[BUF_SIZE]; + + buf_resize(out_stderr, 0); + for (;;) { + success = ReadFile( g_hChildStd_ERR_Rd, chBuf, BUF_SIZE, &dwRead, NULL); + if (!success || dwRead == 0) break; + + buf_append_mem(out_stderr, chBuf, dwRead); + } + CloseHandle(g_hChildStd_ERR_Rd); + } + + WaitForSingleObject(piProcInfo.hProcess, INFINITE); + + DWORD exit_code; + if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) { + zig_panic("GetExitCodeProcess failed"); + } + term->how = TerminationIdClean; + term->code = exit_code; + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + + return ErrorNone; +} +#endif + +Error os_execv(const char *exe, const char **argv) { +#if defined(ZIG_OS_WINDOWS) + return ErrorUnsupportedOperatingSystem; +#else + execv(exe, (char *const *)argv); + switch (errno) { + case ENOMEM: + return ErrorSystemResources; + case EIO: + return ErrorFileSystem; + default: + return ErrorUnexpected; + } +#endif +} + +Error os_exec_process(ZigList<const char *> &args, + Termination *term, Buf *out_stderr, Buf *out_stdout) +{ +#if defined(ZIG_OS_WINDOWS) + return os_exec_process_windows(args, term, out_stderr, out_stdout); +#elif defined(ZIG_OS_POSIX) + return os_exec_process_posix(args, term, out_stderr, out_stdout); +#else +#error "missing os_exec_process implementation" +#endif +} + +Error os_write_file(Buf *full_path, Buf *contents) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + FILE *f = _wfopen(&path_space.data.items[0], L"wb"); +#else + FILE *f = fopen(buf_ptr(full_path), "wb"); +#endif + if (!f) { + zig_panic("os_write_file failed for %s", buf_ptr(full_path)); + } + size_t amt_written = fwrite(buf_ptr(contents), 1, buf_len(contents), f); + if (amt_written != (size_t)buf_len(contents)) + zig_panic("write failed: %s", strerror(errno)); + if (fclose(f)) + zig_panic("close failed"); + return ErrorNone; +} + +static Error copy_open_files(FILE *src_f, FILE *dest_f) { + static const size_t buf_size = 2048; + char buf[buf_size]; + for (;;) { + size_t amt_read = fread(buf, 1, buf_size, src_f); + if (amt_read != buf_size) { + if (ferror(src_f)) { + return ErrorFileSystem; + } + } + size_t amt_written = fwrite(buf, 1, amt_read, dest_f); + if (amt_written != amt_read) { + return ErrorFileSystem; + } + if (feof(src_f)) { + return ErrorNone; + } + } +} + +Error os_dump_file(Buf *src_path, FILE *dest_file) { + Error err; + +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + FILE *src_f = _wfopen(&path_space.data.items[0], L"rb"); +#else + FILE *src_f = fopen(buf_ptr(src_path), "rb"); +#endif + if (!src_f) { + int err = errno; + if (err == ENOENT) { + return ErrorFileNotFound; + } else if (err == EACCES || err == EPERM) { + return ErrorAccess; + } else { + return ErrorFileSystem; + } + } + copy_open_files(src_f, dest_file); + if ((err = copy_open_files(src_f, dest_file))) { + fclose(src_f); + return err; + } + + fclose(src_f); + return ErrorNone; +} + +#if defined(ZIG_OS_WINDOWS) +static void windows_filetime_to_os_timestamp(FILETIME *ft, OsTimeStamp *mtime) { + mtime->sec = (((ULONGLONG) ft->dwHighDateTime) << 32) + ft->dwLowDateTime; + mtime->nsec = 0; +} +static FILETIME windows_os_timestamp_to_filetime(OsTimeStamp mtime) { + FILETIME result; + result.dwHighDateTime = mtime.sec >> 32; + result.dwLowDateTime = mtime.sec; + return result; +} +#endif + +static Error set_file_times(OsFile file, OsTimeStamp ts) { +#if defined(ZIG_OS_WINDOWS) + FILETIME ft = windows_os_timestamp_to_filetime(ts); + if (SetFileTime(file, nullptr, &ft, &ft) == 0) { + return ErrorUnexpected; + } + return ErrorNone; +#else + struct timespec times[2] = { + { (time_t)ts.sec, (long)ts.nsec }, + { (time_t)ts.sec, (long)ts.nsec }, + }; + if (futimens(file, times) == -1) { + switch (errno) { + case EBADF: + zig_panic("futimens EBADF"); + default: + return ErrorUnexpected; + } + } + return ErrorNone; +#endif +} + +Error os_update_file(Buf *src_path, Buf *dst_path) { + Error err; + + OsFile src_file; + OsFileAttr src_attr; + if ((err = os_file_open_r(src_path, &src_file, &src_attr))) { + return err; + } + + OsFile dst_file; + OsFileAttr dst_attr; + if ((err = os_file_open_w(dst_path, &dst_file, &dst_attr, src_attr.mode))) { + os_file_close(&src_file); + return err; + } + + if (src_attr.size == dst_attr.size && + src_attr.mode == dst_attr.mode && + src_attr.mtime.sec == dst_attr.mtime.sec && + src_attr.mtime.nsec == dst_attr.mtime.nsec) + { + os_file_close(&src_file); + os_file_close(&dst_file); + return ErrorNone; + } +#if defined(ZIG_OS_WINDOWS) + if (SetEndOfFile(dst_file) == 0) { + return ErrorUnexpected; + } +#else + if (ftruncate(dst_file, 0) == -1) { + return ErrorUnexpected; + } +#endif +#if defined(ZIG_OS_WINDOWS) + FILE *src_libc_file = _fdopen(_open_osfhandle((intptr_t)src_file, _O_RDONLY), "rb"); + FILE *dst_libc_file = _fdopen(_open_osfhandle((intptr_t)dst_file, 0), "wb"); +#else + FILE *src_libc_file = fdopen(src_file, "rb"); + FILE *dst_libc_file = fdopen(dst_file, "wb"); +#endif + assert(src_libc_file); + assert(dst_libc_file); + + if ((err = copy_open_files(src_libc_file, dst_libc_file))) { + fclose(src_libc_file); + fclose(dst_libc_file); + return err; + } + if (fflush(dst_libc_file) == -1) { + return ErrorUnexpected; + } + err = set_file_times(dst_file, src_attr.mtime); + fclose(src_libc_file); + fclose(dst_libc_file); + return err; +} + +Error os_copy_file(Buf *src_path, Buf *dest_path) { +#if defined(ZIG_OS_WINDOWS) + PathSpace src_path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + FILE *src_f = _wfopen(&src_path_space.data.items[0], L"rb"); +#else + FILE *src_f = fopen(buf_ptr(src_path), "rb"); +#endif + if (!src_f) { + int err = errno; + if (err == ENOENT) { + return ErrorFileNotFound; + } else if (err == EACCES || err == EPERM) { + return ErrorAccess; + } else { + return ErrorFileSystem; + } + } +#if defined(ZIG_OS_WINDOWS) + PathSpace dest_path_space = slice_to_prefixed_file_w(buf_to_slice(dest_path)); + FILE *dest_f = _wfopen(&dest_path_space.data.items[0], L"wb"); +#else + FILE *dest_f = fopen(buf_ptr(dest_path), "wb"); +#endif + if (!dest_f) { + int err = errno; + if (err == ENOENT) { + fclose(src_f); + return ErrorFileNotFound; + } else if (err == EACCES || err == EPERM) { + fclose(src_f); + return ErrorAccess; + } else { + fclose(src_f); + return ErrorFileSystem; + } + } + Error err = copy_open_files(src_f, dest_f); + fclose(src_f); + fclose(dest_f); + return err; +} + +Error os_fetch_file_path(Buf *full_path, Buf *out_contents) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + FILE *f = _wfopen(&path_space.data.items[0], L"rb"); +#else + FILE *f = fopen(buf_ptr(full_path), "rb"); +#endif + if (!f) { + switch (errno) { + case EACCES: + return ErrorAccess; + case EINTR: + return ErrorInterrupted; + case EINVAL: + return ErrorInvalidFilename; + case ENFILE: + case ENOMEM: + return ErrorSystemResources; + case ENOENT: + return ErrorFileNotFound; + default: + return ErrorFileSystem; + } + } + Error result = os_fetch_file(f, out_contents); + fclose(f); + return result; +} + +Error os_get_cwd(Buf *out_cwd) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space; + if (GetCurrentDirectoryW(PATH_MAX_WIDE, &path_space.data.items[0]) == 0) { + zig_panic("GetCurrentDirectory failed"); + } + utf16le_ptr_to_utf8(out_cwd, &path_space.data.items[0]); + return ErrorNone; +#elif defined(ZIG_OS_POSIX) + char buf[PATH_MAX]; + char *res = getcwd(buf, PATH_MAX); + if (res == nullptr) { + zig_panic("unable to get cwd: %s", strerror(errno)); + } + buf_init_from_str(out_cwd, res); + return ErrorNone; +#else +#error "missing os_get_cwd implementation" +#endif +} + +#if defined(ZIG_OS_WINDOWS) +#define is_wprefix(s, prefix) \ + (wcsncmp((s), (prefix), sizeof(prefix) / sizeof(WCHAR) - 1) == 0) +static bool is_stderr_cyg_pty(void) { + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + if (stderr_handle == INVALID_HANDLE_VALUE) + return false; + + const int size = sizeof(FILE_NAME_INFO) + sizeof(WCHAR) * MAX_PATH; + FILE_NAME_INFO *nameinfo; + WCHAR *p = NULL; + + // Cygwin/msys's pty is a pipe. + if (GetFileType(stderr_handle) != FILE_TYPE_PIPE) { + return 0; + } + nameinfo = reinterpret_cast<FILE_NAME_INFO *>(heap::c_allocator.allocate<char>(size)); + if (nameinfo == NULL) { + return 0; + } + // Check the name of the pipe: + // '\{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master' + if (GetFileInformationByHandleEx(stderr_handle, FileNameInfo, nameinfo, size)) { + nameinfo->FileName[nameinfo->FileNameLength / sizeof(WCHAR)] = L'\0'; + p = nameinfo->FileName; + if (is_wprefix(p, L"\\cygwin-")) { /* Cygwin */ + p += 8; + } else if (is_wprefix(p, L"\\msys-")) { /* MSYS and MSYS2 */ + p += 6; + } else { + p = NULL; + } + if (p != NULL) { + while (*p && isxdigit(*p)) /* Skip 16-digit hexadecimal. */ + ++p; + if (is_wprefix(p, L"-pty")) { + p += 4; + } else { + p = NULL; + } + } + if (p != NULL) { + while (*p && isdigit(*p)) /* Skip pty number. */ + ++p; + if (is_wprefix(p, L"-from-master")) { + //p += 12; + } else if (is_wprefix(p, L"-to-master")) { + //p += 10; + } else { + p = NULL; + } + } + } + heap::c_allocator.deallocate(reinterpret_cast<char *>(nameinfo), size); + return (p != NULL); +} +#endif + +bool os_stderr_tty(void) { +#if defined(ZIG_OS_WINDOWS) + return _isatty(_fileno(stderr)) != 0 || is_stderr_cyg_pty(); +#elif defined(ZIG_OS_POSIX) + return isatty(STDERR_FILENO) != 0; +#else +#error "missing os_stderr_tty implementation" +#endif +} + +Error os_delete_file(Buf *path) { + if (remove(buf_ptr(path))) { + return ErrorFileSystem; + } else { + return ErrorNone; + } +} + +Error os_rename(Buf *src_path, Buf *dest_path) { + if (buf_eql_buf(src_path, dest_path)) { + return ErrorNone; + } +#if defined(ZIG_OS_WINDOWS) + PathSpace src_path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + PathSpace dest_path_space = slice_to_prefixed_file_w(buf_to_slice(dest_path)); + if (!MoveFileExW(&src_path_space.data.items[0], &dest_path_space.data.items[0], MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + return ErrorFileSystem; + } +#else + if (rename(buf_ptr(src_path), buf_ptr(dest_path)) == -1) { + return ErrorFileSystem; + } +#endif + return ErrorNone; +} + +OsTimeStamp os_timestamp_calendar(void) { + OsTimeStamp result; +#if defined(ZIG_OS_WINDOWS) + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + windows_filetime_to_os_timestamp(&ft, &result); +#elif defined(__MACH__) + mach_timespec_t mts; + + kern_return_t err = clock_get_time(macos_calendar_clock, &mts); + assert(!err); + + result.sec = mts.tv_sec; + result.nsec = mts.tv_nsec; +#else + struct timespec tms; + clock_gettime(CLOCK_REALTIME, &tms); + + result.sec = tms.tv_sec; + result.nsec = tms.tv_nsec; +#endif + return result; +} + +OsTimeStamp os_timestamp_monotonic(void) { + OsTimeStamp result; +#if defined(ZIG_OS_WINDOWS) + uint64_t counts; + QueryPerformanceCounter((LARGE_INTEGER*)&counts); + result.sec = counts / windows_perf_freq; + result.nsec = (counts % windows_perf_freq) * 1000000000u / windows_perf_freq; +#elif defined(__MACH__) + mach_timespec_t mts; + + kern_return_t err = clock_get_time(macos_monotonic_clock, &mts); + assert(!err); + + result.sec = mts.tv_sec; + result.nsec = mts.tv_nsec; +#else + struct timespec tms; + clock_gettime(CLOCK_MONOTONIC, &tms); + + result.sec = tms.tv_sec; + result.nsec = tms.tv_nsec; +#endif + return result; +} + +Error os_make_path(Buf *path) { + Buf resolved_path = os_path_resolve(&path, 1); + + size_t end_index = buf_len(&resolved_path); + Error err; + while (true) { + if ((err = os_make_dir(buf_slice(&resolved_path, 0, end_index)))) { + if (err == ErrorPathAlreadyExists) { + if (end_index == buf_len(&resolved_path)) + return ErrorNone; + } else if (err == ErrorFileNotFound) { + // march end_index backward until next path component + while (true) { + end_index -= 1; + if (os_is_sep(buf_ptr(&resolved_path)[end_index])) + break; + } + continue; + } else { + return err; + } + } + if (end_index == buf_len(&resolved_path)) + return ErrorNone; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == buf_len(&resolved_path) || os_is_sep(buf_ptr(&resolved_path)[end_index])) + break; + } + } + return ErrorNone; +} + +Error os_make_dir(Buf *path) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(path)); + if (memEql(buf_to_slice(path), str("C:\\dev\\tést"))) { + for (size_t i = 0; i < path_space.len; i++) { + fprintf(stderr, "%d ", path_space.data.items[i]); + } + fprintf(stderr, "\n"); + } + + if (!CreateDirectoryW(&path_space.data.items[0], NULL)) { + if (GetLastError() == ERROR_ALREADY_EXISTS) + return ErrorPathAlreadyExists; + if (GetLastError() == ERROR_PATH_NOT_FOUND) + return ErrorFileNotFound; + if (GetLastError() == ERROR_ACCESS_DENIED) + return ErrorAccess; + return ErrorUnexpected; + } + return ErrorNone; +#else + if (mkdir(buf_ptr(path), 0755) == -1) { + if (errno == EEXIST) + return ErrorPathAlreadyExists; + if (errno == ENOENT) + return ErrorFileNotFound; + if (errno == EACCES) + return ErrorAccess; + return ErrorUnexpected; + } + return ErrorNone; +#endif +} + +static void init_rand() { +#if defined(ZIG_OS_WINDOWS) + char bytes[sizeof(unsigned)]; + unsigned seed; + RtlGenRandom(bytes, sizeof(unsigned)); + memcpy(&seed, bytes, sizeof(unsigned)); + srand(seed); +#elif defined(ZIG_OS_LINUX) + unsigned char *ptr_random = (unsigned char*)getauxval(AT_RANDOM); + unsigned seed; + memcpy(&seed, ptr_random, sizeof(seed)); + srand(seed); +#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) + unsigned seed; + size_t len = sizeof(seed); + int mib[2] = { CTL_KERN, KERN_ARND }; + if (sysctl(mib, 2, &seed, &len, NULL, 0) != 0) { + zig_panic("unable to query random data from sysctl"); + } + srand(seed); +#else + int fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC); + if (fd == -1) { + zig_panic("unable to open /dev/urandom"); + } + char bytes[sizeof(unsigned)]; + ssize_t amt_read; + while ((amt_read = read(fd, bytes, sizeof(unsigned))) == -1) { + if (errno == EINTR) continue; + zig_panic("unable to read /dev/urandom"); + } + if (amt_read != sizeof(unsigned)) { + zig_panic("unable to read enough bytes from /dev/urandom"); + } + close(fd); + unsigned seed; + memcpy(&seed, bytes, sizeof(unsigned)); + srand(seed); +#endif +} + +int os_init(void) { + init_rand(); +#if defined(ZIG_OS_WINDOWS) + _setmode(fileno(stdout), _O_BINARY); + _setmode(fileno(stderr), _O_BINARY); + if (!QueryPerformanceFrequency((LARGE_INTEGER*)&windows_perf_freq)) { + return ErrorSystemResources; + } +#elif defined(__MACH__) + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &macos_monotonic_clock); + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &macos_calendar_clock); +#endif +#if defined(ZIG_OS_POSIX) + // Raise the open file descriptor limit. + // Code lifted from node.js + struct rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) { + // Do a binary search for the limit. + rlim_t min = lim.rlim_cur; + rlim_t max = 1 << 20; + // But if there's a defined upper bound, don't search, just set it. + if (lim.rlim_max != RLIM_INFINITY) { + min = lim.rlim_max; + max = lim.rlim_max; + } + do { + lim.rlim_cur = min + (max - min) / 2; + if (setrlimit(RLIMIT_NOFILE, &lim)) { + max = lim.rlim_cur; + } else { + min = lim.rlim_cur; + } + } while (min + 1 < max); + } +#endif + return 0; +} + +Error os_self_exe_path(Buf *out_path) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space; + DWORD copied_amt = GetModuleFileNameW(nullptr, &path_space.data.items[0], PATH_MAX_WIDE); + if (copied_amt <= 0) { + return ErrorFileNotFound; + } + utf16le_ptr_to_utf8(out_path, &path_space.data.items[0]); + return ErrorNone; + +#elif defined(ZIG_OS_DARWIN) + // How long is the executable's path? + uint32_t u32_len = 0; + int ret1 = _NSGetExecutablePath(nullptr, &u32_len); + assert(ret1 != 0); + + Buf *tmp = buf_alloc_fixed(u32_len); + + // Fill the executable path. + int ret2 = _NSGetExecutablePath(buf_ptr(tmp), &u32_len); + assert(ret2 == 0); + + // According to libuv project, PATH_MAX*2 works around a libc bug where + // the resolved path is sometimes bigger than PATH_MAX. + buf_resize(out_path, PATH_MAX*2); + char *real_path = realpath(buf_ptr(tmp), buf_ptr(out_path)); + if (!real_path) { + buf_init_from_buf(out_path, tmp); + return ErrorNone; + } + + // Resize out_path for the correct length. + buf_resize(out_path, strlen(buf_ptr(out_path))); + + return ErrorNone; +#elif defined(ZIG_OS_LINUX) + buf_resize(out_path, PATH_MAX); + ssize_t amt = readlink("/proc/self/exe", buf_ptr(out_path), buf_len(out_path)); + if (amt == -1) { + return ErrorUnexpected; + } + buf_resize(out_path, amt); + return ErrorNone; +#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_DRAGONFLY) + buf_resize(out_path, PATH_MAX); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + size_t cb = PATH_MAX; + if (sysctl(mib, 4, buf_ptr(out_path), &cb, nullptr, 0) != 0) { + return ErrorUnexpected; + } + buf_resize(out_path, cb - 1); + return ErrorNone; +#elif defined(ZIG_OS_NETBSD) + buf_resize(out_path, PATH_MAX); + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; + size_t cb = PATH_MAX; + if (sysctl(mib, 4, buf_ptr(out_path), &cb, nullptr, 0) != 0) { + return ErrorUnexpected; + } + buf_resize(out_path, cb - 1); + return ErrorNone; +#endif + return ErrorFileNotFound; +} + +#define VT_RED "\x1b[31;1m" +#define VT_GREEN "\x1b[32;1m" +#define VT_CYAN "\x1b[36;1m" +#define VT_WHITE "\x1b[37;1m" +#define VT_BOLD "\x1b[0;1m" +#define VT_RESET "\x1b[0m" + +static void set_color_posix(TermColor color) { + switch (color) { + case TermColorRed: + fprintf(stderr, VT_RED); + break; + case TermColorGreen: + fprintf(stderr, VT_GREEN); + break; + case TermColorCyan: + fprintf(stderr, VT_CYAN); + break; + case TermColorWhite: + fprintf(stderr, VT_WHITE); + break; + case TermColorBold: + fprintf(stderr, VT_BOLD); + break; + case TermColorReset: + fprintf(stderr, VT_RESET); + break; + } +} + + +#if defined(ZIG_OS_WINDOWS) +bool got_orig_console_attrs = false; +WORD original_console_attributes = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; +#endif + +void os_stderr_set_color(TermColor color) { +#if defined(ZIG_OS_WINDOWS) + if (is_stderr_cyg_pty()) { + set_color_posix(color); + return; + } + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + if (stderr_handle == INVALID_HANDLE_VALUE) + zig_panic("unable to get stderr handle"); + fflush(stderr); + + if (!got_orig_console_attrs) { + got_orig_console_attrs = true; + CONSOLE_SCREEN_BUFFER_INFO info; + if (GetConsoleScreenBufferInfo(stderr_handle, &info)) { + original_console_attributes = info.wAttributes; + } + } + + switch (color) { + case TermColorRed: + SetConsoleTextAttribute(stderr_handle, FOREGROUND_RED|FOREGROUND_INTENSITY); + break; + case TermColorGreen: + SetConsoleTextAttribute(stderr_handle, FOREGROUND_GREEN|FOREGROUND_INTENSITY); + break; + case TermColorCyan: + SetConsoleTextAttribute(stderr_handle, FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + break; + case TermColorWhite: + case TermColorBold: + SetConsoleTextAttribute(stderr_handle, + FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + break; + case TermColorReset: + SetConsoleTextAttribute(stderr_handle, original_console_attributes); + break; + } +#else + set_color_posix(color); +#endif +} + +#if defined(ZIG_OS_WINDOWS) +// Ported from std/unicode.zig +struct Utf16LeIterator { + uint8_t *bytes; + size_t i; +}; + +// Ported from std/unicode.zig +static Utf16LeIterator Utf16LeIterator_init(WCHAR *ptr) { + return {(uint8_t*)ptr, 0}; +} + +// Ported from std/unicode.zig +static Optional<uint32_t> Utf16LeIterator_nextCodepoint(Utf16LeIterator *it) { + if (it->bytes[it->i] == 0 && it->bytes[it->i + 1] == 0) + return {}; + uint32_t c0 = ((uint32_t)it->bytes[it->i]) | (((uint32_t)it->bytes[it->i + 1]) << 8); + if ((c0 & ~((uint32_t)0x03ff)) == 0xd800) { + // surrogate pair + it->i += 2; + assert(it->bytes[it->i] != 0 || it->bytes[it->i + 1] != 0); + uint32_t c1 = ((uint32_t)it->bytes[it->i]) | (((uint32_t)it->bytes[it->i + 1]) << 8); + assert((c1 & ~((uint32_t)0x03ff)) == 0xdc00); + it->i += 2; + return Optional<uint32_t>::some(0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff))); + } else { + assert((c0 & ~((uint32_t)0x03ff)) != 0xdc00); + it->i += 2; + return Optional<uint32_t>::some(c0); + } +} + +// Ported from std/unicode.zig +static uint8_t utf8CodepointSequenceLength(uint32_t c) { + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c < 0x10000) return 3; + if (c < 0x110000) return 4; + zig_unreachable(); +} + +// Ported from std.unicode.utf8ByteSequenceLength +static uint8_t utf8ByteSequenceLength(uint8_t first_byte) { + if (first_byte < 0b10000000) return 1; + if ((first_byte & 0b11100000) == 0b11000000) return 2; + if ((first_byte & 0b11110000) == 0b11100000) return 3; + if ((first_byte & 0b11111000) == 0b11110000) return 4; + zig_unreachable(); +} + +// Ported from std/unicode.zig +static size_t utf8Encode(uint32_t c, Slice<uint8_t> out) { + size_t length = utf8CodepointSequenceLength(c); + assert(out.len >= length); + switch (length) { + // The pattern for each is the same + // - Increasing the initial shift by 6 each time + // - Each time after the first shorten the shifted + // value to a max of 0b111111 (63) + case 1: + out.ptr[0] = c; // Can just do 0 + codepoint for initial range + break; + case 2: + out.ptr[0] = 0b11000000 | (c >> 6); + out.ptr[1] = 0b10000000 | (c & 0b111111); + break; + case 3: + assert(!(0xd800 <= c && c <= 0xdfff)); + out.ptr[0] = 0b11100000 | (c >> 12); + out.ptr[1] = 0b10000000 | ((c >> 6) & 0b111111); + out.ptr[2] = 0b10000000 | (c & 0b111111); + break; + case 4: + out.ptr[0] = 0b11110000 | (c >> 18); + out.ptr[1] = 0b10000000 | ((c >> 12) & 0b111111); + out.ptr[2] = 0b10000000 | ((c >> 6) & 0b111111); + out.ptr[3] = 0b10000000 | (c & 0b111111); + break; + default: + zig_unreachable(); + } + return length; +} + +// Ported from std.unicode.utf8Decode2 +static uint32_t utf8Decode2(Slice<uint8_t> bytes) { + assert(bytes.len == 2); + assert((bytes.at(0) & 0b11100000) == 0b11000000); + + uint32_t value = bytes.at(0) & 0b00011111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert(value >= 0x80); + return value; +} + +// Ported from std.unicode.utf8Decode3 +static uint32_t utf8Decode3(Slice<uint8_t> bytes) { + assert(bytes.len == 3); + assert((bytes.at(0) & 0b11110000) == 0b11100000); + + uint32_t value = bytes.at(0) & 0b00001111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert((bytes.at(2) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(2) & 0b00111111; + + assert(value >= 0x80); + assert(value < 0xd800 || value > 0xdfff); + return value; +} + +// Ported from std.unicode.utf8Decode4 +static uint32_t utf8Decode4(Slice<uint8_t> bytes) { + assert(bytes.len == 4); + assert((bytes.at(0) & 0b11111000) == 0b11110000); + + uint32_t value = bytes.at(0) & 0b00000111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert((bytes.at(2) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(2) & 0b00111111; + + assert((bytes.at(3) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(3) & 0b00111111; + + assert(value >= 0x10000 && value <= 0x10FFFF); + return value; +} + +// Ported from std.unicode.utf8Decode +static uint32_t utf8Decode(Slice<uint8_t> bytes) { + switch (bytes.len) { + case 1: + return bytes.at(0); + break; + case 2: + return utf8Decode2(bytes); + break; + case 3: + return utf8Decode3(bytes); + break; + case 4: + return utf8Decode4(bytes); + break; + default: + zig_unreachable(); + } +} +// Ported from std.unicode.utf16leToUtf8Alloc +static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le) { + // optimistically guess that it will all be ascii. + buf_resize(out, 0); + size_t out_index = 0; + Utf16LeIterator it = Utf16LeIterator_init(utf16le); + for (;;) { + Optional<uint32_t> opt_codepoint = Utf16LeIterator_nextCodepoint(&it); + if (!opt_codepoint.is_some) break; + uint32_t codepoint = opt_codepoint.value; + + size_t utf8_len = utf8CodepointSequenceLength(codepoint); + buf_resize(out, buf_len(out) + utf8_len); + utf8Encode(codepoint, {(uint8_t*)buf_ptr(out)+out_index, buf_len(out)-out_index}); + out_index += utf8_len; + } +} + +// Ported from std.unicode.utf8ToUtf16Le +static size_t utf8_to_utf16le(WCHAR *utf16_le, Slice<uint8_t> utf8) { + size_t dest_i = 0; + size_t src_i = 0; + while (src_i < utf8.len) { + uint8_t n = utf8ByteSequenceLength(utf8.at(src_i)); + size_t next_src_i = src_i + n; + uint32_t codepoint = utf8Decode(utf8.slice(src_i, next_src_i)); + if (codepoint < 0x10000) { + utf16_le[dest_i] = codepoint; + dest_i += 1; + } else { + WCHAR high = ((codepoint - 0x10000) >> 10) + 0xD800; + WCHAR low = (codepoint & 0x3FF) + 0xDC00; + utf16_le[dest_i] = high; + utf16_le[dest_i + 1] = low; + dest_i += 2; + } + src_i = next_src_i; + } + return dest_i; +} + +// Ported from std.os.windows.sliceToPrefixedFileW +PathSpace slice_to_prefixed_file_w(Slice<uint8_t> path) { + PathSpace path_space; + for (size_t idx = 0; idx < path.len; idx++) { + assert(path.ptr[idx] != '*' && path.ptr[idx] != '?' && path.ptr[idx] != '"' && + path.ptr[idx] != '<' && path.ptr[idx] != '>' && path.ptr[idx] != '|'); + } + + size_t start_index; + if (memStartsWith(path, str("\\?")) || !isAbsoluteWindows(path)) { + start_index = 0; + } else { + static WCHAR prefix[4] = { u'\\', u'?', u'?', u'\\' }; + memCopy(path_space.data.slice(), Slice<WCHAR> { prefix, 4 }); + start_index = 4; + } + + path_space.len = start_index + utf8_to_utf16le(path_space.data.slice().sliceFrom(start_index).ptr, path); + assert(path_space.len <= path_space.data.len); + + Slice<WCHAR> path_slice = path_space.data.slice().slice(0, path_space.len); + for (size_t elem_idx = 0; elem_idx < path_slice.len; elem_idx += 1) { + if (path_slice.at(elem_idx) == '/') { + path_slice.at(elem_idx) = '\\'; + } + } + + path_space.data.items[path_space.len] = 0; + return path_space; +} +#endif + +// Ported from std.os.getAppDataDir +Error os_get_app_data_dir(Buf *out_path, const char *appname) { +#if defined(ZIG_OS_WINDOWS) + WCHAR *dir_path_ptr; + switch (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &dir_path_ptr)) { + case S_OK: + // defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); + utf16le_ptr_to_utf8(out_path, dir_path_ptr); + CoTaskMemFree(dir_path_ptr); + buf_appendf(out_path, "\\%s", appname); + return ErrorNone; + case E_OUTOFMEMORY: + return ErrorNoMem; + default: + return ErrorUnexpected; + } + zig_unreachable(); +#elif defined(ZIG_OS_DARWIN) + const char *home_dir = getenv("HOME"); + if (home_dir == nullptr) { + // TODO use /etc/passwd + return ErrorFileNotFound; + } + buf_resize(out_path, 0); + buf_appendf(out_path, "%s/Library/Application Support/%s", home_dir, appname); + return ErrorNone; +#elif defined(ZIG_OS_POSIX) + const char *cache_dir = getenv("XDG_CACHE_HOME"); + if (cache_dir == nullptr) { + cache_dir = getenv("HOME"); + if (cache_dir == nullptr) { + // TODO use /etc/passwd + return ErrorFileNotFound; + } + if (cache_dir[0] == 0) { + return ErrorFileNotFound; + } + buf_init_from_str(out_path, cache_dir); + if (buf_ptr(out_path)[buf_len(out_path) - 1] != '/') { + buf_append_char(out_path, '/'); + } + buf_appendf(out_path, ".cache/%s", appname); + } else { + if (cache_dir[0] == 0) { + return ErrorFileNotFound; + } + buf_init_from_str(out_path, cache_dir); + if (buf_ptr(out_path)[buf_len(out_path) - 1] != '/') { + buf_append_char(out_path, '/'); + } + buf_appendf(out_path, "%s", appname); + } + return ErrorNone; +#endif +} + +#if defined(ZIG_OS_LINUX) || defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) +static int self_exe_shared_libs_callback(struct dl_phdr_info *info, size_t size, void *data) { + ZigList<Buf *> *libs = reinterpret_cast< ZigList<Buf *> *>(data); + if (info->dlpi_name[0] == '/') { + libs->append(buf_create_from_str(info->dlpi_name)); + } + return 0; +} +#endif + +Error os_self_exe_shared_libs(ZigList<Buf *> &paths) { +#if defined(ZIG_OS_LINUX) || defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) + paths.resize(0); + dl_iterate_phdr(self_exe_shared_libs_callback, &paths); + return ErrorNone; +#elif defined(ZIG_OS_DARWIN) + paths.resize(0); + uint32_t img_count = _dyld_image_count(); + for (uint32_t i = 0; i != img_count; i += 1) { + const char *name = _dyld_get_image_name(i); + paths.append(buf_create_from_str(name)); + } + return ErrorNone; +#elif defined(ZIG_OS_WINDOWS) + // zig is built statically on windows, so we can return an empty list + paths.resize(0); + return ErrorNone; +#else +#error unimplemented +#endif +} + +Error os_file_open_rw(Buf *full_path, OsFile *out_file, OsFileAttr *attr, bool need_write, uint32_t mode) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + HANDLE result = CreateFileW(&path_space.data.items[0], + need_write ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, + need_write ? 0 : FILE_SHARE_READ, + nullptr, + need_write ? OPEN_ALWAYS : OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + + if (result == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + switch (err) { + case ERROR_SHARING_VIOLATION: + return ErrorSharingViolation; + case ERROR_ALREADY_EXISTS: + return ErrorPathAlreadyExists; + case ERROR_FILE_EXISTS: + return ErrorPathAlreadyExists; + case ERROR_FILE_NOT_FOUND: + return ErrorFileNotFound; + case ERROR_PATH_NOT_FOUND: + return ErrorFileNotFound; + case ERROR_ACCESS_DENIED: + return ErrorAccess; + case ERROR_PIPE_BUSY: + return ErrorPipeBusy; + default: + return ErrorUnexpected; + } + } + *out_file = result; + + if (attr != nullptr) { + BY_HANDLE_FILE_INFORMATION file_info; + if (!GetFileInformationByHandle(result, &file_info)) { + CloseHandle(result); + return ErrorUnexpected; + } + windows_filetime_to_os_timestamp(&file_info.ftLastWriteTime, &attr->mtime); + attr->inode = (((uint64_t)file_info.nFileIndexHigh) << 32) | file_info.nFileIndexLow; + attr->mode = 0; + attr->size = (((uint64_t)file_info.nFileSizeHigh) << 32) | file_info.nFileSizeLow; + } + + return ErrorNone; +#else + for (;;) { + int fd = open(buf_ptr(full_path), + need_write ? (O_RDWR|O_CLOEXEC|O_CREAT) : (O_RDONLY|O_CLOEXEC), mode); + if (fd == -1) { + switch (errno) { + case EINTR: + continue; + case EINVAL: + zig_unreachable(); + case EFAULT: + zig_unreachable(); + case EACCES: + case EPERM: + return ErrorAccess; + case EISDIR: + return ErrorIsDir; + case ENOENT: + return ErrorFileNotFound; + default: + return ErrorFileSystem; + } + } + struct stat statbuf; + if (fstat(fd, &statbuf) == -1) { + close(fd); + return ErrorFileSystem; + } + if (S_ISDIR(statbuf.st_mode)) { + close(fd); + return ErrorIsDir; + } + *out_file = fd; + + if (attr != nullptr) { + attr->inode = statbuf.st_ino; +#if defined(ZIG_OS_DARWIN) + attr->mtime.sec = statbuf.st_mtimespec.tv_sec; + attr->mtime.nsec = statbuf.st_mtimespec.tv_nsec; +#else + attr->mtime.sec = statbuf.st_mtim.tv_sec; + attr->mtime.nsec = statbuf.st_mtim.tv_nsec; +#endif + attr->mode = statbuf.st_mode; + attr->size = statbuf.st_size; + } + return ErrorNone; + } +#endif +} + +Error os_file_open_r(Buf *full_path, OsFile *out_file, OsFileAttr *attr) { + return os_file_open_rw(full_path, out_file, attr, false, 0); +} + +Error os_file_open_w(Buf *full_path, OsFile *out_file, OsFileAttr *attr, uint32_t mode) { + return os_file_open_rw(full_path, out_file, attr, true, mode); +} + +Error os_file_open_lock_rw(Buf *full_path, OsFile *out_file) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + for (;;) { + HANDLE result = CreateFileW(&path_space.data.items[0], GENERIC_READ | GENERIC_WRITE, + 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (result == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + switch (err) { + case ERROR_SHARING_VIOLATION: + // TODO wait for the lock instead of sleeping + Sleep(10); + continue; + case ERROR_ALREADY_EXISTS: + return ErrorPathAlreadyExists; + case ERROR_FILE_EXISTS: + return ErrorPathAlreadyExists; + case ERROR_FILE_NOT_FOUND: + return ErrorFileNotFound; + case ERROR_PATH_NOT_FOUND: + return ErrorFileNotFound; + case ERROR_ACCESS_DENIED: + return ErrorAccess; + case ERROR_PIPE_BUSY: + return ErrorPipeBusy; + default: + return ErrorUnexpected; + } + } + *out_file = result; + return ErrorNone; + } +#else + int fd; + for (;;) { + fd = open(buf_ptr(full_path), O_RDWR|O_CLOEXEC|O_CREAT, 0666); + if (fd == -1) { + switch (errno) { + case EINTR: + continue; + case EINVAL: + zig_unreachable(); + case EFAULT: + zig_unreachable(); + case EACCES: + case EPERM: + return ErrorAccess; + case EISDIR: + return ErrorIsDir; + case ENOENT: + return ErrorFileNotFound; + case ENOTDIR: + return ErrorNotDir; + default: + return ErrorFileSystem; + } + } + break; + } + for (;;) { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLKW, &lock) == -1) { + switch (errno) { + case EINTR: + continue; + case EBADF: + zig_unreachable(); + case EFAULT: + zig_unreachable(); + case EINVAL: + zig_unreachable(); + default: + close(fd); + return ErrorFileSystem; + } + } + break; + } + *out_file = fd; + return ErrorNone; +#endif +} + +Error os_file_read(OsFile file, void *ptr, size_t *len) { +#if defined(ZIG_OS_WINDOWS) + DWORD amt_read; + if (ReadFile(file, ptr, *len, &amt_read, nullptr) == 0) + return ErrorUnexpected; + *len = amt_read; + return ErrorNone; +#else + for (;;) { + ssize_t rc = read(file, ptr, *len); + if (rc == -1) { + switch (errno) { + case EINTR: + continue; + case EBADF: + zig_unreachable(); + case EFAULT: + zig_unreachable(); + case EISDIR: + return ErrorIsDir; + default: + return ErrorFileSystem; + } + } + *len = rc; + return ErrorNone; + } +#endif +} + +Error os_file_read_all(OsFile file, Buf *contents) { + Error err; + size_t index = 0; + for (;;) { + size_t amt = buf_len(contents) - index; + + if (amt < 4096) { + buf_resize(contents, buf_len(contents) + (4096 - amt)); + amt = buf_len(contents) - index; + } + + if ((err = os_file_read(file, buf_ptr(contents) + index, &amt))) + return err; + + if (amt == 0) { + buf_resize(contents, index); + return ErrorNone; + } + + index += amt; + } +} + +Error os_file_overwrite(OsFile file, Buf *contents) { +#if defined(ZIG_OS_WINDOWS) + if (SetFilePointer(file, 0, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + return ErrorFileSystem; + if (!SetEndOfFile(file)) + return ErrorFileSystem; + DWORD bytes_written; + if (!WriteFile(file, buf_ptr(contents), buf_len(contents), &bytes_written, nullptr)) + return ErrorFileSystem; + return ErrorNone; +#else + if (lseek(file, 0, SEEK_SET) == -1) + return ErrorUnexpectedSeekFailure; + if (ftruncate(file, 0) == -1) + return ErrorUnexpectedFileTruncationFailure; + for (;;) { + if (write(file, buf_ptr(contents), buf_len(contents)) == -1) { + switch (errno) { + case EINTR: + continue; + case EINVAL: + zig_unreachable(); + case EBADF: + zig_unreachable(); + case EFAULT: + zig_unreachable(); + case EDQUOT: + return ErrorDiskQuota; + case ENOSPC: + return ErrorDiskSpace; + case EFBIG: + return ErrorFileTooBig; + case EIO: + return ErrorFileSystem; + case EPERM: + return ErrorAccess; + default: + return ErrorUnexpectedWriteFailure; + } + } + return ErrorNone; + } +#endif +} + +void os_file_close(OsFile *file) { +#if defined(ZIG_OS_WINDOWS) + CloseHandle(*file); + *file = NULL; +#else + close(*file); + *file = -1; +#endif +} |
