<commit_before>const std = @import("../../index.zig");
const builtin = @import("builtin");
const os = std.os;
const windows = std.os.windows;
const assert = std.debug.assert;
const mem = std.mem;
const BufMap = std.BufMap;
const cstr = std.cstr;

pub const WaitError = error{
    WaitAbandoned,
    WaitTimeOut,
    Unexpected,
};

pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitError!void {
    const result = windows.WaitForSingleObject(handle, milliseconds);
    return switch (result) {
        windows.WAIT_ABANDONED => error.WaitAbandoned,
        windows.WAIT_OBJECT_0 => {},
        windows.WAIT_TIMEOUT => error.WaitTimeOut,
        windows.WAIT_FAILED => x: {
            const err = windows.GetLastError();
            break :x switch (err) {
                else => os.unexpectedErrorWindows(err),
            };
        },
        else => error.Unexpected,
    };
}

pub fn windowsClose(handle: windows.HANDLE) void {
    assert(windows.CloseHandle(handle) != 0);
}

pub const WriteError = error{
    SystemResources,
    OperationAborted,
    IoPending,
    BrokenPipe,
    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) {
        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.BROKEN_PIPE => WriteError.BrokenPipe,
            else => os.unexpectedErrorWindows(err),
        };
    }
}

pub fn windowsIsTty(handle: windows.HANDLE) bool {
    if (windowsIsCygwinPty(handle))
        return true;

    var out: windows.DWORD = undefined;
    return windows.GetConsoleMode(handle, &out) != 0;
}

pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
    const size = @sizeOf(windows.FILE_NAME_INFO);
    var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH);

    if (windows.GetFileInformationByHandleEx(
        handle,
        windows.FileNameInfo,
        @ptrCast(*c_void, &name_info_bytes[0]),
        @intCast(u32, name_info_bytes.len),
    ) == 0) {
        return true;
    }

    const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
    const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
    const name_wide = @bytesToSlice(u16, name_bytes);
    return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or
        mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null;
}

pub const OpenError = error{
    SharingViolation,
    PathAlreadyExists,
    FileNotFound,
    AccessDenied,
    PipeBusy,
    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 result = windows.CreateFileA(path_with_null.ptr, 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),
        };
    }

    return result;
}

/// Caller must free result.
pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 {
    // count bytes needed
    const bytes_needed = x: {
        var bytes_needed: usize = 1; // 1 for the final null byte
        var it = env_map.iterator();
        while (it.next()) |pair| {
            // +1 for '='
            // +1 for null byte
            bytes_needed += pair.key.len + pair.value.len + 2;
        }
        break :x bytes_needed;
    };
    const result = try allocator.alloc(u8, bytes_needed);
    errdefer allocator.free(result);

    var it = env_map.iterator();
    var i: usize = 0;
    while (it.next()) |pair| {
        mem.copy(u8, result[i..], pair.key);
        i += pair.key.len;
        result[i] = '=';
        i += 1;
        mem.copy(u8, result[i..], pair.value);
        i += pair.value.len;
        result[i] = 0;
        i += 1;
    }
    result[i] = 0;
    return result;
}

pub fn windowsLoadDll(allocator: *mem.Allocator, dll_path: []const u8) !windows.HMODULE {
    const padded_buff = try cstr.addNullByte(allocator, dll_path);
    defer allocator.free(padded_buff);
    return windows.LoadLibraryA(padded_buff.ptr) orelse error.DllNotFound;
}

pub fn windowsUnloadDll(hModule: windows.HMODULE) void {
    assert(windows.FreeLibrary(hModule) != 0);
}

test "InvalidDll" {
    if (builtin.os != builtin.Os.windows) return;

    const DllName = "asdf.dll";
    const allocator = std.debug.global_allocator;
    const handle = os.windowsLoadDll(allocator, DllName) catch |err| {
        assert(err == error.DllNotFound);<commit_msg>std/os/windows/util.zig: SKIP instead of PASS on non-windows systems;<commit_after>const std = @import("../../index.zig");
const builtin = @import("builtin");
const os = std.os;
const windows = std.os.windows;
const assert = std.debug.assert;
const mem = std.mem;
const BufMap = std.BufMap;
const cstr = std.cstr;

pub const WaitError = error{
    WaitAbandoned,
    WaitTimeOut,
    Unexpected,
};

pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitError!void {
    const result = windows.WaitForSingleObject(handle, milliseconds);
    return switch (result) {
        windows.WAIT_ABANDONED => error.WaitAbandoned,
        windows.WAIT_OBJECT_0 => {},
        windows.WAIT_TIMEOUT => error.WaitTimeOut,
        windows.WAIT_FAILED => x: {
            const err = windows.GetLastError();
            break :x switch (err) {
                else => os.unexpectedErrorWindows(err),
            };
        },
        else => error.Unexpected,
    };
}

pub fn windowsClose(handle: windows.HANDLE) void {
    assert(windows.CloseHandle(handle) != 0);
}

pub const WriteError = error{
    SystemResources,
    OperationAborted,
    IoPending,
    BrokenPipe,
    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) {
        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.BROKEN_PIPE => WriteError.BrokenPipe,
            else => os.unexpectedErrorWindows(err),
        };
    }
}

pub fn windowsIsTty(handle: windows.HANDLE) bool {
    if (windowsIsCygwinPty(handle))
        return true;

    var out: windows.DWORD = undefined;
    return windows.GetConsoleMode(handle, &out) != 0;
}

pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
    const size = @sizeOf(windows.FILE_NAME_INFO);
    var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH);

    if (windows.GetFileInformationByHandleEx(
        handle,
        windows.FileNameInfo,
        @ptrCast(*c_void, &name_info_bytes[0]),
        @intCast(u32, name_info_bytes.len),
    ) == 0) {
        return true;
    }

    const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
    const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
    const name_wide = @bytesToSlice(u16, name_bytes);
    return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or
        mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null;
}

pub const OpenError = error{
    SharingViolation,
    PathAlreadyExists,
    FileNotFound,
    AccessDenied,
    PipeBusy,
    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 result = windows.CreateFileA(path_with_null.ptr, 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),
        };
    }

    return result;
}

/// Caller must free result.
pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 {
    // count bytes needed
    const bytes_needed = x: {
        var bytes_needed: usize = 1; // 1 for the final null byte
        var it = env_map.iterator();
        while (it.next()) |pair| {
            // +1 for '='
            // +1 for null byte
            bytes_needed += pair.key.len + pair.value.len + 2;
        }
        break :x bytes_needed;
    };
    const result = try allocator.alloc(u8, bytes_needed);
    errdefer allocator.free(result);

    var it = env_map.iterator();
    var i: usize = 0;
    while (it.next()) |pair| {
        mem.copy(u8, result[i..], pair.key);
        i += pair.key.len;
        result[i] = '=';
        i += 1;
        mem.copy(u8, result[i..], pair.value);
        i += pair.value.len;
        result[i] = 0;
        i += 1;
    }
    result[i] = 0;
    return result;
}

pub fn windowsLoadDll(allocator: *mem.Allocator, dll_path: []const u8) !windows.HMODULE {
    const padded_buff = try cstr.addNullByte(allocator, dll_path);
    defer allocator.free(padded_buff);
    return windows.LoadLibraryA(padded_buff.ptr) orelse error.DllNotFound;
}

pub fn windowsUnloadDll(hModule: windows.HMODULE) void {
    assert(windows.FreeLibrary(hModule) != 0);
}

test "InvalidDll" {
    if (builtin.os != builtin.Os.windows) return error.SkipZigTest;

    const DllName = "asdf.dll";
    const allocator = std.debug.global_allocator;
    const handle = os.windowsLoadDll(allocator, DllName) catch |err| {
        assert(err == error.DllNotFound);
{'fraction_non_alphanumeric': 0.1236277655801385, 'fraction_numerical': 0.00692450599560885, 'mean_word_length': 2.9754951325948307, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3793157', 'n_tokens_mistral': 3886, 'n_tokens_neox': 3800, 'n_words': 817}