master ohmu/fastfetch-openbsd / src / common / impl / path.c
ohmu  ·  2026-10-15
  1#include "common/path.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/arrutil.h"
  5
  6#if !_WIN32
  7const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
  8    char* path = getenv("PATH");
  9    if (!path) {
 10        FF_DEBUG("The PATH environment variable is not set");
 11        return "$PATH not set";
 12    }
 13
 14    #ifdef _WIN32
 15    const bool appendExe = !ffStrEndsWithIgnCase(name, ".exe");
 16    #endif
 17
 18    for (char* token = path; *token; path = token + 1) {
 19        token = strchr(path,
 20    #ifdef _WIN32
 21            ';'
 22    #else
 23            ':'
 24    #endif
 25        );
 26        if (!token) {
 27            token = path + strlen(path);
 28        }
 29
 30        ffStrbufSetNS(result, (uint32_t) (token - path), path);
 31        ffStrbufEnsureEndsWithC(result,
 32    #ifdef _WIN32
 33            '\\'
 34    #else
 35            '/'
 36    #endif
 37        );
 38        ffStrbufAppendS(result, name);
 39    #ifdef _WIN32
 40        if (appendExe) {
 41            ffStrbufAppendS(result, ".exe");
 42        }
 43        if (!ffPathExists(result->chars, FF_PATHTYPE_FILE)) {
 44            continue;
 45        }
 46    #else
 47        if (access(result->chars, X_OK) != 0) {
 48            continue;
 49        }
 50    #endif
 51
 52        return nullptr;
 53    }
 54    ffStrbufClear(result);
 55    FF_DEBUG("%s is not executable in any of the %s entries", name, "PATH");
 56    return "Executable not found";
 57}
 58#else
 59    #include <windows.h>
 60    #include <winioctl.h>
 61    #include <errno.h>
 62    #include <stdalign.h>
 63
 64const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
 65    char buffer[MAX_PATH + 1];
 66    DWORD length = SearchPathA(nullptr, name, ".exe", sizeof(buffer), buffer, nullptr);
 67    if (length == 0) {
 68        FF_DEBUG("SearchPathA(%s) failed: %s", name, ffDebugWin32Error(GetLastError()));
 69        ffStrbufClear(result);
 70        return "Executable not found";
 71    }
 72    ffStrbufSetS(result, buffer);
 73    return nullptr;
 74}
 75
 76static inline int winerr2Errno(DWORD err) {
 77    switch (err) {
 78        case ERROR_FILE_NOT_FOUND:
 79        case ERROR_PATH_NOT_FOUND:
 80        case ERROR_INVALID_NAME:
 81            return ENOENT;
 82        case ERROR_ACCESS_DENIED:
 83        case ERROR_SHARING_VIOLATION:
 84        case ERROR_LOCK_VIOLATION:
 85            return EACCES;
 86        case ERROR_BUFFER_OVERFLOW:
 87        case ERROR_INSUFFICIENT_BUFFER:
 88            return ENAMETOOLONG;
 89        case ERROR_INVALID_PARAMETER:
 90        case ERROR_NOT_A_REPARSE_POINT:
 91            return EINVAL;
 92        default:
 93            return EIO;
 94    }
 95}
 96
 97char* frealpath(HANDLE hFile, char* resolved_name) {
 98    if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !hFile, false)) {
 99        errno = EINVAL;
100        return nullptr;
101    }
102
103    wchar_t resolvedNameW[MAX_PATH + 4]; /* +4 for "\\\\?\\" prefix */
104    DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD) ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED);
105
106    if (lenW == 0) {
107        errno = winerr2Errno(GetLastError());
108        return nullptr;
109    }
110    if (lenW >= ARRAY_SIZE(resolvedNameW)) {
111        errno = E2BIG;
112        return nullptr;
113    }
114    lenW++; // Include null terminator
115
116    wchar_t* srcW = resolvedNameW;
117    DWORD srcLenW = lenW;
118
119    if (srcLenW >= 8 && wcsncmp(resolvedNameW, L"\\\\?\\UNC\\", 8) == 0) {
120        /* Convert "\\?\UNC\server\share" to "\\server\share" */
121        srcW += 6;
122        srcLenW -= 6;
123        *srcW = L'\\';
124    } else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L"\\\\?\\", 4) == 0) {
125        srcW += 4;
126        srcLenW -= 4;
127    }
128
129    if (resolved_name) {
130        ULONG outBytes = 0;
131        if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
132            errno = E2BIG;
133            return nullptr;
134        }
135    } else {
136        /* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */
137        char tmp[(MAX_PATH + 4) * 4];
138        ULONG outBytes = 0;
139
140        if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG) sizeof(tmp), &outBytes, srcW, (ULONG) (srcLenW * sizeof(wchar_t))))) {
141            errno = E2BIG;
142            return nullptr;
143        }
144
145        resolved_name = (char*) malloc(outBytes);
146        if (!resolved_name) {
147            errno = ENOMEM;
148            return nullptr;
149        }
150
151        memcpy(resolved_name, tmp, outBytes);
152    }
153
154    return resolved_name;
155}
156
157char* realpath(const char* __restrict file_name, char* __restrict resolved_name) {
158    if (!file_name) {
159        errno = EINVAL;
160        return nullptr;
161    }
162
163    wchar_t fileNameW[MAX_PATH];
164    ULONG lenBytes = 0;
165
166    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &lenBytes, file_name, (ULONG) strlen(file_name) + 1))) {
167        errno = EINVAL;
168        return nullptr;
169    }
170
171    FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
172        fileNameW,
173        0,
174        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
175        nullptr,
176        OPEN_EXISTING,
177        FILE_FLAG_BACKUP_SEMANTICS,
178        nullptr);
179
180    if (hFile == INVALID_HANDLE_VALUE) {
181        errno = winerr2Errno(GetLastError());
182        return nullptr;
183    }
184
185    return frealpath(hFile, resolved_name);
186}
187
188ssize_t freadlink(HANDLE hFile, char* buf, size_t bufsiz) {
189    if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !buf || bufsiz == 0, false)) {
190        errno = EINVAL;
191        return -1;
192    }
193
194    alignas(REPARSE_DATA_BUFFER) BYTE reparseBuf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
195    DWORD bytesReturned = 0;
196    if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, nullptr, 0, reparseBuf, (DWORD) sizeof(reparseBuf), &bytesReturned, nullptr)) {
197        errno = winerr2Errno(GetLastError());
198        return -1;
199    }
200
201    REPARSE_DATA_BUFFER* rp = (REPARSE_DATA_BUFFER*) reparseBuf;
202    const wchar_t* targetW = nullptr;
203    USHORT targetBytes = 0;
204
205    if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
206        if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 0) {
207            targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
208                (rp->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t));
209            targetBytes = rp->SymbolicLinkReparseBuffer.PrintNameLength;
210        } else {
211            targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
212                (rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
213            targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength;
214
215            if (targetBytes >= 8 &&
216                wcsncmp(targetW, L"\\??\\", 4) == 0) {
217                targetW += 4;
218                targetBytes -= 8;
219            }
220        }
221    } else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
222        if (rp->MountPointReparseBuffer.PrintNameLength > 0) {
223            targetW = rp->MountPointReparseBuffer.PathBuffer +
224                (rp->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t));
225            targetBytes = rp->MountPointReparseBuffer.PrintNameLength;
226        } else {
227            targetW = rp->MountPointReparseBuffer.PathBuffer +
228                (rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
229            targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength;
230
231            if (targetBytes >= 8 &&
232                wcsncmp(targetW, L"\\??\\", 4) == 0) {
233                targetW += 4;
234                targetBytes -= 8;
235            }
236        }
237    } else {
238        errno = EINVAL;
239        return -1;
240    }
241
242    ULONG outBytes = 0;
243    if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes))) {
244        errno = E2BIG;
245        return -1;
246    }
247
248    // Not null-terminated
249    return (ssize_t) outBytes;
250}
251
252ssize_t readlink(const char* path, char* buf, size_t bufsiz) {
253    if (!path || !buf || bufsiz == 0) {
254        errno = EINVAL;
255        return -1;
256    }
257
258    wchar_t pathW[MAX_PATH];
259    ULONG pathWBytes = 0;
260    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), &pathWBytes, path, (ULONG) strlen(path) + 1))) {
261        errno = EINVAL;
262        return -1;
263    }
264
265    FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
266        pathW,
267        0,
268        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
269        nullptr,
270        OPEN_EXISTING,
271        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
272        nullptr);
273
274    if (hFile == INVALID_HANDLE_VALUE) {
275        errno = winerr2Errno(GetLastError());
276        return -1;
277    }
278
279    return freadlink(hFile, buf, bufsiz);
280}
281#endif