Skip to content

Commit 4e234c5

Browse files
committed
Platform (Windows): adds implementation of POSIX readlink(2) and realpath(3)
1 parent edc718d commit 4e234c5

5 files changed

Lines changed: 277 additions & 22 deletions

File tree

src/common/impl/FFPlatform_windows.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static void getExePath(FFPlatform* platform)
2828
NULL);
2929
if (hPath != INVALID_HANDLE_VALUE)
3030
{
31-
DWORD len = GetFinalPathNameByHandleW(hPath, exePathW, MAX_PATH, FILE_NAME_OPENED);
31+
DWORD len = GetFinalPathNameByHandleW(hPath, exePathW, MAX_PATH, FILE_NAME_NORMALIZED);
3232
if (len > 0 && len < MAX_PATH)
3333
{
3434
ffStrbufSetNWS(&platform->exePath, len, exePathW);

src/common/impl/path.c

Lines changed: 260 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "common/path.h"
22
#include "common/io.h"
3-
#include "common/stringUtils.h"
3+
#include "common/arrayUtils.h"
44

55
#if !_WIN32
66
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result)
@@ -49,6 +49,9 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result)
4949
}
5050
#else
5151
#include <windows.h>
52+
#include <winioctl.h>
53+
#include <errno.h>
54+
#include <stdalign.h>
5255

5356
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result)
5457
{
@@ -62,4 +65,260 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result)
6265
ffStrbufSetS(result, buffer);
6366
return NULL;
6467
}
68+
69+
static inline int winerr2Errno(DWORD err)
70+
{
71+
switch (err)
72+
{
73+
case ERROR_FILE_NOT_FOUND:
74+
case ERROR_PATH_NOT_FOUND:
75+
case ERROR_INVALID_NAME:
76+
return ENOENT;
77+
case ERROR_ACCESS_DENIED:
78+
case ERROR_SHARING_VIOLATION:
79+
case ERROR_LOCK_VIOLATION:
80+
return EACCES;
81+
case ERROR_BUFFER_OVERFLOW:
82+
case ERROR_INSUFFICIENT_BUFFER:
83+
return ENAMETOOLONG;
84+
case ERROR_INVALID_PARAMETER:
85+
case ERROR_NOT_A_REPARSE_POINT:
86+
return EINVAL;
87+
default:
88+
return EIO;
89+
}
90+
}
91+
92+
char* frealpath(HANDLE hFile, char* resolved_name)
93+
{
94+
if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !hFile, false))
95+
{
96+
errno = EINVAL;
97+
return NULL;
98+
}
99+
100+
wchar_t resolvedNameW[MAX_PATH + 4]; /* +4 for "\\\\?\\" prefix */
101+
DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD)ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED);
102+
103+
if (lenW == 0)
104+
{
105+
errno = winerr2Errno(GetLastError());
106+
return NULL;
107+
}
108+
if (lenW >= ARRAY_SIZE(resolvedNameW))
109+
{
110+
errno = E2BIG;
111+
return NULL;
112+
}
113+
lenW++; // Include null terminator
114+
115+
wchar_t* srcW = resolvedNameW;
116+
DWORD srcLenW = lenW;
117+
118+
if (srcLenW >= 8 && wcsncmp(resolvedNameW, L"\\\\?\\UNC\\", 8) == 0)
119+
{
120+
/* Convert "\\?\UNC\server\share" to "\\server\share" */
121+
srcW += 6;
122+
srcLenW -= 6;
123+
*srcW = L'\\';
124+
}
125+
else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L"\\\\?\\", 4) == 0)
126+
{
127+
srcW += 4;
128+
srcLenW -= 4;
129+
}
130+
131+
if (resolved_name)
132+
{
133+
ULONG outBytes = 0;
134+
if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t)))))
135+
{
136+
errno = E2BIG;
137+
return NULL;
138+
}
139+
140+
if (outBytes > MAX_PATH)
141+
{
142+
errno = E2BIG;
143+
return NULL;
144+
}
145+
146+
return resolved_name;
147+
}
148+
else
149+
{
150+
/* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */
151+
char tmp[(MAX_PATH + 4) * 4];
152+
ULONG outBytes = 0;
153+
154+
if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG)sizeof(tmp), &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t)))))
155+
{
156+
errno = E2BIG;
157+
return NULL;
158+
}
159+
160+
resolved_name = (char*)malloc(outBytes);
161+
if (!resolved_name)
162+
{
163+
errno = ENOMEM;
164+
return NULL;
165+
}
166+
167+
memcpy(resolved_name, tmp, outBytes);
168+
return resolved_name;
169+
}
170+
171+
return resolved_name;
172+
}
173+
174+
char* realpath(const char* __restrict file_name, char* __restrict resolved_name)
175+
{
176+
if (!file_name)
177+
{
178+
errno = EINVAL;
179+
return NULL;
180+
}
181+
182+
wchar_t fileNameW[MAX_PATH];
183+
ULONG lenBytes = 0;
184+
185+
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG)sizeof(fileNameW), &lenBytes, file_name, (ULONG)strlen(file_name) + 1)))
186+
{
187+
errno = EINVAL;
188+
return NULL;
189+
}
190+
191+
FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
192+
fileNameW,
193+
0,
194+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
195+
NULL,
196+
OPEN_EXISTING,
197+
FILE_FLAG_BACKUP_SEMANTICS,
198+
NULL);
199+
200+
if (hFile == INVALID_HANDLE_VALUE)
201+
{
202+
errno = winerr2Errno(GetLastError());
203+
return NULL;
204+
}
205+
206+
return frealpath(hFile, resolved_name);
207+
}
208+
209+
ssize_t freadlink(HANDLE hFile, char* buf, size_t bufsiz)
210+
{
211+
if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !buf || bufsiz == 0, false))
212+
{
213+
errno = EINVAL;
214+
return -1;
215+
}
216+
217+
alignas(REPARSE_DATA_BUFFER) BYTE reparseBuf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
218+
DWORD bytesReturned = 0;
219+
if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, reparseBuf, (DWORD) sizeof(reparseBuf), &bytesReturned, NULL))
220+
{
221+
errno = winerr2Errno(GetLastError());
222+
return -1;
223+
}
224+
225+
REPARSE_DATA_BUFFER* rp = (REPARSE_DATA_BUFFER*) reparseBuf;
226+
const wchar_t* targetW = NULL;
227+
USHORT targetBytes = 0;
228+
229+
if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
230+
{
231+
if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 0)
232+
{
233+
targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
234+
(rp->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t));
235+
targetBytes = rp->SymbolicLinkReparseBuffer.PrintNameLength;
236+
}
237+
else
238+
{
239+
targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +
240+
(rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
241+
targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength;
242+
243+
if (targetBytes >= 8 &&
244+
wcsncmp(targetW, L"\\??\\", 4) == 0)
245+
{
246+
targetW += 4;
247+
targetBytes -= 8;
248+
}
249+
}
250+
}
251+
else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
252+
{
253+
if (rp->MountPointReparseBuffer.PrintNameLength > 0)
254+
{
255+
targetW = rp->MountPointReparseBuffer.PathBuffer +
256+
(rp->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t));
257+
targetBytes = rp->MountPointReparseBuffer.PrintNameLength;
258+
}
259+
else
260+
{
261+
targetW = rp->MountPointReparseBuffer.PathBuffer +
262+
(rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
263+
targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength;
264+
265+
if (targetBytes >= 8 &&
266+
wcsncmp(targetW, L"\\??\\", 4) == 0)
267+
{
268+
targetW += 4;
269+
targetBytes -= 8;
270+
}
271+
}
272+
}
273+
else
274+
{
275+
errno = EINVAL;
276+
return -1;
277+
}
278+
279+
ULONG outBytes = 0;
280+
if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes)))
281+
{
282+
errno = E2BIG;
283+
return -1;
284+
}
285+
286+
// Not null-terminated
287+
return (ssize_t) outBytes;
288+
}
289+
290+
ssize_t readlink(const char* path, char* buf, size_t bufsiz)
291+
{
292+
if (!path || !buf || bufsiz == 0)
293+
{
294+
errno = EINVAL;
295+
return -1;
296+
}
297+
298+
wchar_t pathW[MAX_PATH];
299+
ULONG pathWBytes = 0;
300+
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), &pathWBytes, path, (ULONG) strlen(path) + 1)))
301+
{
302+
errno = EINVAL;
303+
return -1;
304+
}
305+
306+
FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(
307+
pathW,
308+
0,
309+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
310+
NULL,
311+
OPEN_EXISTING,
312+
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
313+
NULL
314+
);
315+
316+
if (hFile == INVALID_HANDLE_VALUE)
317+
{
318+
errno = winerr2Errno(GetLastError());
319+
return -1;
320+
}
321+
322+
return freadlink(hFile, buf, bufsiz);
323+
}
65324
#endif

src/common/path.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ static inline bool ffIsAbsolutePath(const char* path)
1313
return path[0] == '/';
1414
#endif
1515
}
16+
17+
#if _WIN32
18+
char* frealpath(void* __restrict hFile, char* __restrict resolved_name /*MAX_PATH*/);
19+
char* realpath(const char* __restrict file_name, char* __restrict resolved_name /*MAX_PATH*/);
20+
ssize_t freadlink(void* hFile, char* buf, size_t bufsiz);
21+
ssize_t readlink(const char* path, char* buf, size_t bufsiz);
22+
#endif

src/detection/editor/editor.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@
77

88
#include <stdlib.h>
99

10-
#ifdef _WIN32
11-
static inline char* realpath(const char* restrict file_name, char* restrict resolved_name)
12-
{
13-
assert(resolved_name != NULL);
14-
return _fullpath(resolved_name, file_name, _MAX_PATH);
15-
}
16-
#endif
17-
1810
static bool extractNvimVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata)
1911
{
2012
if (!ffStrStartsWith(str, "NVIM v")) return true;

src/logo/image/image.c

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -407,17 +407,7 @@ static bool printImageKittyDirect(bool printError)
407407
#include <sys/ioctl.h>
408408
#else
409409
#include <wincon.h>
410-
411-
static inline char* realpath(const char* restrict file_name, char* restrict resolved_name)
412-
{
413-
char* result = _fullpath(resolved_name, file_name, _MAX_PATH);
414-
if(result)
415-
{
416-
resolved_name[1] = resolved_name[0]; // Drive Name
417-
resolved_name[0] = '/';
418-
}
419-
return result;
420-
}
410+
#include "common/path.h"
421411
#endif
422412

423413
#ifdef FF_HAVE_ZLIB
@@ -1002,14 +992,21 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError)
1002992
ffStrbufAppendS(&requestData.cacheDir, "fastfetch/images");
1003993

1004994
ffStrbufEnsureFree(&requestData.cacheDir, PATH_MAX);
1005-
if(realpath(instance.config.logo.source.chars, requestData.cacheDir.chars + requestData.cacheDir.length) == NULL)
995+
char* filePath = requestData.cacheDir.chars + requestData.cacheDir.length;
996+
if(realpath(instance.config.logo.source.chars, filePath) == NULL)
1006997
{
1007998
//We can safely return here, because if realpath failed, we surely won't be able to read the file
1008999
ffStrbufDestroy(&requestData.cacheDir);
10091000
if(printError)
10101001
fputs("Logo: Querying realpath of the image source failed\n", stderr);
10111002
return false;
10121003
}
1004+
1005+
#ifdef _WIN32
1006+
filePath[1] = filePath[0]; // Drive Name
1007+
filePath[0] = '/';
1008+
#endif
1009+
10131010
ffStrbufRecalculateLength(&requestData.cacheDir);
10141011
ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/');
10151012
ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight);

0 commit comments

Comments
 (0)