Files
fastfetch/src/common/impl/path.c
T

286 lines
8.1 KiB
C
Raw Normal View History

2026-01-06 09:48:38 +08:00
#include "common/path.h"
#include "common/io.h"
#include "common/arrayUtils.h"
2025-04-07 16:38:13 +08:00
#if !_WIN32
2026-03-29 09:28:49 +08:00
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
char* path = getenv("PATH");
2026-03-29 09:28:49 +08:00
if (!path) {
return "$PATH not set";
2026-03-29 09:28:49 +08:00
}
2026-03-29 09:28:49 +08:00
# ifdef _WIN32
const bool appendExe = !ffStrEndsWithIgnCase(name, ".exe");
2026-03-29 09:28:49 +08:00
# endif
2026-03-29 09:28:49 +08:00
for (char* token = path; *token; path = token + 1) {
2024-08-23 10:17:06 +08:00
token = strchr(path,
2026-03-29 09:28:49 +08:00
# ifdef _WIN32
';'
# else
':'
# endif
2024-08-23 10:17:06 +08:00
);
2026-03-29 09:28:49 +08:00
if (!token) {
token = path + strlen(path);
}
2024-08-23 10:17:06 +08:00
2026-03-29 09:28:49 +08:00
ffStrbufSetNS(result, (uint32_t) (token - path), path);
ffStrbufEnsureEndsWithC(result,
2026-03-29 09:28:49 +08:00
# ifdef _WIN32
'\\'
# else
'/'
# endif
);
ffStrbufAppendS(result, name);
2026-03-29 09:28:49 +08:00
# ifdef _WIN32
if (appendExe) {
ffStrbufAppendS(result, ".exe");
}
if (!ffPathExists(result->chars, FF_PATHTYPE_FILE)) {
continue;
2026-03-29 09:28:49 +08:00
}
# else
if (access(result->chars, X_OK) != 0) {
continue;
2026-03-29 09:28:49 +08:00
}
# endif
return NULL;
}
ffStrbufClear(result);
return "Executable not found";
}
2025-04-07 16:38:13 +08:00
#else
2026-03-29 09:28:49 +08:00
# include <windows.h>
# include <winioctl.h>
# include <errno.h>
# include <stdalign.h>
2025-04-07 16:38:13 +08:00
2026-03-29 09:28:49 +08:00
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
2025-04-07 16:38:13 +08:00
char buffer[MAX_PATH + 1];
DWORD length = SearchPathA(NULL, name, ".exe", sizeof(buffer), buffer, NULL);
2026-03-29 09:28:49 +08:00
if (length == 0) {
2025-04-07 16:38:13 +08:00
ffStrbufClear(result);
return "Executable not found";
}
ffStrbufSetS(result, buffer);
return NULL;
}
2026-03-29 09:28:49 +08:00
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;
}
}
2026-03-29 09:28:49 +08:00
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 */
2026-03-29 09:28:49 +08:00
DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD) ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED);
2026-03-29 09:28:49 +08:00
if (lenW == 0) {
errno = winerr2Errno(GetLastError());
return NULL;
}
2026-03-29 09:28:49 +08:00
if (lenW >= ARRAY_SIZE(resolvedNameW)) {
errno = E2BIG;
return NULL;
}
lenW++; // Include null terminator
wchar_t* srcW = resolvedNameW;
DWORD srcLenW = lenW;
2026-03-29 09:28:49 +08:00
if (srcLenW >= 8 && wcsncmp(resolvedNameW, L"\\\\?\\UNC\\", 8) == 0) {
/* Convert "\\?\UNC\server\share" to "\\server\share" */
srcW += 6;
srcLenW -= 6;
*srcW = L'\\';
2026-03-29 09:28:49 +08:00
} else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L"\\\\?\\", 4) == 0) {
srcW += 4;
srcLenW -= 4;
}
2026-03-29 09:28:49 +08:00
if (resolved_name) {
ULONG outBytes = 0;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
errno = E2BIG;
return NULL;
}
2026-03-29 09:28:49 +08:00
if (outBytes > MAX_PATH) {
errno = E2BIG;
return NULL;
}
return resolved_name;
2026-03-29 09:28:49 +08:00
} else {
/* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */
char tmp[(MAX_PATH + 4) * 4];
ULONG outBytes = 0;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG) sizeof(tmp), &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
errno = E2BIG;
return NULL;
}
2026-03-29 09:28:49 +08:00
resolved_name = (char*) malloc(outBytes);
if (!resolved_name) {
errno = ENOMEM;
return NULL;
}
memcpy(resolved_name, tmp, outBytes);
return resolved_name;
}
return resolved_name;
}
2026-03-29 09:28:49 +08:00
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;
2026-03-29 09:28:49 +08:00
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);
2026-03-29 09:28:49 +08:00
if (hFile == INVALID_HANDLE_VALUE) {
errno = winerr2Errno(GetLastError());
return NULL;
}
return frealpath(hFile, resolved_name);
}
2026-03-29 09:28:49 +08:00
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;
2026-03-29 09:28:49 +08:00
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;
2026-03-29 09:28:49 +08:00
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;
2026-03-29 09:28:49 +08:00
} else {
targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
(rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength;
if (targetBytes >= 8 &&
2026-03-29 09:28:49 +08:00
wcsncmp(targetW, L"\\??\\", 4) == 0) {
targetW += 4;
targetBytes -= 8;
}
}
2026-03-29 09:28:49 +08:00
} 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;
2026-03-29 09:28:49 +08:00
} else {
targetW = rp->MountPointReparseBuffer.PathBuffer +
(rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength;
if (targetBytes >= 8 &&
2026-03-29 09:28:49 +08:00
wcsncmp(targetW, L"\\??\\", 4) == 0) {
targetW += 4;
targetBytes -= 8;
}
}
2026-03-29 09:28:49 +08:00
} else {
errno = EINVAL;
return -1;
}
ULONG outBytes = 0;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes))) {
errno = E2BIG;
return -1;
}
// Not null-terminated
return (ssize_t) outBytes;
}
2026-03-29 09:28:49 +08:00
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;
2026-03-29 09:28:49 +08:00
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,
2026-03-29 09:28:49 +08:00
NULL);
2026-03-29 09:28:49 +08:00
if (hFile == INVALID_HANDLE_VALUE) {
errno = winerr2Errno(GetLastError());
return -1;
}
return freadlink(hFile, buf, bufsiz);
}
2025-04-07 16:38:13 +08:00
#endif