Doxygen
Loading...
Searching...
No Matches
portable.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2026 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16// own header
17#include "portable.h"
18
19// standard includes
20#include <cctype>
21#include <chrono>
22#include <cstdio>
23#include <cstdlib>
24#include <map>
25#include <mutex>
26#include <string>
27#include <thread>
28
29#if defined(_WIN32) && !defined(__CYGWIN__)
30#undef UNICODE
31#define _WIN32_DCOM
32#include <windows.h>
33#else
34#include <unistd.h>
35#include <sys/types.h>
36#include <sys/wait.h>
37#include <errno.h>
38extern char **environ;
39#endif
40
41// other includes
42#include "dir.h"
43#include "dstring.h"
44#include "fileinfo.h"
45#include "message.h"
46#include "util.h"
47
48#ifndef NODEBUG
49#include "debug.h"
50#endif
51
52#if !defined(_WIN32) || defined(__CYGWIN__)
53static bool environmentLoaded = false;
54static std::map<std::string,std::string> proc_env = std::map<std::string,std::string>();
55#endif
56
57
58//---------------------------------------------------------------------------------------------------------
59
60/*! Helper class to keep time interval per thread */
62{
63 public:
64 static SysTimeKeeper &instance();
65 //! start a timer for this thread
66 void start()
67 {
68 std::lock_guard<std::mutex> lock(m_mutex);
69 m_startTimes[std::this_thread::get_id()] = std::chrono::steady_clock::now();
70 }
71 //! ends a timer for this thread, accumulate time difference since start
72 void stop()
73 {
74 std::lock_guard<std::mutex> lock(m_mutex);
75 std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now();
76 auto it = m_startTimes.find(std::this_thread::get_id());
77 if (it == m_startTimes.end())
78 {
79 err("SysTimeKeeper stop() called without matching start()\n");
80 return;
81 }
82 double timeSpent = static_cast<double>(std::chrono::duration_cast<
83 std::chrono::microseconds>(endTime - it->second).count())/1000000.0;
84 //printf("timeSpent on thread %zu: %.4f seconds\n",std::hash<std::thread::id>{}(std::this_thread::get_id()),timeSpent);
85 m_elapsedTime += timeSpent;
86 }
87
88 double elapsedTime() const { return m_elapsedTime; }
89
90 private:
91 struct TimeData
92 {
93 std::chrono::steady_clock::time_point startTime;
94 };
95 std::map<std::thread::id,std::chrono::steady_clock::time_point> m_startTimes;
96 double m_elapsedTime = 0;
97 std::mutex m_mutex;
98};
99
101{
102 static SysTimeKeeper theInstance;
103 return theInstance;
104}
105
112
117
118//---------------------------------------------------------------------------------------------------------
119
120
121int Portable::system(const DString &command,const DString &args,bool commandHasConsole)
122{
123 if (command.empty()) return 1;
124 AutoTimeKeeper timeKeeper;
125
126#if defined(_WIN32) && !defined(__CYGWIN__)
127 DString commandCorrectedPath = substitute(command,'/','\\');
128 DString fullCmd=commandCorrectedPath;
129#else
130 DString fullCmd=command;
131#endif
132 fullCmd=fullCmd.stripWhiteSpace();
133 if (fullCmd.at(0)!='"' && fullCmd.find(' ')!=DString::npos)
134 {
135 // add quotes around command as it contains spaces and is not quoted already
136 fullCmd="\""+fullCmd+"\"";
137 }
138 fullCmd += " ";
139 fullCmd += args;
140#ifndef NODEBUG
141 Debug::print(Debug::ExtCmd,0,"Executing external command `{}`\n",fullCmd);
142#endif
143
144#if !defined(_WIN32) || defined(__CYGWIN__)
145 (void)commandHasConsole;
146 /*! taken from the system() manpage on my Linux box */
147 int pid,status=0;
148
149#ifdef _OS_SOLARIS // for Solaris we use vfork since it is more memory efficient
150
151 // on Solaris fork() duplicates the memory usage
152 // so we use vfork instead
153
154 // spawn shell
155 if ((pid=vfork())<0)
156 {
157 status=-1;
158 }
159 else if (pid==0)
160 {
161 execl("/bin/sh","sh","-c",fullCmd.data(),(char*)0);
162 _exit(127);
163 }
164 else
165 {
166 while (waitpid(pid,&status,0 )<0)
167 {
168 if (errno!=EINTR)
169 {
170 status=-1;
171 break;
172 }
173 }
174 }
175 return status;
176
177#else // Other Unices just use fork
178
179 pid = fork();
180 if (pid==-1)
181 {
182 perror("fork error");
183 return -1;
184 }
185 if (pid==0)
186 {
187 const char * const argv[4] = { "sh", "-c", fullCmd.data(), 0 };
188 execve("/bin/sh",const_cast<char * const*>(argv),environ);
189 exit(127);
190 }
191 for (;;)
192 {
193 if (waitpid(pid,&status,0)==-1)
194 {
195 if (errno!=EINTR) return -1;
196 }
197 else
198 {
199 if (WIFEXITED(status))
200 {
201 return WEXITSTATUS(status);
202 }
203 else
204 {
205 return status;
206 }
207 }
208 }
209#endif // !_OS_SOLARIS
210
211#else // Win32 specific
212 if (commandHasConsole)
213 {
214 return ::system(fullCmd.data());
215 }
216 else
217 {
218 uint16_t* fullCmdW = nullptr;
219 recodeUtf8StringToW(fullCmd, &fullCmdW);
220
221 STARTUPINFOW sStartupInfo;
222 std::memset(&sStartupInfo, 0, sizeof(sStartupInfo));
223 sStartupInfo.cb = sizeof(sStartupInfo);
224 sStartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
225 sStartupInfo.wShowWindow = SW_HIDE;
226
227 PROCESS_INFORMATION sProcessInfo;
228 std::memset(&sProcessInfo, 0, sizeof(sProcessInfo));
229
230 if (!CreateProcessW(
231 nullptr, // No module name (use command line)
232 reinterpret_cast<wchar_t*>(fullCmdW), // Command line, can be mutated by CreateProcessW
233 nullptr, // Process handle not inheritable
234 nullptr, // Thread handle not inheritable
235 false, // Set handle inheritance to false
236 CREATE_NO_WINDOW,
237 nullptr, // Use parent's environment block
238 nullptr, // Use parent's starting directory
239 &sStartupInfo,
240 &sProcessInfo
241 ))
242 {
243 delete[] fullCmdW;
244 return -1;
245 }
246 else if (sProcessInfo.hProcess) /* executable was launched, wait for it to finish */
247 {
248 WaitForSingleObject(sProcessInfo.hProcess,INFINITE);
249 /* get process exit code */
250 DWORD exitCode;
251 bool retval = GetExitCodeProcess(sProcessInfo.hProcess,&exitCode);
252 CloseHandle(sProcessInfo.hProcess);
253 CloseHandle(sProcessInfo.hThread);
254 delete[] fullCmdW;
255 if (!retval) return -1;
256 return exitCode;
257 }
258 }
259#endif
260 return 1; // we should never get here
261
262}
263
265{
266 uint32_t pid;
267#if !defined(_WIN32) || defined(__CYGWIN__)
268 pid = static_cast<uint32_t>(getpid());
269#else
270 pid = static_cast<uint32_t>(GetCurrentProcessId());
271#endif
272 return pid;
273}
274
275#if !defined(_WIN32) || defined(__CYGWIN__)
277{
278 if(environ != nullptr)
279 {
280 unsigned int i = 0;
281 char* current = environ[i];
282
283 while(current != nullptr) // parse all strings contained by environ til the last element (nullptr)
284 {
285 std::string env_var(current); // load current environment variable string
286 size_t pos = env_var.find("=");
287 if(pos != std::string::npos) // only parse the variable, if it is a valid environment variable...
288 { // ...which has to contain an equal sign as delimiter by definition
289 std::string name = env_var.substr(0,pos); // the string til the equal sign contains the name
290 std::string value = env_var.substr(pos + 1); // the string from the equal sign contains the value
291 proc_env[name] = std::move(value); // save the value by the name as its key in the classes map
292 }
293 i++;
294 current = environ[i];
295 }
296 }
297
298 environmentLoaded = true;
299}
300#endif
301
302void Portable::setenv(const DString &name,const DString &value)
303{
304#if defined(_WIN32) && !defined(__CYGWIN__)
305 SetEnvironmentVariable(name.data(),!value.empty() ? value.data() : "");
306#else
307 if(!environmentLoaded) // if the environment variables are not loaded already...
308 { // ...call loadEnvironment to store them in class
310 }
311
312 proc_env[name.str()] = value.str(); // create or replace existing value
313 ::setenv(name.data(),value.data(),1);
314#endif
315}
316
317void Portable::unsetenv(const DString &variable)
318{
319#if defined(_WIN32) && !defined(__CYGWIN__)
320 SetEnvironmentVariable(variable.data(),nullptr);
321#else
322 /* Some systems don't have unsetenv(), so we do it ourselves */
323 if (variable.empty() || variable.find('=')!=DString::npos)
324 {
325 return; // not properly formatted
326 }
327
328 auto it = proc_env.find(variable.str());
329 if (it != proc_env.end())
330 {
331 proc_env.erase(it);
332 ::unsetenv(variable.data());
333 }
334#endif
335}
336
338{
339#if defined(_WIN32) && !defined(__CYGWIN__)
340 #define ENV_BUFSIZE 32768
341 LPTSTR pszVal = (LPTSTR) malloc(ENV_BUFSIZE*sizeof(TCHAR));
342 if (GetEnvironmentVariable(variable.data(),pszVal,ENV_BUFSIZE) == 0) return "";
343 DString out;
344 out = pszVal;
345 free(pszVal);
346 return out;
347 #undef ENV_BUFSIZE
348#else
349 if(!environmentLoaded) // if the environment variables are not loaded already...
350 { // ...call loadEnvironment to store them in class
352 }
353
354 if (proc_env.find(variable.str()) != proc_env.end())
355 {
356 return DString(proc_env[variable.str()]);
357 }
358 else
359 {
360 return DString();
361 }
362#endif
363}
364
365FILE *Portable::fopen(const DString &fileName,const DString &mode)
366{
367#if defined(_WIN32) && !defined(__CYGWIN__)
368 uint16_t *fn = nullptr;
369 size_t fn_len = recodeUtf8StringToW(fileName,&fn);
370 uint16_t *m = nullptr;
371 size_t m_len = recodeUtf8StringToW(mode,&m);
372 FILE *result = nullptr;
373 if (fn_len!=(size_t)-1 && m_len!=(size_t)-1)
374 {
375 result = _wfopen((wchar_t*)fn,(wchar_t*)m);
376 }
377 delete[] fn;
378 delete[] m;
379 return result;
380#else
381 return ::fopen(fileName.data(),mode.data());
382#endif
383}
384
386{
387 return ::fclose(f);
388}
389
391{
392#if defined(_WIN32) && !defined(__CYGWIN__)
393 return "\\";
394#else
395 return "/";
396#endif
397}
398
400{
401#if defined(_WIN32) && !defined(__CYGWIN__)
402 return ";";
403#else
404 return ":";
405#endif
406}
407
408static bool ExistsOnPath(const DString &fileName)
409{
410 FileInfo fi1(fileName.str());
411 if (fi1.exists()) return true;
412
413 DString paths = Portable::getenv("PATH");
414 char listSep = Portable::pathListSeparator()[0];
415 char pathSep = Portable::pathSeparator()[0];
416 size_t strt = 0;
417 size_t idx;
418 while ((idx = paths.find(listSep,strt)) != DString::npos)
419 {
420 DString locFile(paths.mid(strt,idx-strt));
421 locFile += pathSep;
422 locFile += fileName;
423 FileInfo fi(locFile.str());
424 if (fi.exists()) return true;
425 strt = idx + 1;
426 }
427 // to be sure the last path component is checked as well
428 DString locFile(paths.mid(strt));
429 if (!locFile.empty())
430 {
431 locFile += pathSep;
432 locFile += fileName;
433 FileInfo fi(locFile.str());
434 if (fi.exists()) return true;
435 }
436 return false;
437}
438
440{
441#if defined(_WIN32) && !defined(__CYGWIN__)
442 const char *extensions[] = {".bat",".com",".exe"};
443 for (int i = 0; i < sizeof(extensions) / sizeof(*extensions); i++)
444 {
445 if (ExistsOnPath(fileName + extensions[i])) return true;
446 }
447 return false;
448#else
449 return ExistsOnPath(fileName);
450#endif
451}
452
454{
455#if defined(_WIN32) && !defined(__CYGWIN__)
456 static const char *gsexe = nullptr;
457 if (!gsexe)
458 {
459 const char *gsExec[] = {"gswin32c.exe","gswin64c.exe"};
460 for (int i = 0; i < sizeof(gsExec) / sizeof(*gsExec); i++)
461 {
462 if (ExistsOnPath(gsExec[i]))
463 {
464 gsexe = gsExec[i];
465 return gsexe;
466 }
467 }
468 gsexe = gsExec[0];
469 return gsexe;
470 }
471 return gsexe;
472#else
473 return "gs";
474#endif
475}
476
478{
479#if defined(_WIN32) && !defined(__CYGWIN__)
480 return ".exe";
481#else
482 return "";
483#endif
484}
485
487{
488#if defined(_WIN32) || defined(macintosh) || defined(__MACOSX__) || defined(__APPLE__) || defined(__CYGWIN__)
489 return false;
490#else
491 return true;
492#endif
493}
494
495FILE * Portable::popen(const DString &name,const DString &type)
496{
497 #if defined(_MSC_VER) || defined(__BORLANDC__)
498 return ::_popen(name.data(),type.data());
499 #else
500 return ::popen(name.data(),type.data());
501 #endif
502}
503
504int Portable::pclose(FILE *stream)
505{
506 #if defined(_MSC_VER) || defined(__BORLANDC__)
507 return ::_pclose(stream);
508 #else
509 return ::pclose(stream);
510 #endif
511}
512
514{
515 const char *fn = fileName.data();
516# ifdef _WIN32
517 if (fileName.length()>1 && isalpha(fileName[0]) && fileName[1]==':') fn+=2;
518# endif
519 char const fst = fn[0];
520 if (fst == '/') return true;
521# ifdef _WIN32
522 if (fst == '\\') return true;
523# endif
524 return false;
525}
526
527/**
528 * Correct a possible wrong PATH variable
529 *
530 * This routine was inspired by the cause for bug 766059 was that in the Windows path there were forward slashes.
531 */
532void Portable::correctPath(const StringVector &extraPaths)
533{
534 DString p = Portable::getenv("PATH");
535 bool first=true;
536 DString result;
537#if defined(_WIN32) && !defined(__CYGWIN__)
538 for (const auto &path : extraPaths)
539 {
540 if (!first) result+=';';
541 first=false;
542 result += substitute(path,"/","\\");
543 }
544 if (!result.empty() && !p.empty()) result+=';';
545 result += substitute(p,"/","\\");
546#else
547 for (const auto &path : extraPaths)
548 {
549 if (!first) result+=':';
550 first=false;
551 result += path;
552 }
553 if (!result.empty() && !p.empty()) result+=':';
554 result += p;
555#endif
556 if (result!=p) Portable::setenv("PATH",result.data());
557 //printf("settingPath(%s) #extraPaths=%zu\n",Portable::getenv("PATH").data(),extraPaths.size());
558}
559
560void Portable::unlink(const DString &fileName)
561{
562#if defined(_WIN32) && !defined(__CYGWIN__)
563 _unlink(fileName.data());
564#else
565 ::unlink(fileName.data());
566#endif
567}
568
570{
571#if defined(_WIN32) && !defined(__CYGWIN__)
572 long length = 0;
573 TCHAR* buffer = nullptr;
574 // First obtain the size needed by passing nullptr and 0.
575 length = GetShortPathName(Dir::currentDirPath().c_str(), nullptr, 0);
576 // Dynamically allocate the correct size
577 // (terminating null char was included in length)
578 buffer = new TCHAR[length];
579 // Now simply call again using same (long) path.
580 length = GetShortPathName(Dir::currentDirPath().c_str(), buffer, length);
581 // Set the correct directory (short name)
582 Dir::setCurrent(buffer);
583 delete [] buffer;
584#endif
585}
586
587/* Return the first occurrence of NEEDLE in HAYSTACK. */
588static const char * portable_memmem (const char *haystack, size_t haystack_len,
589 const char *needle, size_t needle_len)
590{
591 const char *const last_possible = haystack + haystack_len - needle_len;
592
593 if (needle_len == 0)
594 // The first occurrence of the empty string should to occur at the beginning of the string.
595 {
596 return haystack;
597 }
598
599 // Sanity check
600 if (haystack_len < needle_len)
601 {
602 return nullptr;
603 }
604
605 for (const char *begin = haystack; begin <= last_possible; ++begin)
606 {
607 if (begin[0] == needle[0] && !memcmp(&begin[1], needle + 1, needle_len - 1))
608 {
609 return begin;
610 }
611 }
612
613 return nullptr;
614}
615
616const char *Portable::strnstr(const char *haystack, const char *needle, size_t haystack_len)
617{
618 size_t needle_len = strnlen(needle, haystack_len);
619 if (needle_len < haystack_len || !needle[needle_len])
620 {
621 const char *x = portable_memmem(haystack, haystack_len, needle, needle_len);
622 if (x && !memchr(haystack, 0, x - haystack))
623 {
624 return x;
625 }
626 }
627 return nullptr;
628}
629
630const char *Portable::devNull()
631{
632#if defined(_WIN32) && !defined(__CYGWIN__)
633 return "NUL";
634#else
635 return "/dev/null";
636#endif
637}
638
639size_t Portable::recodeUtf8StringToW(const DString &inputStr,uint16_t **outBuf)
640{
641 if (inputStr.empty() || outBuf==nullptr) return 0; // empty input or invalid output
642 void *handle = portable_iconv_open("UTF-16LE","UTF-8");
643 if (handle==reinterpret_cast<void *>(-1)) return 0; // invalid encoding
644 size_t len = inputStr.length();
645 uint16_t *buf = new uint16_t[len+1];
646 *outBuf = buf;
647 size_t inRemains = len;
648 size_t outRemains = len*sizeof(uint16_t)+2; // chars + \0
649 const char *p = inputStr.data();
650 portable_iconv(handle,&p,&inRemains,reinterpret_cast<char **>(&buf),&outRemains);
651 *buf=0;
652 portable_iconv_close(handle);
653 return len;
654}
655
657{
658 DString result;
659#if defined(_WIN32)
660 if (path.startsWith("//?/")) // strip leading "\\?\" part from path
661 {
662 result = path.mid(4);
663 }
664 else
665#endif
666 {
667 result = path;
668 }
669 return result;
670}
671
672
673//----------------------------------------------------------------------------------------
674// We need to do this part last as including filesystem.hpp earlier
675// causes the code above to fail to compile on Windows.
676
677#include "filesystem.hpp"
678
679namespace fs = ghc::filesystem;
680
681std::ofstream Portable::openOutputStream(const DString &fileName,bool append)
682{
683 std::ios_base::openmode mode = std::ofstream::out | std::ofstream::binary;
684 if (append) mode |= std::ofstream::app;
685#if defined(__clang__) && defined(__MINGW32__)
686 return std::ofstream(fs::path(fileName.str()).wstring(), mode);
687#else
688 return std::ofstream(fs::path(fileName.str()), mode);
689#endif
690}
691
692std::ifstream Portable::openInputStream(const DString &fileName,bool binary, bool openAtEnd)
693{
694 std::ios_base::openmode mode = std::ifstream::in | std::ifstream::binary;
695 if (binary) mode |= std::ios::binary;
696 if (openAtEnd) mode |= std::ios::ate;
697#if defined(__clang__) && defined(__MINGW32__)
698 return std::ifstream(fs::path(fileName.str()).wstring(), mode);
699#else
700 return std::ifstream(fs::path(fileName.str()), mode);
701#endif
702}
703
A String class for use with Doxygen wrapping std::string and adding some additional functionality off...
Definition dstring.h:84
DString mid(size_t index, size_t len=npos) const
Definition dstring.h:318
bool empty() const
Returns true iff the string is empty (std::string compatible alias for isEmpty()).
Definition dstring.h:148
static constexpr size_t npos
value used to indicate 'not found' or 'to the end of the string', matching std::string::npos
Definition dstring.h:178
char & at(size_t i)
Returns a reference to the character at index i.
Definition dstring.h:686
size_t find(char c, size_t pos=0) const
Definition dstring.h:239
DString stripWhiteSpace() const
returns a copy of this string with leading and trailing whitespace removed
Definition dstring.h:337
const std::string & str() const
Definition dstring.h:645
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition dstring.h:157
bool startsWith(const char *s) const
Definition dstring.h:600
size_t length() const
Returns the length of the string, not counting the 0-terminator.
Definition dstring.h:151
@ ExtCmd
Definition debug.h:37
static void print(DebugMask mask, int prio, fmt::format_string< Args... > fmt, Args &&... args)
Definition debug.h:78
static std::string currentDirPath()
Definition dir.cpp:348
static bool setCurrent(const std::string &path)
Definition dir.cpp:356
Minimal replacement for QFileInfo.
Definition fileinfo.h:26
bool exists() const
Definition fileinfo.cpp:34
std::mutex m_mutex
Definition portable.cpp:97
double m_elapsedTime
Definition portable.cpp:96
void stop()
ends a timer for this thread, accumulate time difference since start
Definition portable.cpp:72
void start()
start a timer for this thread
Definition portable.cpp:66
double elapsedTime() const
Definition portable.cpp:88
static SysTimeKeeper & instance()
Definition portable.cpp:100
std::map< std::thread::id, std::chrono::steady_clock::time_point > m_startTimes
Definition portable.cpp:95
std::vector< std::string > StringVector
Definition containers.h:33
DirIterator begin(DirIterator it) noexcept
Definition dir.cpp:176
DString substitute(const DString &s, const DString &src, const DString &dst)
substitute all occurrences of src in s by dst
Definition dstring.cpp:485
#define err(fmt,...)
Definition message.h:127
void correctPath(const StringVector &list)
Correct a possible wrong PATH variable.
Definition portable.cpp:532
FILE * popen(const DString &name, const DString &type)
Definition portable.cpp:495
std::ifstream openInputStream(const DString &name, bool binary=false, bool openAtEnd=false)
Definition portable.cpp:692
double getSysElapsedTime()
Definition portable.cpp:113
void setenv(const DString &variable, const DString &value)
Definition portable.cpp:302
const char * ghostScriptCommand()
Definition portable.cpp:453
uint32_t pid()
Definition portable.cpp:264
int system(const DString &command, const DString &args, bool commandHasConsole=true)
Definition portable.cpp:121
int pclose(FILE *stream)
Definition portable.cpp:504
DString getenv(const DString &variable)
Definition portable.cpp:337
void unlink(const DString &fileName)
Definition portable.cpp:560
DString removeLongPathMarker(const DString &path)
Definition portable.cpp:656
bool fileSystemIsCaseSensitive()
Definition portable.cpp:486
DString pathSeparator()
Definition portable.cpp:390
size_t recodeUtf8StringToW(const DString &inputStr, uint16_t **buf)
Definition portable.cpp:639
bool isAbsolutePath(const DString &fileName)
Definition portable.cpp:513
DString pathListSeparator()
Definition portable.cpp:399
std::ofstream openOutputStream(const DString &name, bool append=false)
Definition portable.cpp:681
FILE * fopen(const DString &fileName, const DString &mode)
Definition portable.cpp:365
const char * commandExtension()
Definition portable.cpp:477
const char * strnstr(const char *haystack, const char *needle, size_t haystack_len)
Definition portable.cpp:616
const char * devNull()
Definition portable.cpp:630
int fclose(FILE *f)
Definition portable.cpp:385
void unsetenv(const DString &variable)
Definition portable.cpp:317
bool checkForExecutable(const DString &fileName)
Definition portable.cpp:439
void setShortDir()
Definition portable.cpp:569
static bool environmentLoaded
Definition portable.cpp:53
static std::map< std::string, std::string > proc_env
Definition portable.cpp:54
static const char * portable_memmem(const char *haystack, size_t haystack_len, const char *needle, size_t needle_len)
Definition portable.cpp:588
char ** environ
void loadEnvironment()
Definition portable.cpp:276
static bool ExistsOnPath(const DString &fileName)
Definition portable.cpp:408
Portable versions of functions that are platform dependent.
int portable_iconv_close(void *cd)
size_t portable_iconv(void *cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
void * portable_iconv_open(const char *tocode, const char *fromcode)
std::chrono::steady_clock::time_point startTime
Definition portable.cpp:93
A bunch of utility functions.