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