1 /*****
2  * util.cc
3  * John Bowman
4  *
5  * A place for useful utility functions.
6  *****/
7 
8 #include <cassert>
9 #include <iostream>
10 #include <cstdio>
11 #include <cfloat>
12 #include <sstream>
13 #include <cerrno>
14 #include <sys/wait.h>
15 #include <sys/param.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <cstring>
21 #include <algorithm>
22 
23 #include "util.h"
24 #include "settings.h"
25 #include "errormsg.h"
26 #include "camperror.h"
27 #include "interact.h"
28 
29 using namespace settings;
30 
31 bool False=false;
32 
33 namespace vm {
34 void error(const char* message);
35 }
36 
37 #if __GNUC__
38 #include <cxxabi.h>
demangle(const char * s)39 string demangle(const char *s)
40 {
41   int status;
42   char *demangled = abi::__cxa_demangle(s,NULL,NULL,&status);
43   if (status == 0 && demangled) {
44     string str(demangled);
45     free(demangled);
46     return str;
47   } else if (status == -2) {
48     free(demangled);
49     return s;
50   } else {
51     free(demangled);
52     return string("Unknown(") + s + ")";
53   }
54 }
55 #else
demangle(const char * s)56 string demangle(const char* s)
57 {
58   return s;
59 }
60 #endif
61 
Strdup(string s)62 char *Strdup(string s)
63 {
64   size_t size=s.size()+1;
65   char *dest=new(UseGC) char[size];
66   std::memcpy(dest,s.c_str(),size*sizeof(char));
67   return dest;
68 }
69 
StrdupNoGC(string s)70 char *StrdupNoGC(string s)
71 {
72   size_t size=s.size()+1;
73   char *dest=new char[size];
74   std::memcpy(dest,s.c_str(),size*sizeof(char));
75   return dest;
76 }
77 
StrdupMalloc(string s)78 char *StrdupMalloc(string s)
79 {
80   size_t size=s.size()+1;
81   char *dest=(char *) std::malloc(size);
82   std::memcpy(dest,s.c_str(),size*sizeof(char));
83   return dest;
84 }
85 
stripDir(string name)86 string stripDir(string name)
87 {
88   size_t p;
89 #ifdef __MSDOS__
90   p=name.rfind('\\');
91   if(p < string::npos) name.erase(0,p+1);
92 #endif
93   p=name.rfind('/');
94   if(p < string::npos) name.erase(0,p+1);
95   return name;
96 }
97 
stripFile(string name)98 string stripFile(string name)
99 {
100   size_t p;
101   bool dir=false;
102 #ifdef __MSDOS__
103   p=name.rfind('\\');
104   if(p < string::npos) {
105     dir=true;
106     while(p > 0 && name[p-1] == '\\') --p;
107     name.erase(p+1);
108   }
109 #endif
110   p=name.rfind('/');
111   if(p < string::npos) {
112     dir=true;
113     while(p > 0 && name[p-1] == '/') --p;
114     name.erase(p+1);
115   }
116 
117   return dir ? name : "";
118 }
119 
stripExt(string name,const string & ext)120 string stripExt(string name, const string& ext)
121 {
122   string suffix="."+ext;
123   size_t p=name.rfind(suffix);
124   size_t n=suffix.length();
125   if(n == 1 || p == name.length()-n)
126     return name.substr(0,p);
127   else return name;
128 }
129 
backslashToSlash(string & s)130 void backslashToSlash(string& s)
131 {
132   size_t p;
133   while((p=s.find('\\')) < string::npos)
134     s[p]='/';
135 }
136 
spaceToUnderscore(string & s)137 void spaceToUnderscore(string& s)
138 {
139   size_t p;
140   while((p=s.find(' ')) < string::npos)
141     s[p]='_';
142 }
143 
Getenv(const char * name,bool msdos)144 string Getenv(const char *name, bool msdos)
145 {
146   char *s=getenv(name);
147   if(!s) return "";
148   string S=string(s);
149   if(msdos) backslashToSlash(S);
150   return S;
151 }
152 
writeDisabled()153 void writeDisabled()
154 {
155   camp::reportError("Write to other directories disabled; override with option -globalwrite");
156 }
157 
cleanpath(string name)158 string cleanpath(string name)
159 {
160   string dir=stripFile(name);
161   name=stripDir(name);
162   spaceToUnderscore(name);
163   return dir+name;
164 }
165 
outpath(string name)166 string outpath(string name)
167 {
168   bool global=globalwrite();
169   string dir=stripFile(name);
170   if(global && !dir.empty()) return name;
171   string outdir=stripFile(outname());
172   if(!(global || dir.empty() || dir == outdir)) writeDisabled();
173   return outdir+stripDir(name);
174 }
175 
buildname(string name,string suffix,string aux)176 string buildname(string name, string suffix, string aux)
177 {
178   name=stripExt(outpath(name),defaultformat())+aux;
179   if(!suffix.empty()) name += "."+suffix;
180   return name;
181 }
182 
auxname(string filename,string suffix)183 string auxname(string filename, string suffix)
184 {
185   return buildname(filename,suffix,"_");
186 }
187 
Signal(int signum,sighandler_t handler)188 sighandler_t Signal(int signum, sighandler_t handler)
189 {
190   struct sigaction action,oldaction;
191   action.sa_handler=handler;
192   sigemptyset(&action.sa_mask);
193   action.sa_flags=0;
194   return sigaction(signum,&action,&oldaction) == 0 ? oldaction.sa_handler :
195     SIG_ERR;
196 }
197 
push_split(mem::vector<string> & a,const string & S)198 void push_split(mem::vector<string>& a, const string& S)
199 {
200   const char *p=S.c_str();
201   string s;
202   char c;
203   while((c=*(p++))) {
204     if(c == ' ') {
205       if(s.size() > 0) {
206         a.push_back(s);
207         s.clear();
208       }
209     } else s += c;
210   }
211   if(s.size() > 0)
212     a.push_back(s);
213 }
214 
args(const mem::vector<string> & s,bool quiet)215 char **args(const mem::vector<string>& s, bool quiet)
216 {
217   size_t count=s.size();
218 
219   char **argv=NULL;
220   argv=new char*[count+1];
221   for(size_t i=0; i < count; ++i)
222     argv[i]=StrdupNoGC(s[i]);
223 
224   if(!quiet && settings::verbose > 1) {
225     cerr << argv[0];
226     for(size_t i=1; i < count; ++i) cerr << " " << argv[i];
227     cerr << endl;
228   }
229 
230   argv[count]=NULL;
231   return argv;
232 }
233 
execError(const char * command,const char * hint,const char * application)234 void execError(const char *command, const char *hint, const char *application)
235 {
236   cerr << "Cannot execute " << command << endl;
237   if(*application == 0) application=hint;
238   if(hint) {
239     string s=string(hint);
240     transform(s.begin(), s.end(), s.begin(), toupper);
241     cerr << "Please put in a file " << getSetting<string>("config")
242          << ": " << endl << endl
243          << "import settings;" << endl
244          << hint << "=\"LOCATION\";" << endl << endl
245          << "where LOCATION specifies the location of "
246          << application << "." << endl << endl
247          << "Alternatively, set the environment variable ASYMPTOTE_" << s
248          << endl << "or use the command line option -" << hint
249          << "=\"LOCATION\". For further details, see" << endl
250          << "http://asymptote.sourceforge.net/doc/Configuring.html" << endl
251          << "http://asymptote.sourceforge.net/doc/Search-paths.html" << endl;
252   }
253 }
254 
255 // quiet: 0=none; 1=suppress stdout; 2=suppress stdout+stderr.
System(const mem::vector<string> & command,int quiet,bool wait,const char * hint,const char * application,int * ppid)256 int System(const mem::vector<string> &command, int quiet, bool wait,
257            const char *hint, const char *application, int *ppid)
258 {
259   int status;
260 
261   cout.flush(); // Flush stdout to avoid duplicate output.
262 
263   char **argv=args(command);
264 
265   int pid=fork();
266   if(pid == -1)
267     camp::reportError("Cannot fork process");
268 
269   if(pid == 0) {
270     if(interact::interactive) signal(SIGINT,SIG_IGN);
271     if(quiet) {
272       static int null=creat("/dev/null",O_WRONLY);
273       close(STDOUT_FILENO);
274       dup2(null,STDOUT_FILENO);
275       if(quiet == 2) {
276         close(STDERR_FILENO);
277         dup2(null,STDERR_FILENO);
278       }
279     }
280     if(argv) {
281       execvp(argv[0],argv);
282       execError(argv[0],hint,application);
283       _exit(-1);
284     }
285   }
286 
287   if(ppid) *ppid=pid;
288   for(;;) {
289     if(waitpid(pid, &status, wait ? 0 : WNOHANG) == -1) {
290       if(errno == ECHILD) return 0;
291       if(errno != EINTR) {
292         if(quiet < 2) {
293           ostringstream msg;
294           msg << "Command failed: ";
295           for(size_t i=0; i < command.size(); ++i) msg << command[i] << " ";
296           camp::reportError(msg);
297         }
298       }
299     } else {
300       if(!wait) return 0;
301       if(WIFEXITED(status)) {
302         if(argv) {
303           char **p=argv;
304           char *s;
305           while((s=*(p++)) != NULL)
306             delete [] s;
307           delete [] argv;
308         }
309         return WEXITSTATUS(status);
310       } else {
311         if(quiet < 2) {
312           ostringstream msg;
313           msg << "Command exited abnormally: ";
314           for(size_t i=0; i < command.size(); ++i) msg << command[i] << " ";
315           camp::reportError(msg);
316         }
317       }
318     }
319   }
320 }
321 
stripblanklines(const string & s)322 string stripblanklines(const string& s)
323 {
324   string S=string(s);
325   bool blank=true;
326   const char *t=S.c_str();
327   size_t len=S.length();
328 
329   for(size_t i=0; i < len; i++) {
330     if(t[i] == '\n') {
331       if(blank) S[i]=' ';
332       else blank=true;
333     } else if(t[i] != '\t' && t[i] != ' ') blank=false;
334   }
335   return S;
336 }
337 
338 char *startpath=NULL;
339 
noPath()340 void noPath()
341 {
342   camp::reportError("Cannot get current path");
343 }
344 
getPath(char * p)345 char *getPath(char *p)
346 {
347 #ifdef MAXPATHLEN
348   static size_t size = MAXPATHLEN;
349 #else
350   static size_t size = 1024;
351 #endif
352   if(!p) p=new(UseGC) char[size];
353   if(!p) noPath();
354   else while(getcwd(p,size) == NULL) {
355       if(errno == ERANGE) {
356         size *= 2;
357         p=new(UseGC) char[size];
358       } else {noPath(); p=NULL;}
359     }
360   return p;
361 }
362 
setPath(const char * s,bool quiet)363 const char *setPath(const char *s, bool quiet)
364 {
365   if(startpath == NULL) startpath=getPath(startpath);
366   if(s == NULL || *s == 0) s=startpath;
367   int rc=chdir(s);
368   if(rc != 0) {
369     ostringstream buf;
370     buf << "Cannot change to directory '" << s << "'";
371     camp::reportError(buf);
372   }
373   char *p=getPath();
374   if(p && (!interact::interactive || quiet) && verbose > 1)
375     cout << "cd " << p << endl;
376   return p;
377 }
378 
push_command(mem::vector<string> & a,const string & s)379 void push_command(mem::vector<string>& a, const string& s)
380 {
381   a.push_back(s);
382 #ifdef __MSDOS__
383   if(s == "cmd") {
384     a.push_back("/c");
385     a.push_back("start");
386     a.push_back("\"\"");
387   }
388 #endif
389 }
390 
popupHelp()391 void popupHelp() {
392   // If the popped-up help is already running, pid stores the pid of the viewer.
393   static int pid=0;
394 
395   // Status is ignored.
396   static int status=0;
397 
398   // If the help viewer isn't running (or its last run has termined), launch the
399   // viewer again.
400   if (pid==0 || (waitpid(pid, &status, WNOHANG) == pid)) {
401     mem::vector<string> cmd;
402     push_command(cmd,getSetting<string>("pdfviewer"));
403     string viewerOptions=getSetting<string>("pdfviewerOptions");
404     if(!viewerOptions.empty())
405       cmd.push_back(viewerOptions);
406     cmd.push_back(docdir+dirsep+"asymptote.pdf");
407     status=System(cmd,0,false,"pdfviewer","your PDF viewer",&pid);
408   }
409 }
410 
411 const char *intrange="integer argument is outside valid range";
412 const char *uintrange="integer argument is outside valid unsigned range";
413 
unsignedcast(Int n)414 unsigned unsignedcast(Int n)
415 {
416   if(n < 0 || n/2 > INT_MAX)
417     vm::error(uintrange);
418   return (unsigned) n;
419 }
420 
unsignedIntcast(Int n)421 unsignedInt unsignedIntcast(Int n)
422 {
423   if(n < 0)
424     vm::error(uintrange);
425   return (unsignedInt) n;
426 }
427 
intcast(Int n)428 int intcast(Int n)
429 {
430   if(Abs(n) > INT_MAX)
431     vm::error(intrange);
432   return (int) n;
433 }
434 
Intcast(unsignedInt n)435 Int Intcast(unsignedInt n)
436 {
437   if(n > (unsignedInt) Int_MAX)
438     vm::error(intrange);
439   return (Int) n;
440 }
441