1 #include "portable.h"
2 #include "qcstring.h"
3
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <chrono>
7
8 #if defined(_WIN32) && !defined(__CYGWIN__)
9 #undef UNICODE
10 #define _WIN32_DCOM
11 #include <windows.h>
12 #else
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <errno.h>
17 extern char **environ;
18 #endif
19
20 #include <assert.h>
21 #include <ctype.h>
22 #include <map>
23 #include <string>
24
25 #include "fileinfo.h"
26
27 #include "util.h"
28 #include "dir.h"
29 #ifndef NODEBUG
30 #include "debug.h"
31 #endif
32
33 #if !defined(_WIN32) || defined(__CYGWIN__)
34 static bool environmentLoaded = false;
35 static std::map<std::string,std::string> proc_env = std::map<std::string,std::string>();
36 #endif
37
38 static double g_sysElapsedTime;
39 static std::chrono::steady_clock::time_point g_startTime;
40
41
system(const QCString & command,const QCString & args,bool commandHasConsole)42 int Portable::system(const QCString &command,const QCString &args,bool commandHasConsole)
43 {
44
45 if (command.isEmpty()) return 1;
46
47 #if defined(_WIN32) && !defined(__CYGWIN__)
48 QCString commandCorrectedPath = substitute(command,'/','\\');
49 QCString fullCmd=commandCorrectedPath;
50 #else
51 QCString fullCmd=command;
52 #endif
53 fullCmd=fullCmd.stripWhiteSpace();
54 if (fullCmd.at(0)!='"' && fullCmd.find(' ')!=-1)
55 {
56 // add quotes around command as it contains spaces and is not quoted already
57 fullCmd="\""+fullCmd+"\"";
58 }
59 fullCmd += " ";
60 fullCmd += args;
61 #ifndef NODEBUG
62 Debug::print(Debug::ExtCmd,0,"Executing external command `%s`\n",qPrint(fullCmd));
63 #endif
64
65 #if !defined(_WIN32) || defined(__CYGWIN__)
66 (void)commandHasConsole;
67 /*! taken from the system() manpage on my Linux box */
68 int pid,status=0;
69
70 #ifdef _OS_SOLARIS // for Solaris we use vfork since it is more memory efficient
71
72 // on Solaris fork() duplicates the memory usage
73 // so we use vfork instead
74
75 // spawn shell
76 if ((pid=vfork())<0)
77 {
78 status=-1;
79 }
80 else if (pid==0)
81 {
82 execl("/bin/sh","sh","-c",fullCmd.data(),(char*)0);
83 _exit(127);
84 }
85 else
86 {
87 while (waitpid(pid,&status,0 )<0)
88 {
89 if (errno!=EINTR)
90 {
91 status=-1;
92 break;
93 }
94 }
95 }
96 return status;
97
98 #else // Other Unices just use fork
99
100 pid = fork();
101 if (pid==-1)
102 {
103 perror("fork error");
104 return -1;
105 }
106 if (pid==0)
107 {
108 const char * argv[4];
109 argv[0] = "sh";
110 argv[1] = "-c";
111 argv[2] = fullCmd.data();
112 argv[3] = 0;
113 execve("/bin/sh",(char * const *)argv,environ);
114 exit(127);
115 }
116 for (;;)
117 {
118 if (waitpid(pid,&status,0)==-1)
119 {
120 if (errno!=EINTR) return -1;
121 }
122 else
123 {
124 if (WIFEXITED(status))
125 {
126 return WEXITSTATUS(status);
127 }
128 else
129 {
130 return status;
131 }
132 }
133 }
134 #endif // !_OS_SOLARIS
135
136 #else // Win32 specific
137 if (commandHasConsole)
138 {
139 return ::system(fullCmd.data());
140 }
141 else
142 {
143 // Because ShellExecuteEx can delegate execution to Shell extensions
144 // (data sources, context menu handlers, verb implementations) that
145 // are activated using Component Object Model (COM), COM should be
146 // initialized before ShellExecuteEx is called. Some Shell extensions
147 // require the COM single-threaded apartment (STA) type.
148 // For that case COM is initialized as follows
149 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
150
151 uint16_t *commandw = NULL;
152 recodeUtf8StringToW( commandCorrectedPath, &commandw );
153 uint16_t *argsw = NULL;
154 recodeUtf8StringToW( args, &argsw );
155
156 // gswin32 is a GUI api which will pop up a window and run
157 // asynchronously. To prevent both, we use ShellExecuteEx and
158 // WaitForSingleObject (thanks to Robert Golias for the code)
159
160 SHELLEXECUTEINFOW sInfo = {
161 sizeof(SHELLEXECUTEINFOW), /* structure size */
162 SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI, /* tell us the process
163 * handle so we can wait till it's done |
164 * do not display msg box if error
165 */
166 NULL, /* window handle */
167 NULL, /* action to perform: open */
168 (LPCWSTR)commandw, /* file to execute */
169 (LPCWSTR)argsw, /* argument list */
170 NULL, /* use current working dir */
171 SW_HIDE, /* minimize on start-up */
172 0, /* application instance handle */
173 NULL, /* ignored: id list */
174 NULL, /* ignored: class name */
175 NULL, /* ignored: key class */
176 0, /* ignored: hot key */
177 NULL, /* ignored: icon */
178 NULL /* resulting application handle */
179 };
180
181 if (!ShellExecuteExW(&sInfo))
182 {
183 delete[] commandw;
184 delete[] argsw;
185 return -1;
186 }
187 else if (sInfo.hProcess) /* executable was launched, wait for it to finish */
188 {
189 WaitForSingleObject(sInfo.hProcess,INFINITE);
190 /* get process exit code */
191 DWORD exitCode;
192 if (!GetExitCodeProcess(sInfo.hProcess,&exitCode))
193 {
194 exitCode = -1;
195 }
196 CloseHandle(sInfo.hProcess);
197 delete[] commandw;
198 delete[] argsw;
199 return exitCode;
200 }
201 }
202 #endif
203 return 1; // we should never get here
204
205 }
206
pid()207 unsigned int Portable::pid()
208 {
209 unsigned int pid;
210 #if !defined(_WIN32) || defined(__CYGWIN__)
211 pid = (unsigned int)getpid();
212 #else
213 pid = (unsigned int)GetCurrentProcessId();
214 #endif
215 return pid;
216 }
217
218 #if !defined(_WIN32) || defined(__CYGWIN__)
loadEnvironment()219 void loadEnvironment()
220 {
221 if(environ != NULL)
222 {
223 unsigned int i = 0;
224 char* current = environ[i];
225
226 while(current != NULL) // parse all strings contained by environ til the last element (NULL)
227 {
228 std::string env_var(current); // load current environment variable string
229 size_t pos = env_var.find("=");
230 if(pos != std::string::npos) // only parse the variable, if it is a valid environment variable...
231 { // ...which has to contain an equal sign as delimiter by definition
232 std::string name = env_var.substr(0,pos); // the string til the equal sign contains the name
233 std::string value = env_var.substr(pos + 1); // the string from the equal sign contains the value
234
235 proc_env[name] = value; // save the value by the name as its key in the classes map
236 }
237 i++;
238 current = environ[i];
239 }
240 }
241
242 environmentLoaded = true;
243 }
244 #endif
245
setenv(const QCString & name,const QCString & value)246 void Portable::setenv(const QCString &name,const QCString &value)
247 {
248 #if defined(_WIN32) && !defined(__CYGWIN__)
249 SetEnvironmentVariable(name.data(),!value.isEmpty() ? value.data() : "");
250 #else
251 if(!environmentLoaded) // if the environment variables are not loaded already...
252 { // ...call loadEnvironment to store them in class
253 loadEnvironment();
254 }
255
256 proc_env[name.str()] = value.str(); // create or replace existing value
257 #endif
258 }
259
unsetenv(const QCString & variable)260 void Portable::unsetenv(const QCString &variable)
261 {
262 #if defined(_WIN32) && !defined(__CYGWIN__)
263 SetEnvironmentVariable(variable.data(),0);
264 #else
265 /* Some systems don't have unsetenv(), so we do it ourselves */
266 if (variable.isEmpty() || variable.find('=')!=-1)
267 {
268 return; // not properly formatted
269 }
270
271 auto it = proc_env.find(variable.str());
272 if (it != proc_env.end())
273 {
274 proc_env.erase(it);
275 }
276 #endif
277 }
278
getenv(const QCString & variable)279 QCString Portable::getenv(const QCString &variable)
280 {
281 #if defined(_WIN32) && !defined(__CYGWIN__)
282 return ::getenv(variable.data());
283 #else
284 if(!environmentLoaded) // if the environment variables are not loaded already...
285 { // ...call loadEnvironment to store them in class
286 loadEnvironment();
287 }
288
289 if (proc_env.find(variable.str()) != proc_env.end())
290 {
291 return QCString(proc_env[variable.str()]);
292 }
293 else
294 {
295 return QCString();
296 }
297 #endif
298 }
299
fseek(FILE * f,portable_off_t offset,int whence)300 portable_off_t Portable::fseek(FILE *f,portable_off_t offset, int whence)
301 {
302 #if defined(__MINGW32__)
303 return fseeko64(f,offset,whence);
304 #elif defined(_WIN32) && !defined(__CYGWIN__)
305 return _fseeki64(f,offset,whence);
306 #else
307 return fseeko(f,offset,whence);
308 #endif
309 }
310
ftell(FILE * f)311 portable_off_t Portable::ftell(FILE *f)
312 {
313 #if defined(__MINGW32__)
314 return ftello64(f);
315 #elif defined(_WIN32) && !defined(__CYGWIN__)
316 return _ftelli64(f);
317 #else
318 return ftello(f);
319 #endif
320 }
321
fopen(const QCString & fileName,const QCString & mode)322 FILE *Portable::fopen(const QCString &fileName,const QCString &mode)
323 {
324 #if defined(_WIN32) && !defined(__CYGWIN__)
325 uint16_t *fn = 0;
326 size_t fn_len = recodeUtf8StringToW(fileName,&fn);
327 uint16_t *m = 0;
328 size_t m_len = recodeUtf8StringToW(mode,&m);
329 FILE *result = 0;
330 if (fn_len!=(size_t)-1 && m_len!=(size_t)-1)
331 {
332 result = _wfopen((wchar_t*)fn,(wchar_t*)m);
333 }
334 delete[] fn;
335 delete[] m;
336 return result;
337 #else
338 return ::fopen(fileName.data(),mode.data());
339 #endif
340 }
341
fclose(FILE * f)342 int Portable::fclose(FILE *f)
343 {
344 return ::fclose(f);
345 }
346
pathSeparator()347 QCString Portable::pathSeparator()
348 {
349 #if defined(_WIN32) && !defined(__CYGWIN__)
350 return "\\";
351 #else
352 return "/";
353 #endif
354 }
355
pathListSeparator()356 QCString Portable::pathListSeparator()
357 {
358 #if defined(_WIN32) && !defined(__CYGWIN__)
359 return ";";
360 #else
361 return ":";
362 #endif
363 }
364
ExistsOnPath(const QCString & fileName)365 static bool ExistsOnPath(const QCString &fileName)
366 {
367 FileInfo fi1(fileName.str());
368 if (fi1.exists()) return true;
369
370 QCString paths = Portable::getenv("PATH");
371 char listSep = Portable::pathListSeparator()[0];
372 char pathSep = Portable::pathSeparator()[0];
373 int strt = 0;
374 int idx;
375 while ((idx = paths.find(listSep,strt)) != -1)
376 {
377 QCString locFile(paths.mid(strt,idx-strt));
378 locFile += pathSep;
379 locFile += fileName;
380 FileInfo fi(locFile.str());
381 if (fi.exists()) return true;
382 strt = idx + 1;
383 }
384 // to be sure the last path component is checked as well
385 QCString locFile(paths.mid(strt));
386 if (!locFile.isEmpty())
387 {
388 locFile += pathSep;
389 locFile += fileName;
390 FileInfo fi(locFile.str());
391 if (fi.exists()) return true;
392 }
393 return false;
394 }
395
checkForExecutable(const QCString & fileName)396 bool Portable::checkForExecutable(const QCString &fileName)
397 {
398 #if defined(_WIN32) && !defined(__CYGWIN__)
399 char *extensions[] = {".bat",".com",".exe"};
400 for (int i = 0; i < sizeof(extensions) / sizeof(*extensions); i++)
401 {
402 if (ExistsOnPath(fileName + extensions[i])) return true;
403 }
404 return false;
405 #else
406 return ExistsOnPath(fileName);
407 #endif
408 }
409
ghostScriptCommand()410 const char *Portable::ghostScriptCommand()
411 {
412 #if defined(_WIN32) && !defined(__CYGWIN__)
413 static char *gsexe = NULL;
414 if (!gsexe)
415 {
416 char *gsExec[] = {"gswin32c.exe","gswin64c.exe"};
417 for (int i = 0; i < sizeof(gsExec) / sizeof(*gsExec); i++)
418 {
419 if (ExistsOnPath(gsExec[i]))
420 {
421 gsexe = gsExec[i];
422 return gsexe;
423 }
424 }
425 gsexe = gsExec[0];
426 return gsexe;
427 }
428 return gsexe;
429 #else
430 return "gs";
431 #endif
432 }
433
commandExtension()434 const char *Portable::commandExtension()
435 {
436 #if defined(_WIN32) && !defined(__CYGWIN__)
437 return ".exe";
438 #else
439 return "";
440 #endif
441 }
442
fileSystemIsCaseSensitive()443 bool Portable::fileSystemIsCaseSensitive()
444 {
445 #if defined(_WIN32) || defined(macintosh) || defined(__MACOSX__) || defined(__APPLE__) || defined(__CYGWIN__)
446 return FALSE;
447 #else
448 return TRUE;
449 #endif
450 }
451
popen(const QCString & name,const QCString & type)452 FILE * Portable::popen(const QCString &name,const QCString &type)
453 {
454 #if defined(_MSC_VER) || defined(__BORLANDC__)
455 return ::_popen(name.data(),type.data());
456 #else
457 return ::popen(name.data(),type.data());
458 #endif
459 }
460
pclose(FILE * stream)461 int Portable::pclose(FILE *stream)
462 {
463 #if defined(_MSC_VER) || defined(__BORLANDC__)
464 return ::_pclose(stream);
465 #else
466 return ::pclose(stream);
467 #endif
468 }
469
sysTimerStart()470 void Portable::sysTimerStart()
471 {
472 g_startTime = std::chrono::steady_clock::now();
473 }
474
sysTimerStop()475 void Portable::sysTimerStop()
476 {
477 std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now();
478 g_sysElapsedTime+= std::chrono::duration_cast<
479 std::chrono::microseconds>(endTime - g_startTime).count()/1000000.0;
480 }
481
getSysElapsedTime()482 double Portable::getSysElapsedTime()
483 {
484 return g_sysElapsedTime;
485 }
486
sleep(int ms)487 void Portable::sleep(int ms)
488 {
489 #if defined(_WIN32) && !defined(__CYGWIN__)
490 Sleep(ms);
491 #else
492 usleep(1000*ms);
493 #endif
494 }
495
isAbsolutePath(const QCString & fileName)496 bool Portable::isAbsolutePath(const QCString &fileName)
497 {
498 const char *fn = fileName.data();
499 # ifdef _WIN32
500 if (fileName.length()>1 && isalpha(fileName[0]) && fileName[1]==':') fn+=2;
501 # endif
502 char const fst = fn[0];
503 if (fst == '/') return true;
504 # ifdef _WIN32
505 if (fst == '\\') return true;
506 # endif
507 return false;
508 }
509
510 /**
511 * Correct a possible wrong PATH variable
512 *
513 * This routine was inspired by the cause for bug 766059 was that in the Windows path there were forward slashes.
514 */
correct_path()515 void Portable::correct_path()
516 {
517 #if defined(_WIN32) && !defined(__CYGWIN__)
518 QCString p = Portable::getenv("PATH");
519 if (p.isEmpty()) return; // no path nothing to correct
520 QCString result = substitute(p,"/","\\");
521 if (result!=p) Portable::setenv("PATH",result.data());
522 #endif
523 }
524
unlink(const QCString & fileName)525 void Portable::unlink(const QCString &fileName)
526 {
527 #if defined(_WIN32) && !defined(__CYGWIN__)
528 _unlink(fileName.data());
529 #else
530 ::unlink(fileName.data());
531 #endif
532 }
533
setShortDir()534 void Portable::setShortDir()
535 {
536 #if defined(_WIN32) && !defined(__CYGWIN__)
537 long length = 0;
538 TCHAR* buffer = NULL;
539 // First obtain the size needed by passing NULL and 0.
540 length = GetShortPathName(Dir::currentDirPath().c_str(), NULL, 0);
541 // Dynamically allocate the correct size
542 // (terminating null char was included in length)
543 buffer = new TCHAR[length];
544 // Now simply call again using same (long) path.
545 length = GetShortPathName(Dir::currentDirPath().c_str(), buffer, length);
546 // Set the correct directory (short name)
547 Dir::setCurrent(buffer);
548 delete [] buffer;
549 #endif
550 }
551
552 /* Return the first occurrence of NEEDLE in HAYSTACK. */
portable_memmem(const char * haystack,size_t haystack_len,const char * needle,size_t needle_len)553 static const char * portable_memmem (const char *haystack, size_t haystack_len,
554 const char *needle, size_t needle_len)
555 {
556 const char *const last_possible = haystack + haystack_len - needle_len;
557
558 if (needle_len == 0)
559 // The first occurrence of the empty string should to occur at the beginning of the string.
560 {
561 return haystack;
562 }
563
564 // Sanity check
565 if (haystack_len < needle_len)
566 {
567 return 0;
568 }
569
570 for (const char *begin = haystack; begin <= last_possible; ++begin)
571 {
572 if (begin[0] == needle[0] && !memcmp(&begin[1], needle + 1, needle_len - 1))
573 {
574 return begin;
575 }
576 }
577
578 return 0;
579 }
580
strnstr(const char * haystack,const char * needle,size_t haystack_len)581 const char *Portable::strnstr(const char *haystack, const char *needle, size_t haystack_len)
582 {
583 size_t needle_len = strnlen(needle, haystack_len);
584 if (needle_len < haystack_len || !needle[needle_len])
585 {
586 const char *x = portable_memmem(haystack, haystack_len, needle, needle_len);
587 if (x && !memchr(haystack, 0, x - haystack))
588 {
589 return x;
590 }
591 }
592 return 0;
593 }
594
devNull()595 const char *Portable::devNull()
596 {
597 #if defined(_WIN32) && !defined(__CYGWIN__)
598 return "NUL";
599 #else
600 return "/dev/null";
601 #endif
602 }
603
recodeUtf8StringToW(const QCString & inputStr,uint16_t ** outBuf)604 size_t Portable::recodeUtf8StringToW(const QCString &inputStr,uint16_t **outBuf)
605 {
606 if (inputStr.isEmpty() || outBuf==0) return 0; // empty input or invalid output
607 void *handle = portable_iconv_open("UTF-16LE","UTF-8");
608 if (handle==(void *)(-1)) return 0; // invalid encoding
609 size_t len = inputStr.length();
610 uint16_t *buf = new uint16_t[len+1];
611 *outBuf = buf;
612 size_t inRemains = len;
613 size_t outRemains = len*sizeof(uint16_t)+2; // chars + \0
614 const char *p = inputStr.data();
615 portable_iconv(handle,&p,&inRemains,(char**)&buf,&outRemains);
616 *buf=0;
617 portable_iconv_close(handle);
618 return len;
619 }
620
621
622