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