ladybird/Libraries/LibCore/SystemWindows.cpp
ayeteadoe 69e92f69a7 LibCore: Implement SIGTERM-based kill() on Windows
There is no direct Win32 API equivalent, but calling WM_CLOSE on
the top-level windows allows for a graceful shutdown where resources
are able to clean themselves up properly
2025-09-15 09:19:52 +02:00

406 lines
11 KiB
C++

/*
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, Kenneth Myhra <kennethmyhra@serenityos.org>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Matthias Zimmerman <matthias291999@gmail.com>
* Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
* Copyright (c) 2024-2025, stasoid <stasoid@yahoo.com>
* Copyright (c) 2025, ayeteadoe <ayeteadoe@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/ScopeGuard.h>
#include <LibCore/Process.h>
#include <LibCore/System.h>
#include <direct.h>
#include <sys/mman.h>
#include <AK/Windows.h>
namespace Core::System {
int windows_socketpair(SOCKET socks[2], int make_overlapped);
static void invalid_parameter_handler(wchar_t const*, wchar_t const*, wchar_t const*, unsigned int, uintptr_t)
{
}
static int init_crt_and_wsa()
{
WSADATA wsa;
WORD version = MAKEWORD(2, 2);
int rc = WSAStartup(version, &wsa);
VERIFY(!rc && wsa.wVersion == version);
// Make _get_osfhandle return -1 instead of crashing on invalid fd in release (debug still __debugbreak's)
_set_invalid_parameter_handler(invalid_parameter_handler);
return 0;
}
static auto dummy = init_crt_and_wsa();
ErrorOr<int> open(StringView path, int options, mode_t mode)
{
ByteString str = path;
int fd = _open(str.characters(), options | O_BINARY | _O_OBTAIN_DIR, mode);
if (fd < 0)
return Error::from_syscall("open"sv, errno);
ScopeGuard guard = [&] { _close(fd); };
return dup(_get_osfhandle(fd));
}
ErrorOr<void> close(int handle)
{
if (is_socket(handle)) {
if (closesocket(handle))
return Error::from_windows_error();
} else {
if (!CloseHandle(to_handle(handle)))
return Error::from_windows_error();
}
return {};
}
ErrorOr<ssize_t> read(int handle, Bytes buffer)
{
DWORD n_read = 0;
if (!ReadFile(to_handle(handle), buffer.data(), buffer.size(), &n_read, NULL))
return Error::from_windows_error();
return n_read;
}
ErrorOr<ssize_t> write(int handle, ReadonlyBytes buffer)
{
DWORD n_written = 0;
if (!WriteFile(to_handle(handle), buffer.data(), buffer.size(), &n_written, NULL))
return Error::from_windows_error();
return n_written;
}
ErrorOr<off_t> lseek(int handle, off_t offset, int origin)
{
static_assert(FILE_BEGIN == SEEK_SET && FILE_CURRENT == SEEK_CUR && FILE_END == SEEK_END, "SetFilePointerEx origin values are incompatible with lseek");
LARGE_INTEGER new_pointer = {};
if (!SetFilePointerEx(to_handle(handle), { .QuadPart = offset }, &new_pointer, origin))
return Error::from_windows_error();
return new_pointer.QuadPart;
}
ErrorOr<void> ftruncate(int handle, off_t length)
{
auto position = TRY(lseek(handle, 0, SEEK_CUR));
ScopeGuard restore_position = [&] { MUST(lseek(handle, position, SEEK_SET)); };
TRY(lseek(handle, length, SEEK_SET));
if (!SetEndOfFile(to_handle(handle)))
return Error::from_windows_error();
return {};
}
ErrorOr<struct stat> fstat(int handle)
{
struct stat st = {};
int fd = _open_osfhandle(TRY(dup(handle)), 0);
ScopeGuard guard = [&] { _close(fd); };
if (::fstat(fd, &st) < 0)
return Error::from_syscall("fstat"sv, errno);
return st;
}
ErrorOr<void> ioctl(int fd, unsigned request, ...)
{
va_list ap;
va_start(ap, request);
u_long arg = va_arg(ap, FlatPtr);
va_end(ap);
if (::ioctlsocket(fd, request, &arg) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<ByteString> getcwd()
{
auto* cwd = _getcwd(nullptr, 0);
if (!cwd)
return Error::from_syscall("getcwd"sv, errno);
ByteString string_cwd(cwd);
free(cwd);
return string_cwd;
}
ErrorOr<void> chdir(StringView path)
{
if (path.is_null())
return Error::from_errno(EFAULT);
ByteString path_string = path;
if (::_chdir(path_string.characters()) < 0)
return Error::from_syscall("chdir"sv, errno);
return {};
}
ErrorOr<struct stat> stat(StringView path)
{
if (path.is_null())
return Error::from_syscall("stat"sv, EFAULT);
struct stat st = {};
ByteString path_string = path;
if (::stat(path_string.characters(), &st) < 0)
return Error::from_syscall("stat"sv, errno);
return st;
}
ErrorOr<void> rmdir(StringView path)
{
if (path.is_null())
return Error::from_errno(EFAULT);
ByteString path_string = path;
if (_rmdir(path_string.characters()) < 0)
return Error::from_syscall("rmdir"sv, errno);
return {};
}
ErrorOr<void> unlink(StringView path)
{
if (path.is_null())
return Error::from_errno(EFAULT);
ByteString path_string = path;
if (_unlink(path_string.characters()) < 0)
return Error::from_syscall("unlink"sv, errno);
return {};
}
ErrorOr<void> mkdir(StringView path, mode_t)
{
ByteString str = path;
if (_mkdir(str.characters()) < 0)
return Error::from_syscall("mkdir"sv, errno);
return {};
}
ErrorOr<int> openat(int, StringView, int, mode_t)
{
dbgln("Core::System::openat() is not implemented");
VERIFY_NOT_REACHED();
}
ErrorOr<struct stat> fstatat(int, StringView, int)
{
dbgln("Core::System::fstatat() is not implemented");
VERIFY_NOT_REACHED();
}
ErrorOr<void*> mmap(void* address, size_t size, int protection, int flags, int file_handle, off_t offset, size_t alignment, StringView)
{
// custom alignment is not supported
VERIFY(!alignment);
int fd = _open_osfhandle(TRY(dup(file_handle)), 0);
ScopeGuard guard = [&] { _close(fd); };
void* ptr = ::mmap(address, size, protection, flags, fd, offset);
if (ptr == MAP_FAILED)
return Error::from_syscall("mmap"sv, errno);
return ptr;
}
ErrorOr<void> munmap(void* address, size_t size)
{
if (::munmap(address, size) < 0)
return Error::from_syscall("munmap"sv, errno);
return {};
}
int getpid()
{
return GetCurrentProcessId();
}
ErrorOr<int> dup(int handle)
{
if (handle < 0) {
return Error::from_windows_error(ERROR_INVALID_HANDLE);
}
if (is_socket(handle)) {
WSAPROTOCOL_INFO pi = {};
if (WSADuplicateSocket(handle, GetCurrentProcessId(), &pi))
return Error::from_windows_error();
SOCKET socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &pi, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
if (socket == INVALID_SOCKET)
return Error::from_windows_error();
return socket;
} else {
HANDLE new_handle = 0;
if (!DuplicateHandle(GetCurrentProcess(), to_handle(handle), GetCurrentProcess(), &new_handle, 0, FALSE, DUPLICATE_SAME_ACCESS))
return Error::from_windows_error();
return to_fd(new_handle);
}
}
bool is_socket(int handle)
{
// FILE_TYPE_PIPE is returned for sockets and pipes. We don't use Windows pipes.
return GetFileType(to_handle(handle)) == FILE_TYPE_PIPE;
}
ErrorOr<void> bind(int sockfd, struct sockaddr const* name, socklen_t name_size)
{
if (::bind(sockfd, name, name_size) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<void> listen(int sockfd, int backlog)
{
if (::listen(sockfd, backlog) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<int> accept(int sockfd, struct sockaddr* addr, socklen_t* addr_size)
{
auto fd = ::accept(sockfd, addr, addr_size);
if (fd == INVALID_SOCKET)
return Error::from_windows_error();
return fd;
}
ErrorOr<ssize_t> sendto(int sockfd, void const* source, size_t source_length, int flags, struct sockaddr const* destination, socklen_t destination_length)
{
auto sent = ::sendto(sockfd, static_cast<char const*>(source), source_length, flags, destination, destination_length);
if (sent == SOCKET_ERROR)
return Error::from_windows_error();
return sent;
}
ErrorOr<ssize_t> recvfrom(int sockfd, void* buffer, size_t buffer_length, int flags, struct sockaddr* address, socklen_t* address_length)
{
auto received = ::recvfrom(sockfd, static_cast<char*>(buffer), buffer_length, flags, address, address_length);
if (received == SOCKET_ERROR)
return Error::from_windows_error();
return received;
}
ErrorOr<void> getsockname(int sockfd, struct sockaddr* name, socklen_t* name_size)
{
if (::getsockname(sockfd, name, name_size) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<void> setsockopt(int sockfd, int level, int option, void const* value, socklen_t value_size)
{
if (::setsockopt(sockfd, level, option, static_cast<char const*>(value), value_size) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<void> socketpair(int domain, int type, int protocol, int sv[2])
{
if (domain != AF_LOCAL || type != SOCK_STREAM || protocol != 0)
return Error::from_string_literal("Unsupported argument value");
SOCKET socks[2] = {};
if (windows_socketpair(socks, true))
return Error::from_windows_error();
sv[0] = socks[0];
sv[1] = socks[1];
return {};
}
ErrorOr<void> sleep_ms(u32 milliseconds)
{
Sleep(milliseconds);
return {};
}
unsigned hardware_concurrency()
{
SYSTEM_INFO si = {};
GetSystemInfo(&si);
// number of logical processors in the current group (max 64)
return si.dwNumberOfProcessors;
}
u64 physical_memory_bytes()
{
MEMORYSTATUSEX ms = {};
ms.dwLength = sizeof ms;
GlobalMemoryStatusEx(&ms);
return ms.ullTotalPhys;
}
ErrorOr<ByteString> current_executable_path()
{
return TRY(Process::get_name()).to_byte_string();
}
ErrorOr<void> set_close_on_exec(int handle, bool enabled)
{
if (!SetHandleInformation(to_handle(handle), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT))
return Error::from_windows_error();
return {};
}
ErrorOr<bool> isatty(int handle)
{
return GetFileType(to_handle(handle)) == FILE_TYPE_CHAR;
}
ErrorOr<int> socket(int domain, int type, int protocol)
{
auto socket = ::socket(domain, type, protocol);
if (socket == INVALID_SOCKET)
return Error::from_windows_error();
return socket;
}
ErrorOr<AddressInfoVector> getaddrinfo(char const* nodename, char const* servname, struct addrinfo const& hints)
{
struct addrinfo* results = nullptr;
int rc = ::getaddrinfo(nodename, servname, &hints, &results);
if (rc != 0)
return Error::from_windows_error(rc);
Vector<struct addrinfo> addresses;
for (auto* result = results; result != nullptr; result = result->ai_next)
TRY(addresses.try_append(*result));
return AddressInfoVector { move(addresses), results };
}
ErrorOr<void> connect(int socket, struct sockaddr const* address, socklen_t address_length)
{
if (::connect(socket, address, address_length) == SOCKET_ERROR)
return Error::from_windows_error();
return {};
}
ErrorOr<void> kill(pid_t pid, int signal)
{
if (signal == SIGTERM) {
if (!EnumWindows([](HWND hwnd, LPARAM l_param) -> BOOL {
DWORD window_pid = 0;
GetWindowThreadProcessId(hwnd, &window_pid);
if (window_pid == static_cast<DWORD>(l_param)) {
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
return TRUE;
},
pid))
return Error::from_windows_error();
} else {
return Error::from_string_literal("Unsupported signal value");
}
return {};
}
}