summaryrefslogtreecommitdiffstats
path: root/src/common/impl/path.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/path.c
Add the files
Diffstat (limited to 'src/common/impl/path.c')
-rw-r--r--src/common/impl/path.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/src/common/impl/path.c b/src/common/impl/path.c
new file mode 100644
index 0000000..c97210e
--- /dev/null
+++ b/src/common/impl/path.c
@@ -0,0 +1,277 @@
+#include "common/path.h"
+#include "common/io.h"
+#include "common/arrutil.h"
+
+#if !_WIN32
+const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
+ char* path = getenv("PATH");
+ if (!path) {
+ return "$PATH not set";
+ }
+
+ #ifdef _WIN32
+ const bool appendExe = !ffStrEndsWithIgnCase(name, ".exe");
+ #endif
+
+ for (char* token = path; *token; path = token + 1) {
+ token = strchr(path,
+ #ifdef _WIN32
+ ';'
+ #else
+ ':'
+ #endif
+ );
+ if (!token) {
+ token = path + strlen(path);
+ }
+
+ ffStrbufSetNS(result, (uint32_t) (token - path), path);
+ ffStrbufEnsureEndsWithC(result,
+ #ifdef _WIN32
+ '\\'
+ #else
+ '/'
+ #endif
+ );
+ ffStrbufAppendS(result, name);
+ #ifdef _WIN32
+ if (appendExe) {
+ ffStrbufAppendS(result, ".exe");
+ }
+ if (!ffPathExists(result->chars, FF_PATHTYPE_FILE)) {
+ continue;
+ }
+ #else
+ if (access(result->chars, X_OK) != 0) {
+ continue;
+ }
+ #endif
+
+ return NULL;
+ }
+ ffStrbufClear(result);
+ return "Executable not found";
+}
+#else
+ #include <windows.h>
+ #include <winioctl.h>
+ #include <errno.h>
+ #include <stdalign.h>
+
+const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
+ char buffer[MAX_PATH + 1];
+ DWORD length = SearchPathA(NULL, name, ".exe", sizeof(buffer), buffer, NULL);
+ if (length == 0) {
+ ffStrbufClear(result);
+ return "Executable not found";
+ }
+ ffStrbufSetS(result, buffer);
+ return NULL;
+}
+
+static inline int winerr2Errno(DWORD err) {
+ switch (err) {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_INVALID_NAME:
+ return ENOENT;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_LOCK_VIOLATION:
+ return EACCES;
+ case ERROR_BUFFER_OVERFLOW:
+ case ERROR_INSUFFICIENT_BUFFER:
+ return ENAMETOOLONG;
+ case ERROR_INVALID_PARAMETER:
+ case ERROR_NOT_A_REPARSE_POINT:
+ return EINVAL;
+ default:
+ return EIO;
+ }
+}
+
+char* frealpath(HANDLE hFile, char* resolved_name) {
+ if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !hFile, false)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ wchar_t resolvedNameW[MAX_PATH + 4]; /* +4 for "\\\\?\\" prefix */
+ DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD) ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED);
+
+ if (lenW == 0) {
+ errno = winerr2Errno(GetLastError());
+ return NULL;
+ }
+ if (lenW >= ARRAY_SIZE(resolvedNameW)) {
+ errno = E2BIG;
+ return NULL;
+ }
+ lenW++; // Include null terminator
+
+ wchar_t* srcW = resolvedNameW;
+ DWORD srcLenW = lenW;
+
+ if (srcLenW >= 8 && wcsncmp(resolvedNameW, L"\\\\?\\UNC\\", 8) == 0) {
+ /* Convert "\\?\UNC\server\share" to "\\server\share" */
+ srcW += 6;
+ srcLenW -= 6;
+ *srcW = L'\\';
+ } else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L"\\\\?\\", 4) == 0) {
+ srcW += 4;
+ srcLenW -= 4;
+ }
+
+ if (resolved_name) {
+ ULONG outBytes = 0;
+ if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
+ errno = E2BIG;
+ return NULL;
+ }
+ } else {
+ /* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */
+ char tmp[(MAX_PATH + 4) * 4];
+ ULONG outBytes = 0;
+
+ if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG) sizeof(tmp), &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
+ errno = E2BIG;
+ return NULL;
+ }
+
+ resolved_name = (char*) malloc(outBytes);
+ if (!resolved_name) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ memcpy(resolved_name, tmp, outBytes);
+ }
+
+ return resolved_name;
+}
+
+char* realpath(const char* __restrict file_name, char* __restrict resolved_name) {
+ if (!file_name) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ wchar_t fileNameW[MAX_PATH];
+ ULONG lenBytes = 0;
+
+ if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &lenBytes, file_name, (ULONG) strlen(file_name) + 1))) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
+ fileNameW,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ errno = winerr2Errno(GetLastError());
+ return NULL;
+ }
+
+ return frealpath(hFile, resolved_name);
+}
+
+ssize_t freadlink(HANDLE hFile, char* buf, size_t bufsiz) {
+ if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !buf || bufsiz == 0, false)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ alignas(REPARSE_DATA_BUFFER) BYTE reparseBuf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ DWORD bytesReturned = 0;
+ if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, reparseBuf, (DWORD) sizeof(reparseBuf), &bytesReturned, NULL)) {
+ errno = winerr2Errno(GetLastError());
+ return -1;
+ }
+
+ REPARSE_DATA_BUFFER* rp = (REPARSE_DATA_BUFFER*) reparseBuf;
+ const wchar_t* targetW = NULL;
+ USHORT targetBytes = 0;
+
+ if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
+ if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 0) {
+ targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
+ (rp->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t));
+ targetBytes = rp->SymbolicLinkReparseBuffer.PrintNameLength;
+ } else {
+ targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
+ (rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
+ targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength;
+
+ if (targetBytes >= 8 &&
+ wcsncmp(targetW, L"\\??\\", 4) == 0) {
+ targetW += 4;
+ targetBytes -= 8;
+ }
+ }
+ } else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
+ if (rp->MountPointReparseBuffer.PrintNameLength > 0) {
+ targetW = rp->MountPointReparseBuffer.PathBuffer +
+ (rp->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t));
+ targetBytes = rp->MountPointReparseBuffer.PrintNameLength;
+ } else {
+ targetW = rp->MountPointReparseBuffer.PathBuffer +
+ (rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
+ targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength;
+
+ if (targetBytes >= 8 &&
+ wcsncmp(targetW, L"\\??\\", 4) == 0) {
+ targetW += 4;
+ targetBytes -= 8;
+ }
+ }
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ULONG outBytes = 0;
+ if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes))) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ // Not null-terminated
+ return (ssize_t) outBytes;
+}
+
+ssize_t readlink(const char* path, char* buf, size_t bufsiz) {
+ if (!path || !buf || bufsiz == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ wchar_t pathW[MAX_PATH];
+ ULONG pathWBytes = 0;
+ if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), &pathWBytes, path, (ULONG) strlen(path) + 1))) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
+ pathW,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ errno = winerr2Errno(GetLastError());
+ return -1;
+ }
+
+ return freadlink(hFile, buf, bufsiz);
+}
+#endif