Selkie.git | lib/Selkie/App/Internal/ | ErrorLogPlatform.rakumod


=begin pod

=head1 NAME

Selkie::App::Internal::ErrorLogPlatform - platform fd adapter for error logging

=head1 DESCRIPTION

Internal implementation detail used by C<Selkie::App::Internal::ErrorLog>.

=end pod

unit module Selkie::App::Internal::ErrorLogPlatform;

use NativeCall;

use Selkie::App::Internal::PosixFD;

# Official Windows MoarVM builds use /MT, so their integer file descriptors
# belong to a private, statically linked CRT.  No NativeCall DLL owns that
# descriptor table, and MoarVM descriptors must never be passed to another
# CRT.  Windows therefore redirects three independent stderr views: Raku's
# PROCESS::<$ERR> object, the Win32 standard handle, and — with descriptors
# created wholly inside ucrtbase (see the ucrt-* subs below) — the shared
# UCRT's fd 2, which C-level fprintf(stderr) writers go through.  POSIX has
# a single libc, so mutating fd 2 there covers everything at once.
our sub error-log-api-spec(Bool:D $is-win --> Map:D) is export(:test) {
	$is-win
		?? Map.new(
			api          => "win32",
			library      => "kernel32",
			create-file  => "CreateFileW",
			close-handle => "CloseHandle",
		)
		!! Map.new(
			api     => "posix",
			library => Str,
			dup     => "dup",
			dup2    => "dup2",
			close   => "close",
		)
}

our constant IS-WINDOWS is export = $*DISTRO.is-win;
our constant WINDOWS-STD-ERROR-HANDLE is export = 0xFFFF_FFF4;
our constant WINDOWS-FILE-APPEND-DATA is export = 0x0000_0004;
our constant WINDOWS-SYNCHRONIZE is export = 0x0010_0000;
our constant WINDOWS-FILE-SHARE-ALL is export = 0x0000_0007;
our constant WINDOWS-OPEN-ALWAYS is export = 4;
our constant WINDOWS-FILE-ATTRIBUTE-NORMAL is export = 0x0000_0080;
our sub fd-dup(int32 $fd --> int32) is export {
	posix-fd-dup($fd)
}
our sub fd-dup2(int32 $source, int32 $target --> int32) is export {
	posix-fd-dup2($source, $target)
}
our sub fd-close(int32 $fd --> int32) is export {
	posix-fd-close($fd)
}

# GetStdHandle and SetStdHandle take a DWORD, not a signed integer.  The
# standard-handle constants are C casts such as ((DWORD)-12), whose value at
# the ABI boundary is the unsigned 32-bit integer 0xFFFFFFF4.  Passing -12 as
# an int32 is not portable through foreign-function interfaces.
our sub windows-get-std-handle(uint32 --> Pointer) is export
	is native("kernel32") is symbol("GetStdHandle") { * }
our sub windows-set-std-handle(uint32, Pointer --> int32) is export
	is native("kernel32") is symbol("SetStdHandle") { * }
our sub windows-create-file(
	Str is encoded("utf16"), uint32, uint32, Pointer,
	uint32, uint32, Pointer --> Pointer
) is export is native("kernel32") is symbol("CreateFileW") { * }
our sub windows-close-handle(Pointer --> int32) is export
	is native("kernel32") is symbol("CloseHandle") { * }

# The shared Universal CRT (ucrtbase.dll) keeps its own descriptor table and
# FILE* set, bound to the console at process start and never re-read from the
# Win32 standard-handle table.  Every MSYS2-ucrt64 or modern-MSVC DLL in the
# process — GLib/libvips warnings, sqlcipher diagnostics, ffmpeg chatter —
# fprintf(stderr)s through it, so SetStdHandle alone leaves all of that on the
# terminal, where a single newline scrolls the TUI behind notcurses' back.
# Redirect it the same way the POSIX path redirects fd 2, but with both fds
# created *inside ucrtbase*: _wopen the log, _dup2 it over ucrt fd 2, keep a
# _dup of the original for restore.  MoarVM's private /MT CRT is never
# involved, so the caveat at the top of this file is respected.
our constant UCRT-O-WRONLY is export = 0x0001;
our constant UCRT-O-APPEND is export = 0x0008;
our constant UCRT-O-CREAT  is export = 0x0100;
our constant UCRT-O-BINARY is export = 0x8000;
our constant UCRT-S-IREAD-IWRITE is export = 0x0180;
our constant WINDOWS-HANDLE-FLAG-PROTECT-FROM-CLOSE is export = 0x0002;
our sub ucrt-dup(int32 --> int32) is export
	is native("ucrtbase") is symbol("_dup") { * }
our sub ucrt-dup2(int32, int32 --> int32) is export
	is native("ucrtbase") is symbol("_dup2") { * }
our sub ucrt-close(int32 --> int32) is export
	is native("ucrtbase") is symbol("_close") { * }
our sub ucrt-wopen(Str is encoded("utf16"), int32, int32 --> int32) is export
	is native("ucrtbase") is symbol("_wopen") { * }
our sub ucrt-get-osfhandle(int32 --> Pointer) is export
	is native("ucrtbase") is symbol("_get_osfhandle") { * }
our sub windows-set-handle-information(Pointer, uint32, uint32 --> int32) is export
	is native("kernel32") is symbol("SetHandleInformation") { * }