clang-tidy: modernize-macro-to-enum
[geeqie.git] / src / whereami.cc
1 // (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses
2 //   without any warranty.
3 //   by Gregory Pakosz (@gpakosz)
4 // https://github.com/gpakosz/whereami
5
6 #ifndef _GNU_SOURCE
7 #define _GNU_SOURCE
8 #endif
9
10 // in case you want to #include "whereami.cc" in a larger compilation unit
11 #if !defined(WHEREAMI_H)
12 #include <whereami.h>
13 #endif
14
15 #ifdef __cplusplus
16 extern "C" {
17 #endif
18
19 #if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC)
20 #include <stdlib.h>
21 #endif
22
23 #if !defined(WAI_MALLOC)
24 #define WAI_MALLOC(size) malloc(size)
25 #endif
26
27 #if !defined(WAI_FREE)
28 #define WAI_FREE(p) free(p)
29 #endif
30
31 #if !defined(WAI_REALLOC)
32 #define WAI_REALLOC(p, size) realloc(p, size)
33 #endif
34
35 #ifndef WAI_NOINLINE
36 #if defined(_MSC_VER)
37 #define WAI_NOINLINE __declspec(noinline)
38 #elif defined(__GNUC__)
39 #define WAI_NOINLINE __attribute__((noinline))
40 #else
41 #error unsupported compiler
42 #endif
43 #endif
44
45 #if defined(_MSC_VER)
46 #define WAI_RETURN_ADDRESS() _ReturnAddress()
47 #elif defined(__GNUC__)
48 #define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0))
49 #else
50 #error unsupported compiler
51 #endif
52
53 #if defined(_WIN32)
54
55 #ifndef WIN32_LEAN_AND_MEAN
56 #define WIN32_LEAN_AND_MEAN
57 #endif
58 #if defined(_MSC_VER)
59 #pragma warning(push, 3)
60 #endif
61 #include <windows.h>
62 #include <intrin.h>
63 #if defined(_MSC_VER)
64 #pragma warning(pop)
65 #endif
66
67 static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length)
68 {
69   wchar_t buffer1[MAX_PATH];
70   wchar_t buffer2[MAX_PATH];
71   wchar_t* path = NULL;
72   int length = -1;
73
74   for (;;)
75   {
76     DWORD size;
77     int length_, length__;
78
79     size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0]));
80
81     if (size == 0)
82       break;
83     else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0])))
84     {
85       DWORD size_ = size;
86       do
87       {
88         wchar_t* path_;
89
90         path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2);
91         if (!path_)
92           break;
93         size_ *= 2;
94         path = path_;
95         size = GetModuleFileNameW(module, path, size_);
96       }
97       while (size == size_);
98
99       if (size == size_)
100         break;
101     }
102     else
103       path = buffer1;
104
105     if (!_wfullpath(buffer2, path, MAX_PATH))
106       break;
107     length_ = (int)wcslen(buffer2);
108     length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL);
109
110     if (length__ == 0)
111       length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL);
112     if (length__ == 0)
113       break;
114
115     if (length__ <= capacity && dirname_length)
116     {
117       int i;
118
119       for (i = length__ - 1; i >= 0; --i)
120       {
121         if (out[i] == '\\')
122         {
123           *dirname_length = i;
124           break;
125         }
126       }
127     }
128
129     length = length__;
130
131     break;
132   }
133
134   if (path != buffer1)
135     WAI_FREE(path);
136
137   return length;
138 }
139
140 WAI_NOINLINE WAI_FUNCSPEC
141 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
142 {
143   return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length);
144 }
145
146 WAI_NOINLINE WAI_FUNCSPEC
147 int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)
148 {
149   HMODULE module;
150   int length = -1;
151
152 #if defined(_MSC_VER)
153 #pragma warning(push)
154 #pragma warning(disable: 4054)
155 #endif
156   if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module))
157 #if defined(_MSC_VER)
158 #pragma warning(pop)
159 #endif
160   {
161     length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length);
162   }
163
164   return length;
165 }
166
167 #elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(__GNU__) || defined(WAI_USE_PROC_SELF_EXE)
168
169 #include <stdio.h>
170 #include <stdlib.h>
171 #include <string.h>
172 #if defined(__linux__)
173 #include <linux/limits.h>
174 #else
175 #include <limits.h>
176 #endif
177 #ifndef __STDC_FORMAT_MACROS
178 #define __STDC_FORMAT_MACROS
179 #endif
180 #include <inttypes.h>
181
182 #if !defined(WAI_PROC_SELF_EXE)
183 #if defined(__sun)
184 #define WAI_PROC_SELF_EXE "/proc/self/path/a.out"
185 #else
186 #define WAI_PROC_SELF_EXE "/proc/self/exe"
187 #endif
188 #endif
189
190 #ifndef PATH_MAX
191 #define PATH_MAX 4096
192 #endif
193
194 WAI_FUNCSPEC
195 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
196 {
197   char buffer[PATH_MAX];
198   char* resolved = nullptr;
199   int length = -1;
200
201   for (;;)
202   {
203     resolved = realpath(WAI_PROC_SELF_EXE, buffer);
204     if (!resolved)
205       break;
206
207     length = (int)strlen(resolved);
208     if (length <= capacity)
209     {
210       memcpy(out, resolved, length);
211
212       if (dirname_length)
213       {
214         int i;
215
216         for (i = length - 1; i >= 0; --i)
217         {
218           if (out[i] == '/')
219           {
220             *dirname_length = i;
221             break;
222           }
223         }
224       }
225     }
226
227     break;
228   }
229
230   return length;
231 }
232
233 #if !defined(WAI_PROC_SELF_MAPS_RETRY)
234 #define WAI_PROC_SELF_MAPS_RETRY 5
235 #endif
236
237 #if !defined(WAI_PROC_SELF_MAPS)
238 #if defined(__sun)
239 #define WAI_PROC_SELF_MAPS "/proc/self/map"
240 #else
241 #define WAI_PROC_SELF_MAPS "/proc/self/maps"
242 #endif
243 #endif
244
245 #if defined(__ANDROID__) || defined(ANDROID)
246 #include <fcntl.h>
247 #include <sys/mman.h>
248 #include <unistd.h>
249 #endif
250
251 WAI_NOINLINE WAI_FUNCSPEC
252 int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)
253 {
254   int length = -1;
255   FILE* maps = nullptr;
256
257   for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r)
258   {
259     maps = fopen(WAI_PROC_SELF_MAPS, "r");
260     if (!maps)
261       break;
262
263     for (;;)
264     {
265       char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX];
266       uint64_t low, high;
267       char perms[5];
268       uint64_t offset;
269       uint32_t major, minor;
270       char path[PATH_MAX];
271       uint32_t inode;
272
273       if (!fgets(buffer, sizeof(buffer), maps))
274         break;
275
276       if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8)
277       {
278         auto addr = reinterpret_cast<uintptr_t>(WAI_RETURN_ADDRESS());
279         if (low <= addr && addr <= high)
280         {
281           char* resolved;
282
283           resolved = realpath(path, buffer);
284           if (!resolved)
285             break;
286
287           length = (int)strlen(resolved);
288 #if defined(__ANDROID__) || defined(ANDROID)
289           if (length > 4
290               &&buffer[length - 1] == 'k'
291               &&buffer[length - 2] == 'p'
292               &&buffer[length - 3] == 'a'
293               &&buffer[length - 4] == '.')
294           {
295             int fd = open(path, O_RDONLY);
296             if (fd == -1)
297             {
298               length = -1; // retry
299               break;
300             }
301
302             char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0);
303             if (begin == MAP_FAILED)
304             {
305               close(fd);
306               length = -1; // retry
307               break;
308             }
309
310             char* p = begin + offset - 30; // minimum size of local file header
311             while (p >= begin) // scan backwards
312             {
313               if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found
314               {
315                 uint16_t length_ = *((uint16_t*)(p + 26));
316
317                 if (length + 2 + length_ < (int)sizeof(buffer))
318                 {
319                   memcpy(&buffer[length], "!/", 2);
320                   memcpy(&buffer[length + 2], p + 30, length_);
321                   length += 2 + length_;
322                 }
323
324                 break;
325               }
326
327               --p;
328             }
329
330             munmap(begin, offset);
331             close(fd);
332           }
333 #endif
334           if (length <= capacity)
335           {
336             memcpy(out, resolved, length);
337
338             if (dirname_length)
339             {
340               int i;
341
342               for (i = length - 1; i >= 0; --i)
343               {
344                 if (out[i] == '/')
345                 {
346                   *dirname_length = i;
347                   break;
348                 }
349               }
350             }
351           }
352
353           break;
354         }
355       }
356     }
357
358     fclose(maps);
359     maps = nullptr;
360
361     if (length != -1)
362       break;
363   }
364
365   if (maps)
366     fclose(maps);
367
368   return length;
369 }
370
371 #elif defined(__APPLE__)
372
373 #define _DARWIN_BETTER_REALPATH
374 #include <mach-o/dyld.h>
375 #include <limits.h>
376 #include <stdlib.h>
377 #include <string.h>
378 #include <dlfcn.h>
379
380 WAI_FUNCSPEC
381 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
382 {
383   char buffer1[PATH_MAX];
384   char buffer2[PATH_MAX];
385   char* path = buffer1;
386   char* resolved = NULL;
387   int length = -1;
388
389   for (;;)
390   {
391     uint32_t size = (uint32_t)sizeof(buffer1);
392     if (_NSGetExecutablePath(path, &size) == -1)
393     {
394       path = (char*)WAI_MALLOC(size);
395       if (!_NSGetExecutablePath(path, &size))
396         break;
397     }
398
399     resolved = realpath(path, buffer2);
400     if (!resolved)
401       break;
402
403     length = (int)strlen(resolved);
404     if (length <= capacity)
405     {
406       memcpy(out, resolved, length);
407
408       if (dirname_length)
409       {
410         int i;
411
412         for (i = length - 1; i >= 0; --i)
413         {
414           if (out[i] == '/')
415           {
416             *dirname_length = i;
417             break;
418           }
419         }
420       }
421     }
422
423     break;
424   }
425
426   if (path != buffer1)
427     WAI_FREE(path);
428
429   return length;
430 }
431
432 WAI_NOINLINE WAI_FUNCSPEC
433 int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)
434 {
435   char buffer[PATH_MAX];
436   char* resolved = NULL;
437   int length = -1;
438
439   for(;;)
440   {
441     Dl_info info;
442
443     if (dladdr(WAI_RETURN_ADDRESS(), &info))
444     {
445       resolved = realpath(info.dli_fname, buffer);
446       if (!resolved)
447         break;
448
449       length = (int)strlen(resolved);
450       if (length <= capacity)
451       {
452         memcpy(out, resolved, length);
453
454         if (dirname_length)
455         {
456           int i;
457
458           for (i = length - 1; i >= 0; --i)
459           {
460             if (out[i] == '/')
461             {
462               *dirname_length = i;
463               break;
464             }
465           }
466         }
467       }
468     }
469
470     break;
471   }
472
473   return length;
474 }
475
476 #elif defined(__QNXNTO__)
477
478 #include <limits.h>
479 #include <stdio.h>
480 #include <stdlib.h>
481 #include <string.h>
482 #include <dlfcn.h>
483
484 #if !defined(WAI_PROC_SELF_EXE)
485 #define WAI_PROC_SELF_EXE "/proc/self/exefile"
486 #endif
487
488 WAI_FUNCSPEC
489 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
490 {
491   char buffer1[PATH_MAX];
492   char buffer2[PATH_MAX];
493   char* resolved = NULL;
494   FILE* self_exe = NULL;
495   int length = -1;
496
497   for (;;)
498   {
499     self_exe = fopen(WAI_PROC_SELF_EXE, "r");
500     if (!self_exe)
501       break;
502
503     if (!fgets(buffer1, sizeof(buffer1), self_exe))
504       break;
505
506     resolved = realpath(buffer1, buffer2);
507     if (!resolved)
508       break;
509
510     length = (int)strlen(resolved);
511     if (length <= capacity)
512     {
513       memcpy(out, resolved, length);
514
515       if (dirname_length)
516       {
517         int i;
518
519         for (i = length - 1; i >= 0; --i)
520         {
521           if (out[i] == '/')
522           {
523             *dirname_length = i;
524             break;
525           }
526         }
527       }
528     }
529
530     break;
531   }
532
533   fclose(self_exe);
534
535   return length;
536 }
537
538 WAI_FUNCSPEC
539 int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)
540 {
541   char buffer[PATH_MAX];
542   char* resolved = NULL;
543   int length = -1;
544
545   for(;;)
546   {
547     Dl_info info;
548
549     if (dladdr(WAI_RETURN_ADDRESS(), &info))
550     {
551       resolved = realpath(info.dli_fname, buffer);
552       if (!resolved)
553         break;
554
555       length = (int)strlen(resolved);
556       if (length <= capacity)
557       {
558         memcpy(out, resolved, length);
559
560         if (dirname_length)
561         {
562           int i;
563
564           for (i = length - 1; i >= 0; --i)
565           {
566             if (out[i] == '/')
567             {
568               *dirname_length = i;
569               break;
570             }
571           }
572         }
573       }
574     }
575
576     break;
577   }
578
579   return length;
580 }
581
582 #elif defined(__DragonFly__) || defined(__FreeBSD__) || \
583       defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)
584
585 #include <limits.h>
586 #include <stdlib.h>
587 #include <string.h>
588 #include <sys/types.h>
589 #include <sys/sysctl.h>
590 #include <dlfcn.h>
591
592 #if defined(__OpenBSD__)
593
594 #include <unistd.h>
595
596 WAI_FUNCSPEC
597 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
598 {
599   char buffer1[4096];
600   char buffer2[PATH_MAX];
601   char buffer3[PATH_MAX];
602   char** argv = (char**)buffer1;
603   char* resolved = NULL;
604   int length = -1;
605
606   for (;;)
607   {
608     int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
609     size_t size;
610
611     if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0)
612         break;
613
614     if (size > sizeof(buffer1))
615     {
616       argv = (char**)WAI_MALLOC(size);
617       if (!argv)
618         break;
619     }
620
621     if (sysctl(mib, 4, argv, &size, NULL, 0) != 0)
622         break;
623
624     if (strchr(argv[0], '/'))
625     {
626       resolved = realpath(argv[0], buffer2);
627       if (!resolved)
628         break;
629     }
630     else
631     {
632       const char* PATH = getenv("PATH");
633       if (!PATH)
634         break;
635
636       size_t argv0_length = strlen(argv[0]);
637
638       const char* begin = PATH;
639       while (1)
640       {
641         const char* separator = strchr(begin, ':');
642         const char* end = separator ? separator : begin + strlen(begin);
643
644         if (end - begin > 0)
645         {
646           if (*(end -1) == '/')
647             --end;
648
649           if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2))
650           {
651             memcpy(buffer2, begin, end - begin);
652             buffer2[end - begin] = '/';
653             memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1);
654
655             resolved = realpath(buffer2, buffer3);
656             if (resolved)
657               break;
658           }
659         }
660
661         if (!separator)
662           break;
663
664         begin = ++separator;
665       }
666
667       if (!resolved)
668         break;
669     }
670
671     length = (int)strlen(resolved);
672     if (length <= capacity)
673     {
674       memcpy(out, resolved, length);
675
676       if (dirname_length)
677       {
678         int i;
679
680         for (i = length - 1; i >= 0; --i)
681         {
682           if (out[i] == '/')
683           {
684             *dirname_length = i;
685             break;
686           }
687         }
688       }
689     }
690
691     break;
692   }
693
694   if (argv != (char**)buffer1)
695     WAI_FREE(argv);
696
697   return length;
698 }
699
700 #else
701
702 WAI_FUNCSPEC
703 int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)
704 {
705   char buffer1[PATH_MAX];
706   char buffer2[PATH_MAX];
707   char* path = buffer1;
708   char* resolved = NULL;
709   int length = -1;
710
711   for (;;)
712   {
713 #if defined(__NetBSD__)
714     int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };
715 #else
716     int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
717 #endif
718     size_t size = sizeof(buffer1);
719
720     if (sysctl(mib, 4, path, &size, NULL, 0) != 0)
721         break;
722
723     resolved = realpath(path, buffer2);
724     if (!resolved)
725       break;
726
727     length = (int)strlen(resolved);
728     if (length <= capacity)
729     {
730       memcpy(out, resolved, length);
731
732       if (dirname_length)
733       {
734         int i;
735
736         for (i = length - 1; i >= 0; --i)
737         {
738           if (out[i] == '/')
739           {
740             *dirname_length = i;
741             break;
742           }
743         }
744       }
745     }
746
747     break;
748   }
749
750   return length;
751 }
752
753 #endif
754
755 WAI_NOINLINE WAI_FUNCSPEC
756 int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)
757 {
758   char buffer[PATH_MAX];
759   char* resolved = NULL;
760   int length = -1;
761
762   for(;;)
763   {
764     Dl_info info;
765
766     if (dladdr(WAI_RETURN_ADDRESS(), &info))
767     {
768       resolved = realpath(info.dli_fname, buffer);
769       if (!resolved)
770         break;
771
772       length = (int)strlen(resolved);
773       if (length <= capacity)
774       {
775         memcpy(out, resolved, length);
776
777         if (dirname_length)
778         {
779           int i;
780
781           for (i = length - 1; i >= 0; --i)
782           {
783             if (out[i] == '/')
784             {
785               *dirname_length = i;
786               break;
787             }
788           }
789         }
790       }
791     }
792
793     break;
794   }
795
796   return length;
797 }
798
799 #else
800
801 #error unsupported platform
802
803 #endif
804
805 #ifdef __cplusplus
806 }
807 #endif