Nuitka
The Python compiler
Loading...
Searching...
No Matches
HelpersFilesystemPaths.c
1// Copyright 2025, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file
2
3// Tools for working with file, and paths cross platform
4// for use in both onefile bootstrap and python compiled
5// program.
6
7// This file is included from another C file, help IDEs to still parse it on
8// its own.
9#ifdef __IDE_ONLY__
10#include "nuitka/prelude.h"
11#endif
12
13#if defined(__FreeBSD__) || defined(__OpenBSD__)
14#include <sys/sysctl.h>
15#endif
16
17#if !defined(_WIN32)
18#include <dlfcn.h>
19#include <fcntl.h>
20#include <libgen.h>
21#if !defined(__wasi__)
22#include <pwd.h>
23#endif
24#include <stdlib.h>
25#include <strings.h>
26#include <sys/mman.h>
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <unistd.h>
30#endif
31
32#if defined(__APPLE__)
33#include <mach-o/dyld.h>
34#endif
35
36// We are using in onefile bootstrap as well, so copy it.
37#ifndef Py_MIN
38#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
39#endif
40
41#include "nuitka/environment_variables_system.h"
42#include "nuitka/filesystem_paths.h"
43#include "nuitka/safe_string_ops.h"
44
45void normalizePath(filename_char_t *filename) {
46 filename_char_t *w = filename;
47
48 while (*w != 0) {
49 // Eliminate duplicate slashes.
50 if (*w == FILENAME_SEP_CHAR) {
51 while (*(w + 1) == FILENAME_SEP_CHAR) {
52 filename_char_t *f = w;
53
54 for (;;) {
55 *f = *(f + 1);
56
57 if (*f == 0) {
58 break;
59 }
60
61 f++;
62 }
63 }
64 }
65
66 w++;
67 }
68
69 // TODO: Need to also remove "./" and resolve "/../" sequences for best
70 // results.
71}
72
73#if defined(_WIN32)
74// Replacement for RemoveFileSpecW, slightly smaller, avoids a link library.
75static wchar_t *stripFilenameW(wchar_t *path) {
76 wchar_t *last_slash = NULL;
77
78 while (*path != 0) {
79 if (*path == L'\\') {
80 last_slash = path;
81 }
82
83 path++;
84 }
85
86 if (last_slash != NULL) {
87 *last_slash = 0;
88 }
89
90 return last_slash;
91}
92
93filename_char_t *stripBaseFilename(filename_char_t const *filename) {
94 static wchar_t result[MAXPATHLEN + 1];
95
96 copyStringSafeW(result, filename, sizeof(result) / sizeof(wchar_t));
97 stripFilenameW(result);
98
99 return result;
100}
101#else
102filename_char_t *stripBaseFilename(filename_char_t const *filename) {
103 static char result[MAXPATHLEN + 1];
104 copyStringSafe(result, filename, sizeof(result));
105
106 return dirname(result);
107}
108#endif
109
110#if defined(_WIN32)
111static void makeShortFilename(wchar_t *filename, size_t buffer_size) {
112#ifndef _NUITKA_EXPERIMENTAL_AVOID_SHORT_PATH
113 // Query length of result first.
114 DWORD length = GetShortPathNameW(filename, NULL, 0);
115 if (length == 0) {
116 return;
117 }
118
119 wchar_t *short_filename = (wchar_t *)malloc((length + 1) * sizeof(wchar_t));
120 DWORD res = GetShortPathNameW(filename, short_filename, length);
121 assert(res != 0);
122
123 if (unlikely(res > length)) {
124 abort();
125 }
126
127 filename[0] = 0;
128 appendWStringSafeW(filename, short_filename, buffer_size);
129
130 free(short_filename);
131#endif
132}
133
134static void makeShortDirFilename(wchar_t *filename, size_t buffer_size) {
135 wchar_t *changed = stripFilenameW(filename);
136 if (changed != NULL) {
137 changed = wcsdup(changed + 1);
138 }
139
140 // Shorten only the directory name.
141 makeShortFilename(filename, buffer_size);
142
143 if (changed != NULL) {
144 appendWCharSafeW(filename, FILENAME_SEP_CHAR, buffer_size);
145 appendWStringSafeW(filename, changed, buffer_size);
146
147 free(changed);
148 }
149}
150
151#endif
152
153#if !defined(_WIN32)
154filename_char_t *_getBinaryPath2(void) {
155 static filename_char_t binary_filename[MAXPATHLEN] = {0};
156 const size_t buffer_size = sizeof(binary_filename);
157
158 if (binary_filename[0] != 0) {
159 return binary_filename;
160 }
161
162#if defined(__APPLE__)
163 uint32_t bufsize = buffer_size;
164 int res = _NSGetExecutablePath(binary_filename, &bufsize);
165
166 if (unlikely(res != 0)) {
167 abort();
168 }
169#elif defined(__OpenBSD__) || defined(_AIX) || defined(_NUITKA_EXPERIMENTAL_FORCE_UNIX_BINARY_NAME)
170#if _NUITKA_DLL_MODE || _NUITKA_MODULE_MODE
171 const char *comm = "invalid";
172 NUITKA_CANNOT_GET_HERE("Cannot query program name on this OS. Please help adding that.");
173#else
174 const char *comm = getOriginalArgv0();
175#endif
176
177 bool success = false;
178
179 if (*comm == '/') {
180 copyStringSafe(binary_filename, comm, buffer_size);
181 success = true;
182 } else {
183 if (getcwd(binary_filename, buffer_size) == NULL) {
184 abort();
185 }
186 // Add a separator either way, later removed.
187 appendCharSafe(binary_filename, '/', buffer_size);
188 appendStringSafe(binary_filename, comm, buffer_size);
189
190 if (isExecutableFile(binary_filename)) {
191 success = true;
192 } else {
193 char *path_environment_value = strdup(getenv("PATH"));
194
195 if (path_environment_value != NULL) {
196 char *sp;
197 char *path = strtok_r(path_environment_value, ":", &sp);
198
199 while (path != NULL) {
200 if (*path != '/') {
201 if (getcwd(binary_filename, buffer_size) == NULL) {
202 abort();
203 }
204
205 appendCharSafe(binary_filename, '/', buffer_size);
206 } else {
207 binary_filename[0] = 0;
208 }
209 appendStringSafe(binary_filename, path, buffer_size);
210 appendCharSafe(binary_filename, '/', buffer_size);
211 appendStringSafe(binary_filename, comm, buffer_size);
212
213 if (isExecutableFile(binary_filename)) {
214 success = true;
215 break;
216 }
217
218 path = strtok_r(NULL, ":", &sp);
219 }
220
221 free(path_environment_value);
222 }
223 }
224 }
225
226 if (success == true) {
227 // fprintf(stderr, "Did resolve binary path %s from PATH %s.\n", comm, binary_filename);
228
229 // TODO: Once it's fully capable, we ought to use this for all methods
230 // for consistency.
231 normalizePath(binary_filename);
232 // fprintf(stderr, "Did normalize binary path %s from PATH %s.\n", comm, binary_filename);
233 } else {
234 fprintf(stderr, "Error, cannot resolve binary path %s from PATH or current directory.\n", comm);
235 abort();
236 }
237#elif defined(__FreeBSD__)
238 /* Not all of FreeBSD has /proc file system, so use the appropriate
239 * "sysctl" instead.
240 */
241 int mib[4];
242 mib[0] = CTL_KERN;
243 mib[1] = KERN_PROC;
244 mib[2] = KERN_PROC_PATHNAME;
245 mib[3] = -1;
246 size_t cb = buffer_size;
247 int res = sysctl(mib, 4, binary_filename, &cb, NULL, 0);
248
249 if (unlikely(res != 0)) {
250 abort();
251 }
252#elif defined(__wasi__)
253 const char *wasi_filename = "program.wasm";
254 copyStringSafe(binary_filename, wasi_filename, buffer_size);
255#else
256 /* The remaining platforms, mostly Linux or compatible. */
257
258 /* The "readlink" call does not terminate result, so fill zeros there, then
259 * it is a proper C string right away. */
260 memset(binary_filename, 0, buffer_size);
261 ssize_t res = readlink("/proc/self/exe", binary_filename, buffer_size - 1);
262
263 if (unlikely(res == -1)) {
264 abort();
265 }
266#endif
267 return binary_filename;
268}
269#endif
270
271filename_char_t const *getBinaryPath(void) {
272#if defined(_WIN32)
273 static filename_char_t binary_filename[MAXPATHLEN] = {0};
274
275 if (binary_filename[0] != 0) {
276 return binary_filename;
277 }
278
279 DWORD res = GetModuleFileNameW(NULL, binary_filename, sizeof(binary_filename) / sizeof(wchar_t));
280 if (unlikely(res == 0)) {
281 abort();
282 }
283
284 return binary_filename;
285#else
286 return _getBinaryPath2();
287#endif
288}
289
290bool readFileChunk(FILE_HANDLE file_handle, void *buffer, size_t size) {
291 // printf("Reading %d\n", size);
292
293#if defined(_WIN32)
294 DWORD read_size;
295 BOOL bool_res = ReadFile(file_handle, buffer, (DWORD)size, &read_size, NULL);
296
297 return bool_res && (read_size == size);
298#else
299 size_t read_size = fread(buffer, 1, size, file_handle);
300
301 return read_size == size;
302#endif
303}
304
305bool writeFileChunk(FILE_HANDLE target_file, void const *chunk, size_t chunk_size) {
306#if defined(_WIN32)
307 DWORD write_size = 0;
308 return WriteFile(target_file, chunk, (DWORD)chunk_size, &write_size, NULL);
309#else
310 size_t written = fwrite(chunk, 1, chunk_size, target_file);
311 return written == chunk_size;
312#endif
313}
314
315FILE_HANDLE createFileForWriting(filename_char_t const *filename) {
316#if defined(_WIN32)
317 FILE_HANDLE result = CreateFileW(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
318#else
319 FILE *result = fopen(filename, "wb");
320#endif
321
322 return result;
323}
324
325FILE_HANDLE openFileForReading(filename_char_t const *filename) {
326#if defined(_WIN32)
327 FILE_HANDLE result = CreateFileW(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
328#else
329 FILE *result = fopen(filename, "rb");
330#endif
331
332 return result;
333}
334
335bool closeFile(FILE_HANDLE target_file) {
336#if defined(_WIN32)
337 CloseHandle(target_file);
338 return true;
339#else
340 int r = fclose(target_file);
341
342 return r == 0;
343#endif
344}
345
346int64_t getFileSize(FILE_HANDLE file_handle) {
347#if defined(_WIN32)
348 // TODO: File size is truncated here, but maybe an OK thing.
349 DWORD file_size = GetFileSize(file_handle, NULL);
350
351 if (file_size == INVALID_FILE_SIZE) {
352 return -1;
353 }
354#else
355 int res = fseek(file_handle, 0, SEEK_END);
356
357 if (unlikely(res != 0)) {
358 return -1;
359 }
360
361 long file_size = ftell(file_handle);
362
363 res = fseek(file_handle, 0, SEEK_SET);
364
365 if (unlikely(res != 0)) {
366 return -1;
367 }
368#endif
369
370 return (int64_t)file_size;
371}
372
373#if !defined(_WIN32)
374#if defined(__APPLE__)
375#include <copyfile.h>
376#else
377#if defined(__MSYS__) || defined(__HAIKU__) || defined(__OpenBSD__) || defined(_AIX) || defined(__wasi__)
378static bool sendfile(int output_file, int input_file, off_t *bytesCopied, size_t count) {
379 char buffer[32768];
380
381 *bytesCopied = 0;
382
383 while (count > 0) {
384 ssize_t read_bytes = read(input_file, buffer, Py_MIN(sizeof(buffer), count));
385
386 if (unlikely(read <= 0)) {
387 return false;
388 }
389
390 count -= read_bytes;
391
392 ssize_t written_bytes = write(output_file, buffer, read_bytes);
393
394 if (unlikely(written_bytes != read_bytes)) {
395 return false;
396 }
397
398 *bytesCopied += written_bytes;
399 }
400
401 return true;
402}
403#elif !defined(__FreeBSD__)
404#include <sys/sendfile.h>
405#endif
406#endif
407#endif
408
409#if !defined(_WIN32)
410bool isExecutableFile(filename_char_t const *filename) {
411 int mode = getFileMode(filename);
412
413 if (mode == -1) {
414 return false;
415 }
416 return mode & S_IXUSR;
417}
418#endif
419
420int getFileMode(filename_char_t const *filename) {
421#if !defined(_WIN32)
422 struct stat fileinfo = {0};
423 if (stat(filename, &fileinfo) == -1) {
424 return -1;
425 }
426
427 return fileinfo.st_mode;
428#else
429 // There is no mode on Windows, but copyFile calls should get it.
430 return 0;
431#endif
432}
433
434bool copyFile(filename_char_t const *source, filename_char_t const *dest, int mode) {
435#if !defined(_WIN32)
436 int input_file = open(source, O_RDONLY);
437
438 if (input_file == -1) {
439 return false;
440 }
441
442 int output_file = creat(dest, mode);
443
444 if (output_file == -1) {
445 close(input_file);
446 return false;
447 }
448
449#if defined(__APPLE__)
450 // Use fcopyfile works on FreeBSD and macOS
451 bool result = fcopyfile(input_file, output_file, 0, COPYFILE_ALL) == 0;
452#elif defined(__FreeBSD__)
453 struct stat input_fileinfo = {0};
454 fstat(input_file, &input_fileinfo);
455 off_t bytesCopied = 0;
456
457 bool result = sendfile(output_file, input_file, 0, input_fileinfo.st_size, 0, &bytesCopied, 0);
458#else
459 // sendfile will work with on Linux 2.6.33+
460 struct stat fileinfo = {0};
461 fstat(input_file, &fileinfo);
462
463 off_t bytesCopied = 0;
464 bool result = sendfile(output_file, input_file, &bytesCopied, fileinfo.st_size) != -1;
465#endif
466
467 close(input_file);
468 close(output_file);
469
470 return result;
471#else
472 return CopyFileW(source, dest, 0) != 0;
473#endif
474}
475
476bool deleteFile(filename_char_t const *filename) {
477#if defined(_WIN32)
478 return DeleteFileW(filename) != 0;
479#else
480 return unlink(filename) == 0;
481#endif
482}
483
484bool renameFile(filename_char_t const *source, filename_char_t const *dest) {
485// spell-checker: ignore _wrename
486#if defined(_WIN32)
487 return _wrename(source, dest) == 0;
488#else
489 return rename(source, dest) == 0;
490#endif
491}
492
493#include "nuitka/checksum_tools.h"
494
495extern error_code_t getLastErrorCode(void) {
496#if defined(_WIN32)
497 return GetLastError();
498#else
499 return errno;
500#endif
501}
502
503#if defined(_WIN32)
504struct MapFileToMemoryInfo {
505 bool error;
506 DWORD error_code;
507 char const *erroring_function;
508 unsigned char const *data;
509 HANDLE file_handle;
510 int64_t file_size;
511 HANDLE handle_mapping;
512};
513
514static struct MapFileToMemoryInfo mapFileToMemory(filename_char_t const *filename) {
515 struct MapFileToMemoryInfo result;
516
517 result.file_handle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
518 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
519
520 if (result.file_handle == INVALID_HANDLE_VALUE) {
521 result.error = true;
522 result.error_code = GetLastError();
523 result.erroring_function = "CreateFileW";
524
525 return result;
526 }
527
528 result.file_size = getFileSize(result.file_handle);
529
530 if (result.file_size == -1) {
531 result.error = true;
532 result.error_code = GetLastError();
533 result.erroring_function = "GetFileSize";
534
535 CloseHandle(result.file_handle);
536
537 return result;
538 }
539
540 result.handle_mapping = CreateFileMappingW(result.file_handle, NULL, PAGE_READONLY, 0, 0, NULL);
541
542 if (result.handle_mapping == NULL) {
543 result.error = true;
544 result.error_code = GetLastError();
545 result.erroring_function = "CreateFileMappingW";
546
547 CloseHandle(result.file_handle);
548
549 return result;
550 }
551
552 result.data = (unsigned char const *)MapViewOfFile(result.handle_mapping, FILE_MAP_READ, 0, 0, 0);
553
554 if (unlikely(result.data == NULL)) {
555 result.error = true;
556 result.error_code = GetLastError();
557 result.erroring_function = "MapViewOfFile";
558
559 CloseHandle(result.handle_mapping);
560 CloseHandle(result.file_handle);
561
562 return result;
563 }
564
565 result.error = false;
566 result.error_code = 0;
567
568 return result;
569}
570
571static void unmapFileFromMemory(struct MapFileToMemoryInfo *mapped_file) {
572 assert(!mapped_file->error);
573
574 UnmapViewOfFile(mapped_file->data);
575 CloseHandle(mapped_file->handle_mapping);
576 CloseHandle(mapped_file->file_handle);
577}
578#else
579
581 bool error;
582 int error_code;
583 char const *erroring_function;
584 unsigned char const *data;
585 int file_handle;
586 int64_t file_size;
587};
588
589static struct MapFileToMemoryInfo mapFileToMemory(filename_char_t const *filename) {
590 struct MapFileToMemoryInfo result;
591
592 result.file_handle = open(filename, O_RDONLY);
593
594 if (unlikely(result.file_handle == -1)) {
595 result.error = true;
596 result.error_code = errno;
597 result.erroring_function = "open";
598 result.file_size = -1;
599 return result;
600 }
601
602 result.file_size = lseek(result.file_handle, 0, SEEK_END);
603 if (unlikely(result.file_size == -1)) {
604 result.error = true;
605 result.error_code = errno;
606 result.erroring_function = "lseek";
607
608 close(result.file_handle);
609
610 return result;
611 }
612 off_t res = lseek(result.file_handle, 0, SEEK_SET);
613
614 if (unlikely(res == -1)) {
615 result.error = true;
616 result.error_code = errno;
617 result.erroring_function = "lseek";
618
619 close(result.file_handle);
620
621 return result;
622 }
623
624 result.data = (unsigned char const *)mmap(NULL, result.file_size, PROT_READ, MAP_PRIVATE, result.file_handle, 0);
625
626 if (unlikely(result.data == MAP_FAILED)) {
627 result.error = true;
628 result.error_code = errno;
629 result.erroring_function = "mmap";
630
631 close(result.file_handle);
632
633 return result;
634 }
635
636 result.error = false;
637 return result;
638}
639
640static void unmapFileFromMemory(struct MapFileToMemoryInfo *mapped_file) {
641 assert(!mapped_file->error);
642
643 munmap((void *)mapped_file->data, mapped_file->file_size);
644 close(mapped_file->file_handle);
645}
646#endif
647
648uint32_t getFileCRC32(filename_char_t const *filename) {
649 struct MapFileToMemoryInfo mapped_file = mapFileToMemory(filename);
650
651 if (mapped_file.error) {
652 return 0;
653 }
654 uint32_t result = calcCRC32(mapped_file.data, (long)mapped_file.file_size);
655
656 // Lets reserve "0" value for error indication.
657 if (result == 0) {
658 result = 1;
659 }
660
661 unmapFileFromMemory(&mapped_file);
662
663 return result;
664}
665
666#ifdef _WIN32
667
668static DWORD Nuitka_GetFinalPathNameByHandleW(HANDLE hFile, LPWSTR FilePath, DWORD cchFilePath, DWORD dwFlags) {
669 typedef DWORD(WINAPI * pfnGetFinalPathNameByHandleW)(HANDLE hFile, LPWSTR FilePath, DWORD cchFilePath,
670 DWORD dwFlags);
671
672 pfnGetFinalPathNameByHandleW fnGetFinalPathNameByHandleW =
673 (pfnGetFinalPathNameByHandleW)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetFinalPathNameByHandleW");
674
675 if (fnGetFinalPathNameByHandleW != NULL) {
676 return fnGetFinalPathNameByHandleW(hFile, FilePath, cchFilePath, dwFlags);
677 } else {
678 // There are no symlinks before Windows Vista.
679 return 0;
680 }
681}
682
683static void resolveFileSymbolicLink(wchar_t *resolved_filename, wchar_t const *filename, DWORD resolved_filename_size,
684 bool resolve_symlinks) {
685 // Resolve any symbolic links in the filename.
686 // Copies the resolved path over the top of the parameter.
687
688 if (resolve_symlinks) {
689 // Open the file in the most non-exclusive way possible
690 HANDLE file_handle = CreateFileW(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
691 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
692
693 if (unlikely(file_handle == INVALID_HANDLE_VALUE)) {
694 abort();
695 }
696
697 // In case, the Windows API for symlinks does not yet exist, just used
698 // the unresolved one.
699 copyStringSafeW(resolved_filename, filename, resolved_filename_size);
700
701 // Resolve the path, get the result with a drive letter
702 DWORD len = Nuitka_GetFinalPathNameByHandleW(file_handle, resolved_filename, resolved_filename_size,
703 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
704
705 CloseHandle(file_handle);
706
707 if (unlikely(len >= resolved_filename_size)) {
708 abort();
709 }
710
711 // Avoid network filenames added by just the resolution, revert it if
712 // they are pointing to local drive.
713 if (wcsncmp(resolved_filename, L"\\\\?\\", 4) == 0) {
714 if (wcscmp(resolved_filename + 4, filename) == 0) {
715 copyStringSafeW(resolved_filename, filename, resolved_filename_size);
716 } else if (resolved_filename[5] == L':') {
717 copyStringSafeW(resolved_filename, resolved_filename + 4, resolved_filename_size);
718 }
719 }
720
721 // Avoid network filenames with UNC prefix, they won't work for loading
722 // extension modules and other things, Python avoids them too.
723 if (wcsncmp(resolved_filename, L"\\\\?\\UNC\\", 8) == 0) {
724 copyStringSafeW(resolved_filename, resolved_filename + 6, resolved_filename_size);
725 resolved_filename[0] = L'\\';
726 }
727
728 } else {
729 copyStringSafeW(resolved_filename, filename, resolved_filename_size);
730 }
731}
732
733#else
734
735static void resolveFileSymbolicLink(char *resolved_filename, char const *filename, size_t resolved_filename_size,
736 bool resolve_symlinks) {
737#ifdef __wasi__
738 copyStringSafe(resolved_filename, filename, resolved_filename_size);
739#else
740 if (resolve_symlinks) {
741 // At least on macOS, realpath cannot allocate a buffer, itself, so lets
742 // use a local one, only on Linux we could use NULL argument and have a
743 // malloc of the resulting value.
744 char buffer[MAXPATHLEN];
745
746 char *result = realpath(filename, buffer);
747
748 if (unlikely(result == NULL)) {
749 abort();
750 }
751
752 copyStringSafe(resolved_filename, buffer, resolved_filename_size);
753 } else {
754 copyStringSafe(resolved_filename, filename, resolved_filename_size);
755 }
756#endif
757}
758#endif
759
760#ifdef _WIN32
761wchar_t const *getBinaryFilenameWideChars(bool resolve_symlinks) {
762 static wchar_t binary_filename[MAXPATHLEN + 1] = {0};
763 static wchar_t binary_filename_resolved[MAXPATHLEN + 1] = {0};
764
765 wchar_t *buffer = resolve_symlinks ? binary_filename : binary_filename_resolved;
766 assert(sizeof(binary_filename) == sizeof(binary_filename_resolved));
767
768 if (buffer[0] == 0) {
769 DWORD res = GetModuleFileNameW(NULL, buffer, sizeof(binary_filename) / sizeof(wchar_t));
770 assert(res != 0);
771
772 // Resolve any symlinks we were invoked via
773 resolveFileSymbolicLink(buffer, buffer, sizeof(binary_filename) / sizeof(wchar_t), resolve_symlinks);
774 makeShortDirFilename(binary_filename, sizeof(binary_filename) / sizeof(wchar_t));
775 }
776
777 return buffer;
778}
779#endif
780
781#ifdef _WIN32
782extern wchar_t const *getBinaryFilenameWideChars(bool resolve_symlinks);
783
784char const *getBinaryFilenameHostEncoded(bool resolve_symlinks) {
785 static char *binary_filename = NULL;
786 static char *binary_filename_resolved = NULL;
787
788 char *binary_filename_target;
789
790 if (resolve_symlinks) {
791 binary_filename_target = binary_filename_resolved;
792 } else {
793 binary_filename_target = binary_filename;
794 }
795
796 if (binary_filename_target != NULL) {
797 return binary_filename_target;
798 }
799 wchar_t const *w = getBinaryFilenameWideChars(resolve_symlinks);
800
801 DWORD bufsize = WideCharToMultiByte(CP_ACP, 0, w, -1, NULL, 0, NULL, NULL);
802 assert(bufsize != 0);
803
804 binary_filename_target = (char *)malloc(bufsize + 1);
805 assert(binary_filename_target);
806
807 DWORD res2 = WideCharToMultiByte(CP_ACP, 0, w, -1, binary_filename_target, bufsize, NULL, NULL);
808 assert(res2 != 0);
809
810 if (unlikely(res2 > bufsize)) {
811 abort();
812 }
813
814 return (char const *)binary_filename_target;
815}
816
817#else
818char const *getBinaryFilenameHostEncoded(bool resolve_symlinks) {
819 const int buffer_size = MAXPATHLEN + 1;
820
821 static char binary_filename[MAXPATHLEN + 1] = {0};
822 static char binary_filename_resolved[MAXPATHLEN + 1] = {0};
823
824 char *binary_filename_target;
825
826 if (resolve_symlinks) {
827 binary_filename_target = binary_filename_resolved;
828 } else {
829 binary_filename_target = binary_filename;
830 }
831
832 if (*binary_filename_target != 0) {
833 return binary_filename_target;
834 }
835
836 copyStringSafe(binary_filename_target, _getBinaryPath2(), buffer_size);
837 resolveFileSymbolicLink(binary_filename_target, binary_filename_target, buffer_size, resolve_symlinks);
838
839 return binary_filename_target;
840}
841#endif
842
843#if defined(_WIN32)
844
845// Note: Keep this separate line, must be included before other Windows headers.
846#include <windows.h>
847
848#include <shlobj.h>
849#include <shlwapi.h>
850
851// For less complete C compilers.
852#ifndef CSIDL_LOCAL_APPDATA
853#define CSIDL_LOCAL_APPDATA 28
854#endif
855#ifndef CSIDL_PROFILE
856#define CSIDL_PROFILE 40
857#endif
858
859// spell-checker: ignore csidl
860
861static bool appendStringCSIDLPathW(wchar_t *target, int csidl_id, size_t buffer_size) {
862 wchar_t path_buffer[MAX_PATH];
863 int res = SHGetFolderPathW(NULL, csidl_id, NULL, 0, path_buffer);
864
865 if (res != S_OK) {
866 return false;
867 }
868 appendWStringSafeW(target, path_buffer, buffer_size);
869
870 return true;
871}
872
873bool expandTemplatePathW(wchar_t *target, wchar_t const *source, size_t buffer_size) {
874 target[0] = 0;
875
876 wchar_t var_name[1024];
877 wchar_t *w = NULL;
878
879 bool var_started = false;
880
881 while (*source != 0) {
882 if (*source == L'{') {
883 assert(var_started == false);
884 var_started = true;
885
886 w = var_name;
887 *w = 0;
888
889 source++;
890
891 continue;
892 } else if (*source == L'}') {
893 assert(var_started == true);
894 var_started = false;
895
896 *w = 0;
897
898 bool is_path = false;
899
900 if (wcsicmp(var_name, L"TEMP") == 0) {
901 GetTempPathW((DWORD)buffer_size, target);
902 is_path = true;
903 } else if (wcsicmp(var_name, L"PROGRAM") == 0) {
904#if _NUITKA_ONEFILE_TEMP_BOOL
905 appendWStringSafeW(target, __wargv[0], buffer_size);
906#else
907 if (!GetModuleFileNameW(NULL, target, (DWORD)buffer_size)) {
908 return false;
909 }
910#endif
911 } else if (wcsicmp(var_name, L"PROGRAM_BASE") == 0) {
912 if (expandTemplatePathW(target, L"{PROGRAM}", buffer_size - wcslen(target)) == false) {
913 return false;
914 }
915
916 size_t length = wcslen(target);
917
918 if ((length >= 4) && (wcsicmp(target + length - 4, L".exe") == 0)) {
919 target[length - 4] = 0;
920 }
921 } else if (wcsicmp(var_name, L"PROGRAM_DIR") == 0) {
922 if (expandTemplatePathW(target, L"{PROGRAM}", buffer_size - wcslen(target)) == false) {
923 return false;
924 }
925
926 stripFilenameW(target);
927 } else if (wcsicmp(var_name, L"PID") == 0) {
928 // Python binaries from onefile use onefile parent pid
929 environment_char_t const *environment_value = NULL;
930
931#if _NUITKA_ONEFILE_MODE
932 environment_value = getEnvironmentVariable("NUITKA_ONEFILE_PARENT");
933#endif
934 if (environment_value != NULL) {
935 checkWStringNumber(environment_value);
936
937 appendWStringSafeW(target, getEnvironmentVariable("NUITKA_ONEFILE_PARENT"), buffer_size);
938 } else {
939 char pid_buffer[128];
940 snprintf(pid_buffer, sizeof(pid_buffer), "%ld", GetCurrentProcessId());
941 appendStringSafeW(target, pid_buffer, buffer_size);
942 }
943 } else if (wcsicmp(var_name, L"HOME") == 0) {
944 if (appendStringCSIDLPathW(target, CSIDL_PROFILE, buffer_size) == false) {
945 return false;
946 }
947 is_path = true;
948 } else if (wcsicmp(var_name, L"CACHE_DIR") == 0) {
949 if (appendStringCSIDLPathW(target, CSIDL_LOCAL_APPDATA, buffer_size) == false) {
950 return false;
951 }
952 is_path = true;
953#ifdef NUITKA_COMPANY_NAME
954 } else if (wcsicmp(var_name, L"COMPANY") == 0) {
955 appendWStringSafeW(target, L"" NUITKA_COMPANY_NAME, buffer_size);
956#endif
957#ifdef NUITKA_PRODUCT_NAME
958 } else if (wcsicmp(var_name, L"PRODUCT") == 0) {
959 appendWStringSafeW(target, L"" NUITKA_PRODUCT_NAME, buffer_size);
960#endif
961#ifdef NUITKA_VERSION_COMBINED
962 } else if (wcsicmp(var_name, L"VERSION") == 0) {
963 appendWStringSafeW(target, L"" NUITKA_VERSION_COMBINED, buffer_size);
964#endif
965 } else if (wcsicmp(var_name, L"TIME") == 0) {
966 environment_char_t const *environment_value = NULL;
967
968#if _NUITKA_ONEFILE_MODE
969 environment_value = getEnvironmentVariable("NUITKA_ONEFILE_START");
970#endif
971
972 if (environment_value != NULL) {
973 appendWStringSafeW(target, getEnvironmentVariable("NUITKA_ONEFILE_START"), buffer_size);
974 } else {
975 wchar_t time_buffer[1024];
976
977 // spell-checker: ignore LPFILETIME
978 __int64 time = 0;
979 assert(sizeof(time) == sizeof(FILETIME));
980 GetSystemTimeAsFileTime((LPFILETIME)&time);
981
982 swprintf(time_buffer, sizeof(time_buffer), L"%lld", time);
983
984#if _NUITKA_ONEFILE_MODE
985 setEnvironmentVariable("NUITKA_ONEFILE_START", time_buffer);
986#endif
987 appendWStringSafeW(target, time_buffer, buffer_size);
988 }
989
990 } else {
991 return false;
992 }
993
994 // Skip over appended stuff.
995 while (*target) {
996 target++;
997 buffer_size -= 1;
998 }
999
1000 if (is_path) {
1001 while (*(target - 1) == FILENAME_SEP_CHAR) {
1002 target--;
1003 *target = 0;
1004 buffer_size += 1;
1005 }
1006 }
1007
1008 w = NULL;
1009 source++;
1010
1011 continue;
1012 }
1013
1014 if (w != NULL) {
1015 *w++ = *source++;
1016 continue;
1017 }
1018
1019 if (buffer_size < 1) {
1020 return false;
1021 }
1022
1023 *target++ = *source++;
1024 *target = 0;
1025 buffer_size -= 1;
1026 }
1027
1028 *target = 0;
1029
1030 assert(var_started == false);
1031 return true;
1032}
1033
1034#else
1035
1036bool expandTemplatePath(char *target, char const *source, size_t buffer_size) {
1037 target[0] = 0;
1038
1039 char var_name[1024];
1040 char *w = NULL;
1041
1042 NUITKA_MAY_BE_UNUSED bool var_started = false;
1043
1044 while (*source != 0) {
1045 if (*source == '{') {
1046 assert(var_started == false);
1047 var_started = true;
1048
1049 w = var_name;
1050 *w = 0;
1051
1052 source++;
1053
1054 continue;
1055 } else if (*source == '}') {
1056 assert(var_started == true);
1057 var_started = false;
1058 *w = 0;
1059
1060 bool is_path = false;
1061
1062 if (strcasecmp(var_name, "TEMP") == 0) {
1063 char const *tmp_dir = getenv("TMPDIR");
1064 if (tmp_dir == NULL) {
1065 tmp_dir = "/tmp";
1066 }
1067
1068 appendStringSafe(target, tmp_dir, buffer_size);
1069 is_path = true;
1070 } else if (strcasecmp(var_name, "PROGRAM") == 0) {
1071 char const *exe_name = getBinaryFilenameHostEncoded(false);
1072
1073 appendStringSafe(target, exe_name, buffer_size);
1074 } else if (strcasecmp(var_name, "PROGRAM_BASE") == 0) {
1075 if (expandTemplatePath(target, "{PROGRAM}", buffer_size - strlen(target)) == false) {
1076 return false;
1077 }
1078
1079 size_t length = strlen(target);
1080
1081 if ((length >= 4) && (strcasecmp(target + length - 4, ".bin") == 0)) {
1082 target[length - 4] = 0;
1083 }
1084 } else if (strcasecmp(var_name, "PROGRAM_DIR") == 0) {
1085 if (expandTemplatePath(target, "{PROGRAM}", buffer_size - strlen(target)) == false) {
1086 return false;
1087 }
1088
1089 size_t length = strlen(target);
1090
1091 // TODO: We should have an inplace strip dirname function, like for
1092 // Win32 stripFilenameW, but that then knows the length and check
1093 // if that empties the string, but this works for now.
1094 while (true) {
1095 if (length == 0) {
1096 return false;
1097 }
1098
1099 if (target[length] == '/') {
1100 break;
1101 }
1102
1103 target[length] = 0;
1104 }
1105
1106 is_path = true;
1107 } else if (strcasecmp(var_name, "PID") == 0) {
1108 // Python binaries from onefile use onefile parent pid
1109 environment_char_t const *environment_value = NULL;
1110
1111#if _NUITKA_ONEFILE_MODE
1112 environment_value = getEnvironmentVariable("NUITKA_ONEFILE_PARENT");
1113#endif
1114 if (environment_value != NULL) {
1115 checkStringNumber(environment_value);
1116
1117 appendStringSafe(target, getEnvironmentVariable("NUITKA_ONEFILE_PARENT"), buffer_size);
1118 } else {
1119 char pid_buffer[128];
1120
1121 snprintf(pid_buffer, sizeof(pid_buffer), "%d", getpid());
1122
1123 appendStringSafe(target, pid_buffer, buffer_size);
1124 }
1125 } else if (strcasecmp(var_name, "HOME") == 0) {
1126 char const *home_path = getenv("HOME");
1127 if (home_path == NULL) {
1128#ifdef __wasi__
1129 return false;
1130#else
1131 struct passwd *pw_data = getpwuid(getuid());
1132
1133 if (unlikely(pw_data == NULL)) {
1134 return false;
1135 }
1136
1137 home_path = pw_data->pw_dir;
1138#endif
1139 }
1140
1141 appendStringSafe(target, home_path, buffer_size);
1142 is_path = true;
1143 } else if (strcasecmp(var_name, "CACHE_DIR") == 0) {
1144 char const *xdg_cache_home = getenv("XDG_CACHE_HOME");
1145
1146 if (xdg_cache_home != NULL && xdg_cache_home[0] == '/') {
1147 appendStringSafe(target, xdg_cache_home, buffer_size);
1148 } else {
1149 if (expandTemplatePath(target, "{HOME}/.cache", buffer_size - strlen(target)) == false) {
1150 return false;
1151 }
1152 }
1153 is_path = true;
1154#ifdef NUITKA_COMPANY_NAME
1155 } else if (strcasecmp(var_name, "COMPANY") == 0) {
1156 appendStringSafe(target, NUITKA_COMPANY_NAME, buffer_size);
1157#endif
1158#ifdef NUITKA_PRODUCT_NAME
1159 } else if (strcasecmp(var_name, "PRODUCT") == 0) {
1160 appendStringSafe(target, NUITKA_PRODUCT_NAME, buffer_size);
1161#endif
1162#ifdef NUITKA_VERSION_COMBINED
1163 } else if (strcasecmp(var_name, "VERSION") == 0) {
1164 appendStringSafe(target, NUITKA_VERSION_COMBINED, buffer_size);
1165#endif
1166 } else if (strcasecmp(var_name, "TIME") == 0) {
1167 environment_char_t const *environment_value = NULL;
1168
1169#if _NUITKA_ONEFILE_MODE
1170 environment_value = getEnvironmentVariable("NUITKA_ONEFILE_START");
1171#endif
1172
1173 if (environment_value != NULL) {
1174 appendStringSafe(target, getEnvironmentVariable("NUITKA_ONEFILE_START"), buffer_size);
1175 } else {
1176 char time_buffer[1024];
1177
1178 struct timeval current_time;
1179 gettimeofday(&current_time, NULL);
1180 snprintf(time_buffer, sizeof(time_buffer), "%ld_%ld", current_time.tv_sec,
1181 (long)current_time.tv_usec);
1182
1183#if _NUITKA_ONEFILE_MODE
1184 setEnvironmentVariable("NUITKA_ONEFILE_START", time_buffer);
1185#endif
1186
1187 appendStringSafe(target, time_buffer, buffer_size);
1188 }
1189 } else {
1190 return false;
1191 }
1192 // Skip over appended stuff.
1193 while (*target) {
1194 target++;
1195 buffer_size -= 1;
1196 }
1197
1198 if (is_path) {
1199 while (*(target - 1) == FILENAME_SEP_CHAR) {
1200 target--;
1201 *target = 0;
1202 buffer_size += 1;
1203 }
1204 }
1205
1206 w = NULL;
1207 source++;
1208
1209 continue;
1210 }
1211
1212 if (w != NULL) {
1213 *w++ = *source++;
1214 continue;
1215 }
1216
1217 if (buffer_size < 1) {
1218 return false;
1219 }
1220
1221 *target++ = *source++;
1222 *target = 0;
1223 buffer_size -= 1;
1224 }
1225
1226 *target = 0;
1227
1228 assert(var_started == false);
1229 return true;
1230}
1231
1232#endif
1233
1234#if _NUITKA_DLL_MODE || _NUITKA_MODULE_MODE
1235#if defined(_WIN32)
1236// Small helper function to get current DLL handle, spell-checker: ignore lpcstr
1237static HMODULE getDllModuleHandle(void) {
1238 static HMODULE hm = NULL;
1239
1240 if (hm == NULL) {
1241 int res =
1242 GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1243 (LPCSTR)&getDllModuleHandle, &hm);
1244 assert(res != 0);
1245 }
1246
1247 assert(hm != NULL);
1248 return hm;
1249}
1250#endif
1251
1252#if defined(_AIX)
1253#include "AixDllAddr.c"
1254#endif
1255
1256static filename_char_t const *getDllFilename(void) {
1257#if defined(_WIN32)
1258 static WCHAR dll_filename[MAXPATHLEN + 1] = {0};
1259
1260 if (dll_filename[0] == 0) {
1261 int res = GetModuleFileNameW(getDllModuleHandle(), dll_filename, MAXPATHLEN);
1262 assert(res != 0);
1263
1264 makeShortDirFilename(dll_filename, sizeof(dll_filename) / sizeof(wchar_t));
1265 }
1266
1267 return dll_filename;
1268#else
1269 Dl_info where;
1270
1271 {
1272 NUITKA_MAY_BE_UNUSED int res = dladdr((void *)getDllDirectory, &where);
1273 assert(res != 0);
1274 }
1275
1276 return where.dli_fname;
1277#endif
1278}
1279
1280filename_char_t const *getDllDirectory(void) {
1281#if defined(_WIN32)
1282 static WCHAR path[MAXPATHLEN + 1] = {0};
1283 if (path[0] == 0) {
1284 copyStringSafeFilename(path, getDllFilename(), MAXPATHLEN);
1285 stripFilenameW(path);
1286 }
1287
1288 return path;
1289#else
1290 // Need to cache it, as dirname modifies stuff.
1291 static char const *result = NULL;
1292
1293 if (result == NULL) {
1294 Dl_info where;
1295
1296 {
1297 NUITKA_MAY_BE_UNUSED int res = dladdr((void *)getDllDirectory, &where);
1298 assert(res != 0);
1299 }
1300
1301 result = dirname((char *)strdup(where.dli_fname));
1302 }
1303
1304 return result;
1305#endif
1306}
1307#endif
1308// Part of "Nuitka", an optimizing Python compiler that is compatible and
1309// integrates with CPython, but also works on its own.
1310//
1311// Licensed under the Apache License, Version 2.0 (the "License");
1312// you may not use this file except in compliance with the License.
1313// You may obtain a copy of the License at
1314//
1315// http://www.apache.org/licenses/LICENSE-2.0
1316//
1317// Unless required by applicable law or agreed to in writing, software
1318// distributed under the License is distributed on an "AS IS" BASIS,
1319// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1320// See the License for the specific language governing permissions and
1321// limitations under the License.
Definition HelpersFilesystemPaths.c:580