Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Module.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cstdio>
7
8#if qStroika_Foundation_Common_Platform_MacOS
9#include <crt_externs.h>
10#include <libproc.h>
11#include <mach-o/dyld.h>
12#endif
13#if qStroika_Foundation_Common_Platform_POSIX && qSupport_Proc_Filesystem
14#include <unistd.h>
15#endif
16#if qStroika_Foundation_Common_Platform_Windows
17#include <windows.h>
18#endif
19
21#include "Stroika/Foundation/Containers/Sequence.h"
23#include "Stroika/Foundation/Execution/Exceptions.h"
25#include "Stroika/Foundation/Execution/Throw.h"
27
28#include "Module.h"
29
30using namespace Stroika::Foundation;
33using namespace Stroika::Foundation::Execution;
34
35// Comment this in to turn on aggressive noisy DbgTrace in this module
36//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
37
38/*
39 ********************************************************************************
40 ***************************** Execution::GetEXEDir *****************************
41 ********************************************************************************
42 */
43filesystem::path Execution::GetEXEDir ()
44{
45 return GetEXEPath ().parent_path ();
46}
47
48/*
49 ********************************************************************************
50 **************************** Execution::GetEXEPath *****************************
51 ********************************************************************************
52 */
53filesystem::path Execution::GetEXEPath ()
54{
55// See also http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
56// Mac OS X: _NSGetExecutablePath() (man 3 dyld)
57// Linux: readlink /proc/self/exe
58// Solaris: getexecname()
59// FreeBSD: sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
60// BSD with procfs: readlink /proc/curproc/file
61// Windows: GetModuleFileName() with hModule = nullptr
62//
63#if qStroika_Foundation_Common_Platform_MacOS
64 uint32_t bufSize = 0;
65 Verify (_NSGetExecutablePath (nullptr, &bufSize) == -1);
66 Assert (bufSize > 0);
67 Memory::StackBuffer<char> buf{Memory::eUninitialized, bufSize};
68 Verify (_NSGetExecutablePath (buf.begin (), &bufSize) == 0);
69 Assert (buf[bufSize - 1] == '\0');
70 return buf.begin ();
71#elif qStroika_Foundation_Common_Platform_POSIX && qSupport_Proc_Filesystem
72 // readlink () isn't clear about finding the right size. The only way to tell it wasn't enuf (maybe) is
73 // if all the bytes passed in are used. That COULD mean it all fit, or there was more. If we get that -
74 // double buf size and try again
75 Memory::StackBuffer<SDKChar> buf{Memory::eUninitialized, 1024};
76 ssize_t n;
77 while ((n = ::readlink ("/proc/self/exe", buf.data (), buf.GetSize ())) == buf.GetSize ()) {
78 buf.GrowToSize_uninitialized (buf.GetSize () * 2);
79 }
80 if (n < 0) {
81 ThrowPOSIXErrNo (errno);
82 }
83 Assert (n <= buf.GetSize ()); // could leave no room for NUL-byte, but not needed
84 return SDKString{buf.begin (), buf.begin () + n};
85#elif qStroika_Foundation_Common_Platform_Windows
86 SDKChar buf[MAX_PATH];
87 Verify (::GetModuleFileName (nullptr, buf, static_cast<DWORD> (Memory::NEltsOf (buf))));
88 buf[Memory::NEltsOf (buf) - 1] = '\0'; // cheaper and just as safe as memset() - more even. Buffer always nul-terminated, and if GetModuleFileName succeeds will be nul-terminated
89 return buf;
90#else
92 return filesystem::path{};
93#endif
94}
95
96/*
97 ********************************************************************************
98 ***************************** Execution::GetEXEPath ****************************
99 ********************************************************************************
100 */
101filesystem::path Execution::GetEXEPath ([[maybe_unused]] pid_t processID)
102{
103#if qStroika_Foundation_Common_Platform_MacOS
104 char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
105 int ret = ::proc_pidpath (processID, pathbuf, sizeof (pathbuf));
106 if (ret <= 0) {
107 Throw (Exception{"proc_pidpath failed"sv}); // @todo - horrible reporting, but not obvious what this API is? proc_pidpath?
108 }
109 else {
110 return pathbuf;
111 }
112#elif qStroika_Foundation_Common_Platform_POSIX && qSupport_Proc_Filesystem
113 // readlink () isn't clear about finding the right size. The only way to tell it wasn't enuf (maybe) is
114 // if all the bytes passed in are used. That COULD mean it all fit, or there was more. If we get that -
115 // double buf size and try again
116 Memory::StackBuffer<SDKChar> buf{Memory::eUninitialized, 1024};
117 ssize_t n;
118 char linkNameBuf[1024];
119 (void)std::snprintf (linkNameBuf, sizeof (linkNameBuf), "/proc/%ld/exe", static_cast<long> (processID));
120 while ((n = ::readlink (linkNameBuf, buf.data (), buf.GetSize ())) == buf.GetSize ()) {
121 buf.GrowToSize_uninitialized (buf.GetSize () * 2);
122 }
123 if (n < 0) {
124 ThrowPOSIXErrNo (errno);
125 }
126 Assert (n <= buf.GetSize ()); // could leave no room for NUL-byte, but not needed
127 return SDKString{buf.begin (), buf.begin () + n};
128#elif qStroika_Foundation_Common_Platform_Windows
129 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682621(v=vs.85).aspx but a bit of work
130 // not needed yet
132 return filesystem::path{};
133#else
135 return filesystem::path{};
136#endif
137}
138
139/*
140 ********************************************************************************
141 ******************************** Execution::kPath ******************************
142 ********************************************************************************
143 */
146 if (const char* env_p = std::getenv ("PATH")) {
147 String pathVar = String::FromNarrowSDKString (env_p);
148#if qStroika_Foundation_Common_Platform_POSIX
149 return pathVar.Tokenize ({':'}).Map<Sequence<filesystem::path>> ([] (auto i) { return i.template As<filesystem::path> (); });
150#elif qStroika_Foundation_Common_Platform_Windows
151 return pathVar.Tokenize ({';'}).Map<Sequence<filesystem::path>> ([] (auto i) { return i.template As<filesystem::path> (); });
152#endif
153 }
154 DISABLE_COMPILER_MSC_WARNING_END (4996)
155 return {};
156}};
157
158#if qStroika_Foundation_Common_Platform_Windows
159/*
160 ********************************************************************************
161 ***************************** Execution::kPathEXT ******************************
162 ********************************************************************************
163 */
166 if (const char* env_p = std::getenv ("PATHEXT")) {
167 String pathVar = String::FromNarrowSDKString (env_p);
168 return pathVar.Tokenize ({';'}).Map<Sequence<filesystem::path>> ([] (auto i) { return i.template As<filesystem::path> (); });
169 }
170 DISABLE_COMPILER_MSC_WARNING_END (4996)
171 return {};
172}};
173#endif
174
175/*
176 ********************************************************************************
177 ************************ Execution::kRawEnvironment ****************************
178 ********************************************************************************
179 */
182 const SDKChar* const* envHead = nullptr;
183#if qStroika_Foundation_Common_Platform_Windows
184 // documented in https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getenv-wgetenv?view=msvc-170 can happen, and how to workaround
186 if constexpr (same_as<SDKChar, wchar_t>) {
187 if (_wenviron == nullptr) {
188 [[maybe_unused]] auto ignored = ::_wgetenv (L"PATH");
189 }
190 }
191 else {
192 if (environ == nullptr) {
193 [[maybe_unused]] auto ignored = ::getenv ("PATH");
194 }
195 }
196 DISABLE_COMPILER_MSC_WARNING_END (4996)
197#endif
198#if qStroika_Foundation_Common_Platform_Windows
199 if constexpr (same_as<SDKChar, wchar_t>) {
200 envHead = _wenviron;
201 }
202 else
203#endif
204 {
205#if qStroika_Foundation_Common_Platform_MacOS
206 envHead = (*_NSGetEnviron ());
207#else
208 envHead = environ;
209#endif
210 }
211 // NULL-terminated array of NUL-terminated strings
212 AssertNotNull (envHead);
213 for (const SDKChar* const* p = envHead; *p; ++p) {
214 SDKString eltStr = *p;
215 size_t i = eltStr.find ('=');
216 if (i == SDKString::npos) {
217 DbgTrace ("bad env elt: {}"_f, String::FromSDKString (eltStr));
219 }
220 else {
221 r.Add (eltStr.substr (0, i), eltStr.substr (i + 1));
222 }
223 }
224 return r;
225}};
226
227/*
228 ********************************************************************************
229 *************************** Execution::kEnvironment ****************************
230 ********************************************************************************
231 */
234 for (auto i : kRawEnvironment ()) {
235 r.Add (String::FromSDKString (i.fKey), String::FromSDKString (i.fValue));
236 }
237 return r;
238}};
239
240/*
241 ********************************************************************************
242 ********************** Execution::FindExecutableInPath *************************
243 ********************************************************************************
244 */
245optional<filesystem::path> Execution::FindExecutableInPath (const filesystem::path& fn)
246{
247 auto checkExists = [] (const filesystem::path& exe) {
248 // better to use 'access' api?
249 return filesystem::exists (exe) and filesystem::is_regular_file (exe) and
250 static_cast<bool> (filesystem::status (exe).permissions () & filesystem::perms::owner_exec);
251 };
252 if (fn.is_absolute ()) {
253 if (checkExists (fn)) {
254 return fn;
255 }
256#if qStroika_Foundation_Common_Platform_Windows
257 if (fn.extension ().empty ()) {
258 filesystem::path exe = fn;
259 for (auto exeExt : kPathEXT ()) {
260 exe.replace_extension (exeExt);
261 if (checkExists (exe)) {
262 return exe;
263 }
264 }
265 }
266#endif
267 }
268 else {
269 // if not absolute, try relative to each element of the path (and CWD? not for now??)
270 for (filesystem::path d : kPath ()) {
271 filesystem::path exe = d / fn;
272 if (checkExists (exe)) {
273 return exe;
274 }
275#if qStroika_Foundation_Common_Platform_Windows
276 if (fn.extension ().empty ()) {
277 for (auto exeExt : kPathEXT ()) {
278 exe.replace_extension (exeExt);
279 if (checkExists (exe)) {
280 return exe;
281 }
282 }
283 }
284#endif
285 }
286 }
287 return nullopt;
288}
#define AssertNotNull(p)
Definition Assertions.h:333
#define WeakAssertNotReached()
Definition Assertions.h:467
#define AssertNotImplemented()
Definition Assertions.h:401
#define Verify(c)
Definition Assertions.h:419
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual Containers::Sequence< String > Tokenize() const
Definition String.cpp:1234
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:190
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
value-object, where the value construction is delayed until first needed (can be handy to avoid c++ i...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual void GrowToSize_uninitialized(size_t nElements)
same as GrowToSize (), except leaves newly created elements uninitialized (requires is_trivially_copy...
conditional_t< qTargetPlatformSDKUseswchar_t, wchar_t, char > SDKChar
Definition SDKChar.h:71
basic_string< SDKChar > SDKString
Definition SDKString.h:38
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
const LazyInitialized< Containers::Mapping< Characters::String, Characters::String > > kEnvironment
convert getenv() to a Mapping of Strings for easier access
Definition Module.cpp:232
const LazyInitialized< Containers::Sequence< filesystem::path > > kPath
Definition Module.cpp:144
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...
const LazyInitialized< Containers::Mapping< Characters::SDKString, Characters::SDKString > > kRawEnvironment
convert getenv() to a Mapping of SDKString (in case some issue with charactor set conversion)
Definition Module.cpp:180
filesystem::path GetEXEDir()
Definition Module.cpp:43
optional< filesystem::path > FindExecutableInPath(const filesystem::path &fn)
If fn refers to an executable - return it (using kPATH, and kPathEXT as appropriate)
Definition Module.cpp:245
int pid_t
TODO - maybe move this to configuraiotn module???
Definition Module.h:34
filesystem::path GetEXEPath()
Definition Module.cpp:53
STL namespace.