diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/io_windows.c | |
Add the files
Diffstat (limited to 'src/common/impl/io_windows.c')
| -rw-r--r-- | src/common/impl/io_windows.c | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/common/impl/io_windows.c b/src/common/impl/io_windows.c new file mode 100644 index 0000000..24b8d05 --- /dev/null +++ b/src/common/impl/io_windows.c @@ -0,0 +1,501 @@ +#include "fastfetch.h" +#include "common/io.h" +#include "common/strutil.h" +#include "common/windows/nt.h" +#include "common/windows/unicode.h" + +#include <windows.h> + +static bool createSubfolders(wchar_t* fileName) { + HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle; + bool closeRoot = false; + wchar_t* ptr = fileName; + + // Absolute drive path: C:\... + if (ffCharIsEnglishAlphabet((char) ptr[0]) && ptr[1] == L':' && ptr[2] == L'\\') { + wchar_t saved = ptr[3]; + ptr[3] = L'\0'; + + hRoot = CreateFileW( + fileName, + FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + ptr[3] = saved; + if (hRoot == INVALID_HANDLE_VALUE) { + return false; + } + + closeRoot = true; + ptr += 3; // skip "C:\" + } + // UNC path: \\server\share\... + else if (ptr[0] == L'\\' && ptr[1] == L'\\') { + wchar_t* serverEnd = wcschr(ptr + 2, L'\\'); + if (serverEnd == NULL) { + return false; + } + + wchar_t* shareEnd = wcschr(serverEnd + 1, L'\\'); + if (shareEnd == NULL) { + return true; // no parent subfolder exists before file name + } + + wchar_t saved = *shareEnd; + *shareEnd = L'\0'; + + hRoot = CreateFileW( + fileName, + FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + *shareEnd = saved; + if (hRoot == INVALID_HANDLE_VALUE) { + return false; + } + + closeRoot = true; + ptr = shareEnd + 1; // first component under share + } + // Rooted path on current drive: \foo\bar + else if (ptr[0] == L'\\') { + UNICODE_STRING* dosPath = &ffGetPeb()->ProcessParameters->CurrentDirectory.DosPath; + wchar_t driveRoot[] = { dosPath->Buffer[0], L':', L'\\', L'\0' }; + hRoot = CreateFileW( + driveRoot, + FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hRoot == INVALID_HANDLE_VALUE) { + return false; + } + closeRoot = true; + ptr++; // skip leading '\' + } + + while (true) { + wchar_t* token = wcschr(ptr, L'\\'); + if (token == NULL) { + break; + } + + // Skip empty path segments caused by duplicated '\' + if (token == ptr) { + ptr = token + 1; + continue; + } + + HANDLE hNew = INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK iosb = {}; + + NTSTATUS status = NtCreateFile( + &hNew, + FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, + &(OBJECT_ATTRIBUTES) { + .Length = sizeof(OBJECT_ATTRIBUTES), + .RootDirectory = hRoot, + .ObjectName = &(UNICODE_STRING) { + .Buffer = ptr, + .Length = (USHORT) ((USHORT) (token - ptr) * sizeof(wchar_t)), + .MaximumLength = (USHORT) ((USHORT) (token - ptr) * sizeof(wchar_t)), + }, + .Attributes = OBJ_CASE_INSENSITIVE, + }, + &iosb, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, + 0); + + if (!NT_SUCCESS(status)) { + if (closeRoot && hRoot != INVALID_HANDLE_VALUE) { + NtClose(hRoot); + } + return false; + } + + if (closeRoot && hRoot != INVALID_HANDLE_VALUE) { + NtClose(hRoot); + } + hRoot = hNew; + closeRoot = true; + + ptr = token + 1; + } + + if (closeRoot && hRoot != INVALID_HANDLE_VALUE) { + NtClose(hRoot); + } + + return true; +} + +bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) { + wchar_t fileNameW[MAX_PATH]; + ULONG len = 0; + if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) { + return false; + } + + for (ULONG i = 0; i < len / sizeof(wchar_t); ++i) { + if (fileNameW[i] == L'/') { + fileNameW[i] = L'\\'; + } + } + + HANDLE FF_AUTO_CLOSE_FD handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + if (!createSubfolders(fileNameW)) { + return false; + } + handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) { + return false; + } + } else { + return false; + } + } + + DWORD written; + return !!WriteFile(handle, data, (DWORD) dataSize, &written, NULL); +} + +static inline void readWithLength(HANDLE handle, FFstrbuf* buffer, uint32_t length) { + ffStrbufEnsureFree(buffer, length); + DWORD bytesRead = 0; + while ( + length > 0 && + ReadFile(handle, buffer->chars + buffer->length, length, &bytesRead, NULL) != FALSE && + bytesRead > 0) { + buffer->length += (uint32_t) bytesRead; + length -= (uint32_t) bytesRead; + } +} + +static inline void readUntilEOF(HANDLE handle, FFstrbuf* buffer) { + ffStrbufEnsureFree(buffer, 31); + uint32_t available = ffStrbufGetFree(buffer); + DWORD bytesRead = 0; + while ( + ReadFile(handle, buffer->chars + buffer->length, available, &bytesRead, NULL) != FALSE && + bytesRead > 0) { + buffer->length += (uint32_t) bytesRead; + if ((uint32_t) bytesRead == available) { + ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte. + } + available = ffStrbufGetFree(buffer); + } +} + +bool ffAppendFDBuffer(HANDLE handle, FFstrbuf* buffer) { + FILE_STANDARD_INFORMATION fileInfo; + IO_STATUS_BLOCK iosb; + if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileStandardInformation))) { + fileInfo.EndOfFile.QuadPart = 0; + } + + if (fileInfo.EndOfFile.QuadPart > 0) { + readWithLength(handle, buffer, (uint32_t) fileInfo.EndOfFile.QuadPart); + } else { + readUntilEOF(handle, buffer); + } + + buffer->chars[buffer->length] = '\0'; + + return buffer->length > 0; +} + +HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory) { + assert(fileNameLen <= 0x7FFF); + + HANDLE hFile; + IO_STATUS_BLOCK iosb = {}; + if (!NT_SUCCESS(NtOpenFile(&hFile, + (directory ? FILE_LIST_DIRECTORY | FILE_TRAVERSE : FILE_READ_DATA | FILE_READ_EA) | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &(OBJECT_ATTRIBUTES) { + .Length = sizeof(OBJECT_ATTRIBUTES), + .RootDirectory = dfd, + .ObjectName = &(UNICODE_STRING) { + .Buffer = (PWSTR) fileName, + .Length = fileNameLen * (USHORT) sizeof(wchar_t), + .MaximumLength = (fileNameLen + 1) * (USHORT) sizeof(wchar_t), + }, + .Attributes = OBJ_CASE_INSENSITIVE, + }, + &iosb, + FILE_SHARE_READ | (directory ? FILE_SHARE_WRITE | FILE_SHARE_DELETE : 0), + FILE_SYNCHRONOUS_IO_NONALERT | (directory ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE)))) { + return INVALID_HANDLE_VALUE; + } + + return hFile; +} + +HANDLE openat(HANDLE dfd, const char* fileName, int oflag) { + wchar_t fileNameW[MAX_PATH]; + ULONG len; + if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) { + return INVALID_HANDLE_VALUE; + } + // Implies `fileNameW[len] = L'\0';` and `len` includes the null terminator + len /= sizeof(wchar_t); // convert from bytes to characters + + for (uint32_t i = 0; i < len - 1; ++i) { + if (fileNameW[i] == L'/') { + fileNameW[i] = L'\\'; + } + } + + return openatW(dfd, fileNameW, (uint16_t) (len - 1), !!(oflag & O_DIRECTORY)); +} + +bool ffPathExpandEnv(const char* in, FFstrbuf* out) { + if (in[0] == '~') { + if ((in[1] == '/' || in[1] == '\\' || in[1] == '\0') && !ffStrContainsC(in, '%')) { + ffStrbufSet(out, &instance.state.platform.homeDir); + ffStrbufAppendS(out, in + 1); + return true; + } + } + + wchar_t pathInW[MAX_PATH], pathOutW[MAX_PATH]; + ULONG len = (ULONG) strlen(in); + if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathInW, (ULONG) sizeof(pathInW), &len, in, len))) { + return false; + } + len /= sizeof(wchar_t); // convert from bytes to characters + + SIZE_T outLen; // in characters, including null terminator + if (!NT_SUCCESS(RtlExpandEnvironmentStrings(NULL, pathInW, len, pathOutW, ARRAY_SIZE(pathOutW), &outLen))) { + return false; + } + + ffStrbufSetNWS(out, (uint32_t) outLen - 1, pathOutW); + return true; +} + +bool ffSuppressIO(bool suppress) { +#ifndef NDEBUG + if (instance.config.display.debugMode) { + return false; + } +#endif + + static bool init = false; + static HANDLE hOrigOut = INVALID_HANDLE_VALUE; + static HANDLE hOrigErr = INVALID_HANDLE_VALUE; + HANDLE hNullFile = ffGetNullFD(); + static int fOrigOut = -1; + static int fOrigErr = -1; + static int fNullFile = -1; + + if (!init) { + if (!suppress) { + return true; + } + + hOrigOut = GetStdHandle(STD_OUTPUT_HANDLE); + hOrigErr = GetStdHandle(STD_ERROR_HANDLE); + fOrigOut = _dup(STDOUT_FILENO); + fOrigErr = _dup(STDERR_FILENO); + fNullFile = _open_osfhandle((intptr_t) hNullFile, 0); + + init = true; + } + if (hNullFile == INVALID_HANDLE_VALUE || fNullFile == -1) { + return false; + } + + fflush(stdout); + fflush(stderr); + + SetStdHandle(STD_OUTPUT_HANDLE, suppress ? hNullFile : hOrigOut); + SetStdHandle(STD_ERROR_HANDLE, suppress ? hNullFile : hOrigErr); + _dup2(suppress ? fNullFile : fOrigOut, STDOUT_FILENO); + _dup2(suppress ? fNullFile : fOrigErr, STDERR_FILENO); + + return true; +} + +void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty) { + uint32_t folderLength = folder->length; + + if (pretty && folderName != NULL) { + for (uint8_t i = 0; i < indentation - 1; i++) { + fputs(" | ", stdout); + } + printf("%s/\n", folderName); + } + + ffStrbufAppendC(folder, '*'); + WIN32_FIND_DATAA entry; + HANDLE hFind = FindFirstFileA(folder->chars, &entry); + ffStrbufTrimRight(folder, '*'); + if (hFind == INVALID_HANDLE_VALUE) { + return; + } + + do { + if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (ffStrEquals(entry.cFileName, ".") || ffStrEquals(entry.cFileName, "..")) { + continue; + } + + ffStrbufSubstrBefore(folder, folderLength); + ffStrbufAppendS(folder, entry.cFileName); + ffStrbufAppendC(folder, '/'); + listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry.cFileName, pretty); + ffStrbufSubstrBefore(folder, folderLength); + continue; + } + + if (pretty) { + for (uint8_t i = 0; i < indentation; i++) { + fputs(" | ", stdout); + } + } else { + fputs(folder->chars + baseLength, stdout); + } + + puts(entry.cFileName); + } while (FindNextFileA(hFind, &entry)); + FindClose(hFind); +} + +void ffListFilesRecursively(const char* path, bool pretty) { + FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path); + ffStrbufEnsureEndsWithC(&folder, '/'); + listFilesRecursively(folder.length, &folder, 0, NULL, pretty); +} + +const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) { + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE; + DWORD inputMode = 0; + bool hasInputMode = !!GetConsoleMode(hInput, &inputMode); + if (!hasInputMode) { + hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL); + hInput = hConin; + hasInputMode = !!GetConsoleMode(hInput, &inputMode); + } + SetConsoleMode(hInput, 0); + + FlushConsoleInputBuffer(hInput); + + { + DWORD bytes = 0; + HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE; + DWORD outputMode; + if (!GetConsoleMode(hOutput, &outputMode)) { + hConout = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL); + hOutput = hConout; + } + WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, NULL); + } + + while (true) { + if (NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0) { + SetConsoleMode(hInput, inputMode); + return "NtWaitForSingleObject() failed or timeout"; + } + + // Ignore all unexpected input events + INPUT_RECORD record; + DWORD len = 0; + if (!PeekConsoleInputW(hInput, &record, 1, &len)) { + break; + } + + if ( + record.EventType == KEY_EVENT && + record.Event.KeyEvent.uChar.UnicodeChar != L'\r' && + record.Event.KeyEvent.uChar.UnicodeChar != L'\n') { + break; + } else { + ReadConsoleInputW(hInput, &record, 1, &len); + } + } + + va_list args; + va_start(args, format); + + char buffer[1024]; + uint32_t bytesRead = 0; + + while (true) { + DWORD bytes = 0; + if (!ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) { + va_end(args); + return "ReadFile() failed"; + } + + bytesRead += bytes; + if (__builtin_expect(bytesRead >= sizeof(buffer) - 1, false)) { + va_end(args); + return "terminal response buffer overflow"; + } + buffer[bytesRead] = '\0'; + + va_list cargs; + va_copy(cargs, args); + int ret = vsscanf(buffer, format, cargs); + va_end(cargs); + + if (ret <= 0) { + va_end(args); + return "vsscanf(buffer, format, args) failed"; + } + if (ret >= nParams) { + break; + } + } + + if (hasInputMode) { + SetConsoleMode(hInput, inputMode); + } + + va_end(args); + + return NULL; +} + +FFNativeFD ffGetNullFD(void) { + static FFNativeFD hNullFile = INVALID_HANDLE_VALUE; + if (hNullFile != INVALID_HANDLE_VALUE) { + return hNullFile; + } + hNullFile = CreateFileW( + L"NUL", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + 0, + &(SECURITY_ATTRIBUTES) { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .lpSecurityDescriptor = NULL, + .bInheritHandle = TRUE, + }); + return hNullFile; +} + +bool ffRemoveFile(const char* fileName) { + return DeleteFileA(fileName) != FALSE; +} |