Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/benchmark/CodepointWidth.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
const CodepointWidth = @This();

const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig");
Expand Down Expand Up @@ -104,6 +105,11 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
extern "c" fn wcwidth(c: u32) c_int;

fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
if (comptime builtin.os.tag == .windows) {
log.warn("wcwidth is not available on Windows", .{});
return;
}

const self: *CodepointWidth = @ptrCast(@alignCast(ptr));

const f = self.data_f orelse return;
Expand Down
43 changes: 37 additions & 6 deletions src/cli/ssh-cache/DiskCache.zig
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,24 @@ pub fn clear(self: DiskCache) !void {

pub const AddResult = enum { added, updated };

pub const AddError = std.fs.Dir.MakeError ||
std.fs.Dir.StatFileError ||
std.fs.File.OpenError ||
std.fs.File.ChmodError ||
std.io.Reader.LimitedAllocError ||
FixupPermissionsError ||
ReadEntriesError ||
WriteCacheFileError ||
Error;

/// Add or update a hostname entry in the cache.
/// Returns AddResult.added for new entries or AddResult.updated for existing ones.
/// The cache file is created if it doesn't exist with secure permissions (0600).
pub fn add(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !AddResult {
) AddError!AddResult {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;

// Create cache directory if needed
Expand Down Expand Up @@ -128,13 +138,19 @@ pub fn add(
return result;
}

pub const RemoveError = std.fs.File.OpenError ||
FixupPermissionsError ||
ReadEntriesError ||
WriteCacheFileError ||
Error;

/// Remove a hostname entry from the cache.
/// No error is returned if the hostname doesn't exist or the cache file is missing.
pub fn remove(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !void {
) RemoveError!void {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;

// Open our file
Expand Down Expand Up @@ -168,13 +184,17 @@ pub fn remove(
try self.writeCacheFile(entries, null);
}

pub const ContainsError = std.fs.File.OpenError ||
ReadEntriesError ||
error{HostnameIsInvalid};

/// Check if a hostname exists in the cache.
/// Returns false if the cache file doesn't exist.
pub fn contains(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !bool {
) ContainsError!bool {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;

// Open our file
Expand All @@ -194,7 +214,9 @@ pub fn contains(
return entries.contains(hostname);
}

fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.ChmodError)!void {
pub const FixupPermissionsError = (std.fs.File.StatError || std.fs.File.ChmodError);

fn fixupPermissions(file: std.fs.File) FixupPermissionsError!void {
// Windows does not support chmod
if (comptime builtin.os.tag == .windows) return;

Expand All @@ -206,11 +228,18 @@ fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.Chm
}
}

pub const WriteCacheFileError = std.fs.Dir.OpenError ||
std.fs.AtomicFile.InitError ||
std.fs.AtomicFile.FlushError ||
std.fs.AtomicFile.FinishError ||
Entry.FormatError ||
error{InvalidCachePath};

fn writeCacheFile(
self: DiskCache,
entries: std.StringHashMap(Entry),
expire_days: ?u32,
) !void {
) WriteCacheFileError!void {
const cache_dir = std.fs.path.dirname(self.path) orelse return error.InvalidCachePath;
const cache_basename = std.fs.path.basename(self.path);

Expand Down Expand Up @@ -270,10 +299,12 @@ pub fn deinitEntries(
entries.deinit();
}

pub const ReadEntriesError = std.mem.Allocator.Error || std.io.Reader.LimitedAllocError;

fn readEntries(
alloc: Allocator,
file: std.fs.File,
) !std.StringHashMap(Entry) {
) ReadEntriesError!std.StringHashMap(Entry) {
var reader = file.reader(&.{});
const content = try reader.interface.allocRemaining(
alloc,
Expand Down
4 changes: 3 additions & 1 deletion src/cli/ssh-cache/Entry.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ pub fn parse(line: []const u8) ?Entry {
};
}

pub fn format(self: Entry, writer: *std.Io.Writer) !void {
pub const FormatError = std.Io.Writer.Error;

pub fn format(self: Entry, writer: *std.Io.Writer) FormatError!void {
try writer.print(
"{s}|{d}|{s}\n",
.{ self.hostname, self.timestamp, self.terminfo_version },
Expand Down
8 changes: 7 additions & 1 deletion src/os/homedir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const Error = error{
/// is generally an expensive process so the value should be cached.
pub inline fn home(buf: []u8) !?[]const u8 {
return switch (builtin.os.tag) {
inline .linux, .freebsd, .macos => try homeUnix(buf),
.linux, .freebsd, .macos => try homeUnix(buf),
.windows => try homeWindows(buf),

// iOS doesn't have a user-writable home directory
Expand Down Expand Up @@ -122,7 +122,13 @@ pub const ExpandError = error{
pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 {
return switch (builtin.os.tag) {
.linux, .freebsd, .macos => try expandHomeUnix(path, buf),

// `~/` is not an idiom generally used on Windows
.windows => return path,

// iOS doesn't have a user-writable home directory
.ios => return path,

else => @compileError("unimplemented"),
};
}
Expand Down
37 changes: 31 additions & 6 deletions src/os/hostname.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const posix = std.posix;

pub const LocalHostnameValidationError = error{
Expand Down Expand Up @@ -99,19 +100,43 @@ pub fn isLocal(hostname: []const u8) LocalHostnameValidationError!bool {
if (std.mem.eql(u8, "localhost", hostname)) return true;

// If hostname is not "localhost" it must match our hostname.
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const ourHostname = try posix.gethostname(&buf);
return std.mem.eql(u8, hostname, ourHostname);
switch (builtin.os.tag) {
.windows => {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return false;
const ourHostname = buf[0..nSize];
return std.mem.eql(u8, hostname, ourHostname);
},
else => {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const ourHostname = try posix.gethostname(&buf);
return std.mem.eql(u8, hostname, ourHostname);
},
}
}

test "isLocal returns true when provided hostname is localhost" {
try std.testing.expect(try isLocal("localhost"));
}

test "isLocal returns true when hostname is local" {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const localHostname = try posix.gethostname(&buf);
try std.testing.expect(try isLocal(localHostname));
switch (builtin.os.tag) {
.windows => {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return error.GetComputerNameFailed;
const localHostname = buf[0..nSize];
try std.testing.expect(try isLocal(localHostname));
},
else => {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const localHostname = try posix.gethostname(&buf);
try std.testing.expect(try isLocal(localHostname));
},
}
}

test "isLocal returns false when hostname is not local" {
Expand Down
21 changes: 13 additions & 8 deletions src/os/windows.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,22 @@ pub const exp = struct {
hWritePipe: *windows.HANDLE,
lpPipeAttributes: ?*const windows.SECURITY_ATTRIBUTES,
nSize: windows.DWORD,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn CreatePseudoConsole(
size: windows.COORD,
hInput: windows.HANDLE,
hOutput: windows.HANDLE,
dwFlags: windows.DWORD,
phPC: *HPCON,
) callconv(windows.WINAPI) windows.HRESULT;
pub extern "kernel32" fn ResizePseudoConsole(hPC: HPCON, size: windows.COORD) callconv(windows.WINAPI) windows.HRESULT;
pub extern "kernel32" fn ClosePseudoConsole(hPC: HPCON) callconv(windows.WINAPI) void;
) callconv(.winapi) windows.HRESULT;
pub extern "kernel32" fn ResizePseudoConsole(hPC: HPCON, size: windows.COORD) callconv(.winapi) windows.HRESULT;
pub extern "kernel32" fn ClosePseudoConsole(hPC: HPCON) callconv(.winapi) void;
pub extern "kernel32" fn InitializeProcThreadAttributeList(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
dwAttributeCount: windows.DWORD,
dwFlags: windows.DWORD,
lpSize: *windows.SIZE_T,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn UpdateProcThreadAttribute(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
dwFlags: windows.DWORD,
Expand All @@ -77,15 +77,15 @@ pub const exp = struct {
cbSize: windows.SIZE_T,
lpPreviousValue: ?windows.PVOID,
lpReturnSize: ?*windows.SIZE_T,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn PeekNamedPipe(
hNamedPipe: windows.HANDLE,
lpBuffer: ?windows.LPVOID,
nBufferSize: windows.DWORD,
lpBytesRead: ?*windows.DWORD,
lpTotalBytesAvail: ?*windows.DWORD,
lpBytesLeftThisMessage: ?*windows.DWORD,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
// Duplicated here because lpCommandLine is not marked optional in zig std
pub extern "kernel32" fn CreateProcessW(
lpApplicationName: ?windows.LPWSTR,
Expand All @@ -98,7 +98,12 @@ pub const exp = struct {
lpCurrentDirectory: ?windows.LPWSTR,
lpStartupInfo: *windows.STARTUPINFOW,
lpProcessInformation: *windows.PROCESS_INFORMATION,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
/// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamea
pub extern "kernel32" fn GetComputerNameA(
lpBuffer: windows.LPSTR,
nSize: *windows.DWORD,
) callconv(.winapi) windows.BOOL;
};

pub const PROC_THREAD_ATTRIBUTE_NUMBER = 0x0000FFFF;
Expand Down