1 /*
2  * IceWM
3  *
4  * Copyright (C) 1997-2002 Marko Macek
5  */
6 #include "config.h"
7 #include "base.h"
8 #include <errno.h>
9 #include <limits.h>
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 #include <unistd.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <pwd.h>
20 
21 #if defined(__GXX_RTTI) && (__GXX_ABI_VERSION >= 1002)
22 #define HAVE_GCC_ABI_DEMANGLE
23 #endif
24 #ifdef HAVE_GCC_ABI_DEMANGLE
25 #include <cxxabi.h>
26 #endif
27 #if defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_EXECINFO_H)
28 #include <execinfo.h>
29 #endif
30 
31 #include "intl.h"
32 #include "ascii.h"
33 
34 using namespace ASCII;
35 
36 #ifdef DEBUG
37 bool debug = false;
38 bool debug_z = false;
39 #endif
40 
endMsg(const char * msg)41 static void endMsg(const char *msg) {
42     if (*msg == 0 || msg[strlen(msg)-1] != '\n') {
43         fputc('\n', stderr);
44     }
45     fflush(stderr);
46 }
47 
die(int exitcode,char const * msg,...)48 void die(int exitcode, char const *msg, ...) {
49     fprintf(stderr, "%s: ", ApplicationName);
50 
51     va_list ap;
52     va_start(ap, msg);
53     vfprintf(stderr, msg, ap);
54     va_end(ap);
55     endMsg(msg);
56 
57     exit(exitcode);
58 }
59 
precondition(const char * expr,const char * file,int line)60 void precondition(const char *expr, const char *file, int line) {
61     fprintf(stderr, "%s: PRECONDITION FAILED at %s:%d: ( %s )\n",
62             ApplicationName, file, line, expr);
63     fflush(stderr);
64     show_backtrace();
65     abort();
66 }
67 
warn(char const * msg,...)68 void warn(char const *msg, ...) {
69     fprintf(stderr, "%s: ", ApplicationName);
70     fputs(_("Warning: "), stderr);
71 
72     va_list ap;
73     va_start(ap, msg);
74     vfprintf(stderr, msg, ap);
75     va_end(ap);
76     endMsg(msg);
77 }
78 
fail(char const * msg,...)79 void fail(char const *msg, ...) {
80     int errcode = errno;
81     fprintf(stderr, "%s: ", ApplicationName);
82     fputs(_("Warning: "), stderr);
83 
84     va_list ap;
85     va_start(ap, msg);
86     vfprintf(stderr, msg, ap);
87     va_end(ap);
88     fprintf(stderr, ": %s\n", strerror(errcode));
89     fflush(stderr);
90 }
91 
msg(char const * msg,...)92 void msg(char const *msg, ...) {
93     fprintf(stderr, "%s: ", ApplicationName);
94 
95     va_list ap;
96     va_start(ap, msg);
97     vfprintf(stderr, msg, ap);
98     va_end(ap);
99     endMsg(msg);
100 }
101 
tlog(char const * msg,...)102 void tlog(char const *msg, ...) {
103     timeval now;
104     gettimeofday(&now, nullptr);
105     struct tm *loc = localtime(&now.tv_sec);
106 
107     fprintf(stderr, "%02d:%02d:%02d.%03u: %s: ", loc->tm_hour,
108             loc->tm_min, loc->tm_sec,
109             (unsigned)(now.tv_usec / 1000),
110             ApplicationName);
111 
112     va_list ap;
113     va_start(ap, msg);
114     vfprintf(stderr, msg, ap);
115     va_end(ap);
116     endMsg(msg);
117 }
118 
cstrJoin(char const * str,...)119 char *cstrJoin(char const *str, ...) {
120     va_list ap;
121     char const *s;
122     char *res, *p;
123     int len = 0;
124 
125     if (str == nullptr)
126         return nullptr;
127 
128     va_start(ap, str);
129     s = str;
130     while (s) {
131         len += strlen(s);
132         s = va_arg(ap, char *);
133     }
134     va_end(ap);
135 
136     if ((p = res = new char[len + 1]) == nullptr)
137         return nullptr;
138 
139     va_start(ap, str);
140     s = str;
141     while (s) {
142         len = strlen(s);
143         memcpy(p, s, len);
144         p += len;
145         s = va_arg(ap, char *);
146     }
147     va_end(ap);
148     *p = 0;
149     return res;
150 }
151 
152 #if (__GNUC__ == 3) || defined(__clang__)
153 
__cxa_pure_virtual()154 extern "C" void __cxa_pure_virtual() {
155     warn("BUG: Pure virtual method called. Terminating.");
156     abort();
157 }
158 
159 /* time to rewrite in C */
160 
161 #endif
162 
163 #ifdef NEED_ALLOC_OPERATORS
164 
MALLOC(unsigned int len)165 static void *MALLOC(unsigned int len) {
166     if (len == 0) return 0;
167     return malloc(len);
168 }
169 
FREE(void * p)170 static void FREE(void *p) {
171     if (p) free(p);
172 }
173 
operator new(size_t len)174 void *operator new(size_t len) {
175     return MALLOC(len);
176 }
177 
operator new[](size_t len)178 void *operator new[](size_t len) {
179     if (len == 0) len = 1;
180     return MALLOC(len);
181 }
182 
operator delete(void * p)183 void operator delete (void *p) {
184     FREE(p);
185 }
186 
operator delete[](void * p)187 void operator delete[](void *p) {
188     FREE(p);
189 }
190 
191 #endif
192 
193 /* Prefer this as a safer alternative over strcpy. Return strlen(from). */
194 #if !defined(HAVE_STRLCPY) || !HAVE_STRLCPY
strlcpy(char * dest,const char * from,size_t dest_size)195 size_t strlcpy(char *dest, const char *from, size_t dest_size)
196 {
197     const char *in = from;
198     if (dest_size > 0) {
199         char *to = dest;
200         char *const stop = to + dest_size - 1;
201         while (to < stop && *in)
202             *to++ = *in++;
203         *to = '\0';
204     }
205     while (*in) ++in;
206     return in - from;
207 }
208 #endif
209 
210 /* Prefer this over strcat. Return strlen(dest) + strlen(from). */
211 #if !defined(HAVE_STRLCAT) || !HAVE_STRLCAT
strlcat(char * dest,const char * from,size_t dest_size)212 size_t strlcat(char *dest, const char *from, size_t dest_size)
213 {
214     char *to = dest;
215     char *const stop = to + dest_size - 1;
216     while (to < stop && *to) ++to;
217     return to - dest + strlcpy(to, from, dest_size - (to - dest));
218 }
219 #endif
220 
newstr(char const * str)221 char *newstr(char const *str) {
222     return (str != nullptr ? newstr(str, strlen(str)) : nullptr);
223 }
224 
newstr(char const * str,char const * delim)225 char *newstr(char const *str, char const *delim) {
226     return (str != nullptr ? newstr(str, strcspn(str, delim)) : nullptr);
227 }
228 
newstr(char const * str,int len)229 char *newstr(char const *str, int len) {
230     char *s(nullptr);
231 
232     if (str != nullptr && len >= 0 && (s = new char[len + 1]) != nullptr) {
233         memcpy(s, str, len);
234         s[len] = '\0';
235     }
236 
237     return s;
238 }
239 
demangle(const char * str)240 char* demangle(const char* str) {
241 #ifdef HAVE_GCC_ABI_DEMANGLE
242     int status = 0;
243     char* c_name = abi::__cxa_demangle(str, nullptr, nullptr, &status);
244     if (c_name)
245         return c_name;
246 #endif
247     return strdup(str);
248 }
249 
little()250 bool little() {
251 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
252     return true;
253 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
254     return false;
255 #else
256 #error undefined byte order
257 #endif
258 }
259 
strhash(const char * str)260 unsigned long strhash(const char* str) {
261     unsigned long hash = 5381;
262     for (; *str; ++str)
263         hash = 33 * hash ^ (unsigned char) *str;
264     return hash;
265 }
266 
267 /*
268  *      Returns zero if s2 is a prefix of s1.
269  *
270  *      Ie the following will match and return 0 with respective given
271  *      arguments:
272  *
273  *              "--interface=/tmp" "--interface"
274  */
strpcmp(char const * str,char const * pfx,char const * delim)275 int strpcmp(char const * str, char const * pfx, char const * delim) {
276     if (str == nullptr || pfx == nullptr) return -1;
277     while (*pfx == *str && *pfx != '\0') ++str, ++pfx;
278 
279     return (*pfx == '\0' && strchr(delim, *str) ? 0 : *str - *pfx);
280 }
281 
strnxt(const char * str,const char * delim)282 char const * strnxt(const char * str, const char * delim) {
283     str+= strcspn(str, delim);
284     str+= strspn(str, delim);
285     return str;
286 }
287 
288 #ifndef HAVE_MEMRCHR
memrchr(const void * ptr,char chr,size_t num)289 void* memrchr(const void* ptr, char chr, size_t num) {
290     char* str = (char *) ptr;
291     char* q = str + num;
292     while (q > str && *--q != chr);
293     return q >= str && *q == chr ? q : nullptr;
294 }
295 #endif
296 
tokens(char * data,const char * sep)297 tokens::tokens(char* data, const char* sep)
298     : sep(sep)
299     , save(nullptr)
300     , tok(strtok_r(data, sep, &save))
301 {
302 }
303 
operator ++()304 char* tokens::operator++() {
305     return tok = strtok_r(nullptr, sep, &save);
306 }
307 
GetShortArgument(char * & ret,const char * name,char ** & argpp,char ** endpp)308 bool GetShortArgument(char* &ret, const char *name, char** &argpp, char **endpp)
309 {
310     unsigned int alen = strlen(name);
311     if (**argpp != '-' || strncmp((*argpp) + 1, name, alen))
312         return false;
313     char ch = (*argpp)[1 + alen];
314     if (ch) {
315         ret = (*argpp) + 1 + alen + (ch == '=');
316         return true;
317     }
318     else if (argpp + 1 >= endpp)
319         return false;
320     ++argpp;
321     ret = *argpp;
322     return true;
323 }
324 
GetLongArgument(char * & ret,const char * name,char ** & argpp,char ** endpp)325 bool GetLongArgument(char* &ret, const char *name, char** &argpp, char **endpp)
326 {
327     unsigned int alen = strlen(name);
328     if (strncmp(*argpp, "--", 2) || strncmp((*argpp) + 2, name, alen))
329         return false;
330     char ch = (*argpp)[2 + alen];
331     if (ch == '=') {
332         ret = (*argpp) + 3 + alen;
333         return true;
334     }
335     if (argpp + 1 >= endpp)
336         return false;
337     ++argpp;
338     ret = *argpp;
339     return true;
340 }
341 
GetArgument(char * & ret,const char * sn,const char * ln,char ** & arg,char ** end)342 bool GetArgument(char* &ret, const char *sn, const char *ln, char** &arg, char **end)
343 {
344     bool got = false;
345     if (arg && *arg && **arg == '-') {
346         if (arg[0][1] == '-') {
347             got = GetLongArgument(ret, ln, arg, end);
348         } else {
349             got = GetShortArgument(ret, sn, arg, end);
350         }
351     }
352     return got;
353 }
354 
is_short_switch(const char * arg,const char * name)355 bool is_short_switch(const char *arg, const char *name)
356 {
357     return arg && *arg == '-' && 0 == strcmp(arg + 1, name);
358 }
359 
is_long_switch(const char * arg,const char * name)360 bool is_long_switch(const char *arg, const char *name)
361 {
362     return arg && *arg == '-' && arg[1] == '-' && 0 == strcmp(arg + 2, name);
363 }
364 
is_switch(const char * arg,const char * short_name,const char * long_name)365 bool is_switch(const char *arg, const char *short_name, const char *long_name)
366 {
367     return is_short_switch(arg, short_name) || is_long_switch(arg, long_name);
368 }
369 
is_copying_switch(const char * arg)370 bool is_copying_switch(const char *arg)
371 {
372     return is_switch(arg, "C", "copying");
373 }
374 
is_help_switch(const char * arg)375 bool is_help_switch(const char *arg)
376 {
377     return is_switch(arg, "h", "help") || is_switch(arg, "?", "?");
378 }
379 
is_version_switch(const char * arg)380 bool is_version_switch(const char *arg)
381 {
382     return is_switch(arg, "V", "version");
383 }
384 
print_help_exit(const char * help)385 void print_help_exit(const char *help)
386 {
387     printf(_("Usage: %s [OPTIONS]\n"
388              "Options:\n"
389              "%s"
390              "\n"
391              "  -C, --copying       Prints license information and exits.\n"
392              "  -V, --version       Prints version information and exits.\n"
393              "  -h, --help          Prints this usage screen and exits.\n"
394              "\n"),
395             ApplicationName, help);
396     exit(0);
397 }
398 
print_version_exit(const char * version)399 void print_version_exit(const char *version)
400 {
401     printf("%s %s, %s.\n", ApplicationName, version,
402         "Copyright 1997-2012 Marko Macek, 2001 Mathias Hasselmann");
403     exit(0);
404 }
405 
print_copying_exit()406 void print_copying_exit()
407 {
408     printf("%s\n",
409     "IceWM is licensed under the GNU Library General Public License.\n"
410     "See the file COPYING in the distribution for full details.\n"
411     );
412     exit(0);
413 }
414 
check_help_version(const char * arg,help_text_fun help,const char * version)415 void check_help_version(const char *arg, help_text_fun help, const char *version)
416 {
417     if (is_help_switch(arg)) {
418         print_help_exit(help());
419     }
420     if (is_version_switch(arg)) {
421         print_version_exit(version);
422     }
423     if (is_copying_switch(arg)) {
424         print_copying_exit();
425     }
426 }
427 
help_display()428 static const char* help_display()
429 {
430     return "  -d, --display=NAME    NAME of the X server to use.\n";
431 }
432 
check_argv(int argc,char ** argv,help_text_fun help,const char * version)433 void check_argv(int argc, char **argv, help_text_fun help, const char *version)
434 {
435     if (ApplicationName == nullptr) {
436         ApplicationName = my_basename(argv[0]);
437     }
438     for (char **arg = argv + 1; arg < argv + argc; ++arg) {
439         if ('-' == arg[0][0]) {
440             char c = ('-' == arg[0][1]) ? arg[0][2] : arg[0][1];
441             if (c == '\0') {
442                 if ('-' == arg[0][1]) {
443                     break;
444                 }
445             }
446             else if (strchr("h?vVcC", c)) {
447                 check_help_version(*arg,
448                     help ? help : help_display,
449                     version);
450             }
451             else if (c == 'd') {
452                 char* value(nullptr);
453                 if (GetArgument(value, "d", "display", arg, argv + argc)) {
454                     setenv("DISPLAY", value, 1);
455                 }
456             }
457         }
458     }
459 }
460 
errno_string()461 const char* errno_string() {
462     return strerror(errno);
463 }
464 
my_basename(const char * path)465 const char *my_basename(const char *path) {
466     const char *base = ::strrchr(path, '/');
467     return (base ? base + 1 : path);
468 }
469 
isFile(const char * path)470 bool isFile(const char* path) {
471     struct stat s;
472     return stat(path, &s) == 0 && S_ISREG(s.st_mode);
473 }
474 
isExeFile(const char * path)475 bool isExeFile(const char* path) {
476     struct stat s;
477     return stat(path, &s) == 0 && S_ISREG(s.st_mode)
478         && (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
479 }
480 
481 // parse a C-style identifier
identifier(const char * text)482 char* identifier(const char* text) {
483     const char* scan = text;
484     if (*scan == '_' || isAlpha(*scan)) {
485         while (*++scan == '_' || isAlnum(*scan)) { }
486     }
487     return text < scan ? newstr(text, int(scan - text)) : nullptr;
488 }
489 
490 // free a string when out-of-scope
491 class strp {
492 public:
strp(char * string=nullptr)493     strp(char* string = nullptr) : ptr(string) { }
~strp()494     ~strp() { delete[] ptr; }
operator char*()495     operator char*() { return ptr; }
operator =(char * p)496     void operator=(char* p) { ptr = p; }
497 private:
498     char* ptr;
499 };
500 
501 // obtain the home directory for a named user
userhome(const char * username)502 char* userhome(const char* username) {
503     const size_t buflen = 1234;
504     char buf[buflen];
505     passwd pwd, *result = nullptr;
506     int get;
507     if (nonempty(username)) {
508         get = getpwnam_r(username, &pwd, buf, buflen, &result);
509     } else {
510         get = getpwuid_r(getuid(), &pwd, buf, buflen, &result);
511     }
512     if (get == 0 && result) {
513         return newstr(result->pw_dir);
514     } else {
515         return nullptr;
516     }
517 }
518 
519 // expand a leading environment variable
dollar_expansion(const char * name)520 char* dollar_expansion(const char* name) {
521     if (name[0] == '$') {
522         bool leftb = (name[1] == '{');
523         strp ident = identifier(name + 1 + leftb);
524         if (ident) {
525             size_t idlen = strlen(ident);
526             bool right = (name[1 + leftb + idlen] == '}');
527             if (leftb == right) {
528                 char* expand = getenv(ident);
529                 if (expand) {
530                     size_t xlen = strlen(expand);
531                     size_t size = xlen + strlen(name + idlen);
532                     char* path = new char[size];
533                     if (path) {
534                         strlcpy(path, expand, size);
535                         strlcat(path, name + 1 + leftb + idlen + right, size);
536                         return path;
537                     }
538                 }
539             }
540         }
541     }
542     return nullptr;
543 }
544 
545 // expand a leading tilde+slash or tilde+username
tilde_expansion(const char * name)546 char* tilde_expansion(const char* name) {
547     if (name[0] != '~') {
548         return nullptr;
549     }
550     else if (name[1] == '/' || name[1] == '\0') {
551         char* home = getenv("HOME");
552         strp user;
553         if (isEmpty(home)) {
554             user = userhome(nullptr);
555             home = user;
556         }
557         if (nonempty(home)) {
558             size_t size = strlen(home) + strlen(name);
559             char* path = new char[size];
560             if (path) {
561                 strlcpy(path, home, size);
562                 strlcat(path, name + 1, size);
563                 return path;
564             }
565         }
566     }
567     else {
568         strp ident = identifier(name + 1);
569         if (ident) {
570             size_t idlen = strlen(ident);
571             if (name[1 + idlen] == '/' || name[1 + idlen] == '\0') {
572                 strp home = userhome(ident);
573                 if (home) {
574                     size_t hlen = strlen(home);
575                     size_t size = hlen + strlen(name + idlen);
576                     char* path = new char[size];
577                     if (path) {
578                         strlcpy(path, home, size);
579                         strlcat(path, name + 1 + idlen, size);
580                         return path;
581                     }
582                 }
583             }
584         }
585     }
586     return nullptr;
587 }
588 
589 // lookup "name" in PATH and return a new string or 0.
path_lookup(const char * name)590 char* path_lookup(const char* name) {
591     if (isEmpty(name))
592         return nullptr;
593 
594     if (name[0] == '~') {
595         char* expand = tilde_expansion(name);
596         if (expand) {
597             if (isExeFile(expand)) {
598                 return expand;
599             } else {
600                 delete[] expand;
601             }
602         }
603         return nullptr;
604     }
605 
606     if (name[0] == '$') {
607         char* expand = dollar_expansion(name);
608         if (expand) {
609             if (isExeFile(expand)) {
610                 return expand;
611             } else {
612                 delete[] expand;
613             }
614         }
615         return nullptr;
616     }
617 
618     if (strchr(name, '/')) {
619         return isExeFile(name) ? newstr(name) : nullptr;
620     }
621 
622     strp env = newstr(getenv("PATH"));
623     if (env == nullptr)
624         return nullptr;
625 
626     const size_t namlen = strlen(name);
627 
628     for (tokens directory(env, ":"); directory; ++directory) {
629         size_t dirlen = strlen(directory);
630         size_t length = dirlen + namlen + 3;
631         const size_t bufsize = 1234;
632         if (length < bufsize) {
633             char filebuf[bufsize];
634             snprintf(filebuf, bufsize, "%s/%s",
635                      dirlen ? directory : ".", name);
636             if (isExeFile(filebuf)) {
637                 return newstr(filebuf);
638             }
639         }
640     }
641     return nullptr;
642 }
643 
644 #ifdef __gnu_hurd__
getprogname()645 const char* getprogname() {
646     return ApplicationName;
647 }
648 #endif
649 
650 // get path of executable.
progpath()651 char* progpath() {
652 #ifdef __linux__
653     char* path = program_invocation_name;
654     bool fail = (isEmpty(path) || !isExeFile(path));
655     if (fail) {
656         const size_t linksize = 123;
657         char link[linksize];
658         const size_t procsize = 42;
659         char proc[procsize];
660         snprintf(proc, procsize, "/proc/%d/exe", int(getpid()));
661         ssize_t read = readlink(proc, link, linksize);
662         if (inrange<ssize_t>(read, 1, ssize_t(linksize - 1))) {
663             link[read] = 0;
664             char* annotation = strstr(link, " (deleted)");
665             if (annotation && annotation > link) {
666                 annotation[0] = 0;
667             }
668             if ((fail = access(link, R_OK | X_OK)) == 0) {
669                 path = program_invocation_name = newstr(link);
670                 INFO("1: set program_invocation_name %s", path);
671             }
672         }
673     }
674     if (fail && (path = path_lookup(path)) != nullptr) {
675         program_invocation_name = path;
676         INFO("2: set program_invocation_name %s", path);
677     }
678 #else
679     static char* path;
680     if (path == 0)
681         path = path_lookup(getprogname());
682 #endif
683     return path;
684 }
685 
show_backtrace(const int limit)686 void show_backtrace(const int limit) {
687 #if defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_EXECINFO_H)
688     const int asize = Elvis(limit, 20);
689     void *array[asize];
690     const int count = backtrace(array, asize);
691     const char tool[] = "/usr/bin/addr2line";
692     char* prog = progpath();
693     char* path = prog ? prog : path_lookup("icewm");
694 
695     timeval now;
696     gettimeofday(&now, nullptr);
697     struct tm *loc = localtime(&now.tv_sec);
698     unsigned msec = (unsigned)(now.tv_usec / 1000);
699 
700     fprintf(stderr, "%02d:%02d:%02d.%03u: %s:\n", loc->tm_hour, loc->tm_min,
701             loc->tm_sec, msec, "backtrace"); fflush(stderr);
702 
703     int status(1);
704     if (path && access(tool, X_OK) == 0) {
705         const size_t bufsize(1234);
706         char buf[bufsize];
707         snprintf(buf, bufsize, "%s -C -f -p -s -e '%s'", tool, path);
708         size_t len = strlen(buf);
709         for (int i = 0; i < count && len + 21 < bufsize; ++i) {
710             snprintf(buf + len, bufsize - len, " %p", array[i]);
711             len += strlen(buf + len);
712         }
713         FILE* fp = popen(buf, "r");
714         if (fp) {
715             const int linesize(256);
716             char line[linesize];
717             int lineCount = 0;
718             while (fgets(line, linesize, fp)) {
719                 ++lineCount;
720                 if (strncmp(line, "?? ??:0", 7)) {
721                     fputs(line, stderr);
722                 }
723             }
724             if (pclose(fp) == 0 && lineCount >= count) {
725                 status = 0;
726             }
727         }
728         else
729             status = system(buf);
730     }
731     if (status) {
732         backtrace_symbols_fd(array, count, 2);
733     }
734     fputs("end\n\n", stderr); fflush(stderr);
735 #endif
736 }
737 
738 // vim: set sw=4 ts=4 et:
739