1 /*
2 * Error and message routines
3 *
4 * Copyright 2020 by Gray Watson
5 *
6 * This file is part of the dmalloc package.
7 *
8 * Permission to use, copy, modify, and distribute this software for
9 * any purpose and without fee is hereby granted, provided that the
10 * above copyright notice and this permission notice appear in all
11 * copies, and that the name of Gray Watson not be used in advertising
12 * or publicity pertaining to distribution of the document or software
13 * without specific, written prior permission.
14 *
15 * Gray Watson makes no representations about the suitability of the
16 * software described herein for any purpose. It is provided "as is"
17 * without express or implied warranty.
18 *
19 * The author may be contacted via http://dmalloc.com/
20 */
21
22 /*
23 * This file contains the routines needed for processing error codes
24 * produced by the library.
25 */
26
27 #include <fcntl.h> /* for O_WRONLY, etc. */
28
29 #if HAVE_STDIO_H
30 # include <stdio.h> /* for FILE */
31 #endif
32 #if HAVE_STDARG_H
33 # include <stdarg.h> /* for message vsprintf */
34 #endif
35 #if HAVE_STDLIB_H
36 # include <stdlib.h> /* for abort */
37 #endif
38 #if HAVE_UNISTD_H
39 # include <unistd.h> /* for write */
40 #endif
41
42 #include "conf.h" /* up here for _INCLUDE */
43
44 /* for KILL_PROCESS define */
45 #if USE_ABORT == 0
46 #ifdef KILL_INCLUDE
47 #include KILL_INCLUDE /* for kill signals */
48 #endif
49 #endif
50
51 #if LOG_PNT_TIMEVAL
52 # ifdef TIMEVAL_INCLUDE
53 # include TIMEVAL_INCLUDE
54 # endif
55 #else
56 # if HAVE_TIME
57 # ifdef TIME_INCLUDE
58 # include TIME_INCLUDE
59 # endif
60 # endif
61 #endif
62
63 #define DMALLOC_DISABLE
64
65 #include "dmalloc.h"
66
67 #include "append.h"
68 #include "chunk.h" /* for _dmalloc_memory_limit */
69 #include "compat.h"
70 #include "debug_tok.h"
71 #include "env.h" /* for LOGPATH_INIT */
72 #include "error.h"
73 #include "error_val.h"
74 #include "dmalloc_loc.h"
75 #include "version.h"
76
77 #if LOCK_THREADS
78 #if IDENT_WORKS
79 #ident "@(#) $Information: lock-threads is enabled $"
80 #else
81 static char *information = "@(#) $Information: lock-threads is enabled $";
82 #endif
83 #endif
84
85 #define MINS_IN_HOUR 60
86 #define SECS_IN_MIN 60
87 #define SECS_IN_HOUR (MINS_IN_HOUR * SECS_IN_MIN)
88
89 /* external routines */
90 extern const char *dmalloc_strerror(const int errnum);
91
92 /*
93 * exported variables
94 */
95
96 /* address to look for. when discovered call dmalloc_error() */
97 DMALLOC_PNT _dmalloc_address = NULL;
98 /* when to stop at an address */
99 unsigned long _dmalloc_address_seen_n = 0;
100
101 /* global debug flags that are set my DMALLOC_DEBUG environ variable */
102 unsigned int _dmalloc_flags = 0;
103
104 /* global iteration counter for activities */
105 unsigned long _dmalloc_iter_c = 0;
106
107 /* how often to check the heap */
108 unsigned long _dmalloc_check_interval = 0;
109
110 #if LOG_PNT_TIMEVAL
111 /* overhead information storing when the library started up for elapsed time */
112 TIMEVAL_TYPE _dmalloc_start;
113 #endif
114
115 /* NOTE: we do the ifdef this way for fillproto */
116 #if LOG_PNT_TIMEVAL == 0
117 #if HAVE_TIME
118 TIME_TYPE _dmalloc_start = 0;
119 #endif
120 #endif
121
122 /* when we are going to startup our locking subsystem */
123 int _dmalloc_lock_on = 0;
124
125 /* global flag which indicates when we are aborting */
126 int _dmalloc_aborting_b = 0;
127
128 /* local variables */
129 static int outfile_fd = -1; /* output file descriptor */
130 /* the following are here to reduce stack overhead */
131 static char error_str[1024]; /* error string buffer */
132 static char message_str[1024]; /* message string buffer */
133
134 /*
135 * void _dmalloc_open_log
136 *
137 * Build our logfile path in the buffer provided.
138 */
build_logfile_path(char * buf,const int buf_len)139 static void build_logfile_path(char *buf, const int buf_len)
140 {
141 char *bounds_p, *path_p, *buf_p;
142 int len;
143
144 if (dmalloc_logpath == NULL) {
145 buf[0] = '\0';
146 return;
147 }
148
149 buf_p = buf;
150 bounds_p = buf + buf_len;
151
152 for (path_p = dmalloc_logpath; *path_p != '\0'; path_p++) {
153
154 /* if we don't have to do anything special then just continue */
155 if (*path_p != '%' || *(path_p + 1) == '\0') {
156 if (buf_p < bounds_p) {
157 *buf_p++ = *path_p;
158 }
159 continue;
160 }
161
162 /* skip over the % */
163 path_p++;
164
165 /* dump the hostname */
166 if (*path_p == 'h') {
167 #if HAVE_GETHOSTNAME
168 char our_host[128];
169 gethostname(our_host, sizeof(our_host));
170 buf_p = append_string(buf_p, bounds_p, our_host);
171 #else
172 buf_p = append_string(buf_p, bounds_p, "not-gethostname");
173 #endif
174 }
175 /* dump the thread-id */
176 if (*path_p == 'i') {
177 #if LOG_PNT_THREAD_ID
178 char id_str[256];
179 THREAD_TYPE id;
180
181 id = THREAD_GET_ID();
182 THREAD_ID_TO_STRING(id_str, sizeof(id_str), id);
183 buf_p = append_string(buf_p, bounds_p, id_str);
184 #else
185 buf_p = append_string(buf_p, bounds_p, "no-thread-id");
186 #endif
187 }
188 /* dump the pid -- also support backwards compatibility with %d */
189 if (*path_p == 'p' || *path_p == 'd') {
190 #if HAVE_GETPID
191 /* we make it long in case it's big and we hope it will promote if not */
192 long our_pid = getpid();
193 buf_p = append_long(buf_p, bounds_p, our_pid, 10);
194 #else
195 buf_p = append_string(buf_p, bounds_p, "no-getpid");
196 #endif
197 }
198 /* dump the time value */
199 if (*path_p == 't') {
200 #if HAVE_TIME
201 /* we make time a long here so it will promote */
202 long now;
203 now = time(NULL);
204 buf_p = append_long(buf_p, bounds_p, now, 10);
205 #else
206 buf_p = append_string(buf_p, bounds_p, "no-time");
207 #endif
208 }
209 /* dump the user-id */
210 if (*path_p == 'u') {
211 #if HAVE_GETUID
212 /* we make it long in case it's big and we hope it will promote if not */
213 long our_uid = getuid();
214 buf_p = append_long(buf_p, bounds_p, our_uid, 10);
215 #else
216 buf_p = append_string(buf_p, bounds_p, "no-uid");
217 #endif
218 }
219 }
220
221 if (buf_p >= bounds_p - 1) {
222 /* NOTE: we can't use dmalloc_message of course so do it the hard way */
223 len = loc_snprintf(error_str, sizeof(error_str),
224 "debug-malloc library: logfile path too large '%s'\r\n",
225 dmalloc_logpath);
226 (void)write(STDERR, error_str, len);
227 }
228
229 append_null(buf_p, bounds_p);
230 }
231
232 /*
233 * void _dmalloc_open_log
234 *
235 * Open up our log file and write some version of settings information.
236 */
_dmalloc_open_log(void)237 void _dmalloc_open_log(void)
238 {
239 char log_path[1024];
240 int len;
241
242 /* if it's already open or if we don't have a log file configured */
243 if (outfile_fd >= 0
244 || dmalloc_logpath == NULL) {
245 return;
246 }
247
248 build_logfile_path(log_path, sizeof(log_path));
249
250 /* open our logfile */
251 outfile_fd = open(log_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
252 if (outfile_fd < 0) {
253 /* NOTE: we can't use dmalloc_message of course so do it the hardway */
254 len = loc_snprintf(error_str, sizeof(error_str),
255 "debug-malloc library: could not open '%s'\r\n",
256 log_path);
257 (void)write(STDERR, error_str, len);
258 /* disable log_path */
259 dmalloc_logpath = NULL;
260 return;
261 }
262
263 /*
264 * NOTE: this makes it go recursive here, but it will never enter
265 * this section of code.
266 */
267
268 dmalloc_message("Dmalloc version '%s' from '%s'",
269 dmalloc_version, DMALLOC_HOME);
270 dmalloc_message("flags = %#x, logfile '%s'", _dmalloc_flags, log_path);
271 dmalloc_message("interval = %lu, addr = %p, seen # = %ld, limit = %ld",
272 _dmalloc_check_interval, _dmalloc_address,
273 _dmalloc_address_seen_n, _dmalloc_memory_limit);
274 #if LOCK_THREADS
275 dmalloc_message("threads enabled, lock-on = %d, lock-init = %d",
276 _dmalloc_lock_on, THREAD_INIT_LOCK);
277 #endif
278
279 #if LOG_PNT_TIMEVAL
280 {
281 char time_buf[64];
282 dmalloc_message("starting time = %s",
283 _dmalloc_ptimeval(&_dmalloc_start, time_buf,
284 sizeof(time_buf), 0));
285 }
286 #else
287 #if HAVE_TIME /* NOT LOG_PNT_TIME */
288 {
289 char time_buf[64];
290 dmalloc_message("starting time = %s",
291 _dmalloc_ptime(&_dmalloc_start, time_buf,
292 sizeof(time_buf), 0));
293 }
294 #endif
295 #endif
296
297 #if HAVE_GETPID
298 {
299 /* we make it long in case it's big and we hope it will promote if not */
300 long our_pid = getpid();
301
302 dmalloc_message("process pid = %ld", our_pid);
303 }
304 #endif
305 }
306
307 /*
308 * void _dmalloc_reopen_log
309 *
310 * Re-open our log file which basically calls close() on the
311 * logfile-fd. If we change the name of the log-file then we will
312 * re-open the file.
313 */
_dmalloc_reopen_log(void)314 void _dmalloc_reopen_log(void)
315 {
316 /* no need to reopen it if it hasn't been reopened yet */
317 if (outfile_fd < 0) {
318 return;
319 }
320
321 if (dmalloc_logpath == NULL) {
322 dmalloc_message("Closing logfile to not be reopened");
323 }
324 else {
325 dmalloc_message("Closing logfile to be reopened as '%s'",
326 dmalloc_logpath);
327 }
328
329 (void)close(outfile_fd);
330 outfile_fd = -1;
331 /* we don't call open here, we'll let the next message do it */
332 }
333
334 #if LOG_PNT_TIMEVAL
335 /*
336 * char *_dmalloc_ptimeval
337 *
338 * Print the time into local buffer.
339 *
340 * Returns a pointer to the buf argument.
341 *
342 * ARGUMENTS:
343 *
344 * timeval_p -> Pointer to a time value.
345 *
346 * buf -> Internal buffer into which we are writing the time.
347 *
348 * buf_size -> Size of the buffer.
349 *
350 * elapsed_b -> Set to 1 to dump the elapsed instead of global time.
351 */
_dmalloc_ptimeval(const TIMEVAL_TYPE * timeval_p,char * buf,const int buf_size,const int elapsed_b)352 char *_dmalloc_ptimeval(const TIMEVAL_TYPE *timeval_p, char *buf,
353 const int buf_size, const int elapsed_b)
354 {
355 unsigned long hrs, mins, secs, usecs;
356
357 secs = timeval_p->tv_sec;
358 usecs = timeval_p->tv_usec;
359
360 if (elapsed_b) {
361 if (usecs >= _dmalloc_start.tv_usec) {
362 usecs -= _dmalloc_start.tv_usec;
363 }
364 else {
365 usecs = _dmalloc_start.tv_usec - usecs;
366 secs--;
367 }
368 secs -= _dmalloc_start.tv_sec;
369
370 hrs = secs / SECS_IN_HOUR;
371 mins = (secs / SECS_IN_MIN) % MINS_IN_HOUR;
372 secs %= SECS_IN_MIN;
373
374 (void)loc_snprintf(buf, buf_size, "%lu:%02lu:%02lu.%06lu",
375 hrs, mins, secs, usecs);
376 }
377 else {
378 (void)loc_snprintf(buf, buf_size, "%lu.%06lu", secs, usecs);
379 }
380
381 return buf;
382 }
383 #endif
384
385 /* NOTE: we do the ifdef this way for fillproto */
386 #if LOG_PNT_TIMEVAL == 0 && HAVE_TIME
387 /*
388 * char *_dmalloc_ptime
389 *
390 * Print the time into local buffer.
391 *
392 * Returns a Pointer to the buf argument.
393 *
394 * ARGUMENTS:
395 *
396 * time_p -> Pointer to a time value.
397 *
398 * buf -> Internal buffer into which we are writing the time.
399 *
400 * buf_size -> Size of the buffer.
401 *
402 * elapsed_b -> Set to 1 to dump the elapsed instead of global time.
403 */
_dmalloc_ptime(const TIME_TYPE * time_p,char * buf,const int buf_size,const int elapsed_b)404 char *_dmalloc_ptime(const TIME_TYPE *time_p, char *buf, const int buf_size,
405 const int elapsed_b)
406 {
407 unsigned long hrs, mins, secs;
408
409 secs = *time_p;
410
411 if (elapsed_b) {
412 secs -= _dmalloc_start;
413
414 hrs = secs / SECS_IN_HOUR;
415 mins = (secs / SECS_IN_MIN) % MINS_IN_HOUR;
416 secs %= SECS_IN_MIN;
417
418 (void)loc_snprintf(buf, buf_size, "%lu:%02lu:%02lu", hrs, mins, secs);
419 }
420 else {
421 (void)loc_snprintf(buf, buf_size, "%lu", secs);
422 }
423
424 return buf;
425 }
426 #endif
427
428 /*
429 * void _dmalloc_vmessage
430 *
431 * Message writer with vprintf like arguments which adds a line to the
432 * dmalloc logfile.
433 *
434 * NOTE: An internal snprintf has been implemented which doesn't support all
435 * formats. This was done to stop dmalloc from going recursive. YMMV.
436 *
437 * ARGUMENTS:
438 *
439 * format -> Printf-style format statement.
440 *
441 * args -> Already converted pointer to a stdarg list.
442 */
_dmalloc_vmessage(const char * format,va_list args)443 void _dmalloc_vmessage(const char *format, va_list args)
444 {
445 char *str_p, *bounds_p;
446 int len;
447
448 str_p = message_str;
449 bounds_p = str_p + sizeof(message_str);
450
451 /* no logpath and no print then no workie */
452 if (dmalloc_logpath == NULL
453 && ! BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_PRINT_MESSAGES)) {
454 return;
455 }
456
457 #if HAVE_GETPID && LOG_REOPEN
458 if (dmalloc_logpath != NULL) {
459 char *log_p;
460
461 /*
462 * This static pid will be checked to make sure it doesn't change.
463 * We make it long in case it's big and we hope it will promote if
464 * not.
465 */
466 static long current_pid = -1;
467 long new_pid;
468
469 new_pid = getpid();
470 if (new_pid != current_pid) {
471 /* NOTE: we need to do this _before_ the reopen otherwise we recurse */
472 current_pid = new_pid;
473
474 /* if the new pid doesn't match the old one then reopen it */
475 if (current_pid >= 0) {
476
477 /* this only works if there is a %p in the logpath */
478 for (log_p = dmalloc_logpath; *log_p != '\0'; log_p++) {
479 if (*log_p == '%' && *(log_p + 1) == 'p') {
480 _dmalloc_reopen_log();
481 break;
482 }
483 }
484 }
485 }
486 }
487 #endif
488
489 /* do we need to open the logfile? */
490 if (dmalloc_logpath != NULL && outfile_fd < 0) {
491 _dmalloc_open_log();
492 }
493
494 #if HAVE_TIME
495 #if LOG_TIME_NUMBER
496 {
497 long now;
498 now = time(NULL);
499 str_p = append_format(str_p, bounds_p, "%ld: ", now);
500 }
501 #endif /* LOG_TIME_NUMBER */
502 #if HAVE_CTIME
503 #if LOG_CTIME_STRING
504 {
505 TIME_TYPE now;
506 now = time(NULL);
507 str_p = append_format(str_p, bounds_p, "%.24s: ", ctime(&now));
508 }
509 #endif /* LOG_CTIME_STRING */
510 #endif /* HAVE_CTIME */
511 #endif /* HAVE_TIME */
512
513 #if LOG_ITERATION
514 /* add the iteration number */
515 str_p = append_format(str_p, bounds_p, "%lu: ", _dmalloc_iter_c);
516 #endif
517 #if LOG_PID && HAVE_GETPID
518 {
519 /* we make it long in case it's big and we hope it will promote if not */
520 long our_pid = getpid();
521
522 /* add the pid to the log file */
523 str_p = append_format(str_p, bounds_p, "p%ld: ", our_pid);
524 }
525 #endif
526
527 /*
528 * NOTE: the following code, as well as the function definition
529 * above, would need to be altered to conform to non-ANSI-C
530 * specifications if necessary.
531 */
532
533 /* write the format + info into str */
534 char *start_p = str_p;
535 str_p = append_vformat(str_p, bounds_p, format, args);
536
537 /* was it an empty format? */
538 if (str_p == start_p) {
539 return;
540 }
541
542 /* tack on a '\n' if necessary */
543 if (*(str_p - 1) != '\n') {
544 *str_p++ = '\n';
545 *str_p = '\0';
546 }
547 len = str_p - message_str;
548
549 /* do we need to write the message to the logfile */
550 if (dmalloc_logpath != NULL) {
551 (void)write(outfile_fd, message_str, len);
552 }
553
554 /* do we need to print the message? */
555 if (BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_PRINT_MESSAGES)) {
556 (void)write(STDERR, message_str, len);
557 }
558 }
559
560 /*
561 * void _dmalloc_die
562 *
563 * Kill the program because of an internal malloc error.
564 *
565 * ARGUMENTS:
566 *
567 * silent_b -> Set to 1 to not drop log entries.
568 */
_dmalloc_die(const int silent_b)569 void _dmalloc_die(const int silent_b)
570 {
571 char *stop_str;
572 int len;
573
574 if (! silent_b) {
575 if (BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_ERROR_ABORT)) {
576 stop_str = "dumping";
577 }
578 else {
579 stop_str = "halting";
580 }
581
582 /* print a message that we are going down */
583 len = loc_snprintf(error_str, sizeof(error_str),
584 "debug-malloc library: %s program, fatal error\r\n",
585 stop_str);
586 (void)write(STDERR, error_str, len);
587 if (dmalloc_errno != DMALLOC_ERROR_NONE) {
588 len = loc_snprintf(error_str, sizeof(error_str),
589 " Error: %s (err %d)\r\n",
590 dmalloc_strerror(dmalloc_errno), dmalloc_errno);
591 (void)write(STDERR, error_str, len);
592 }
593 }
594
595 /* set this in case the following generates a recursive call for some reason */
596 _dmalloc_aborting_b = 1;
597
598 /* do I need to drop core? */
599 if (BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_ERROR_ABORT)
600 || BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_ERROR_DUMP)) {
601 #if USE_ABORT
602 abort();
603 #else
604 #ifdef KILL_PROCESS
605 KILL_PROCESS;
606 #endif
607 #endif
608 }
609
610 /*
611 * NOTE: this should not be exit() because fclose will free, etc
612 */
613 _exit(1);
614 }
615
616 /*
617 * void dmalloc_error
618 *
619 * Handler of error codes. The caller should have set the errno already
620 *
621 * ARGUMENTS:
622 *
623 * func -> Function name for the logs.
624 */
dmalloc_error(const char * func)625 void dmalloc_error(const char *func)
626 {
627 /* do we need to log or print the error? */
628 if (dmalloc_logpath != NULL
629 || BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_PRINT_MESSAGES)) {
630
631 /* default str value */
632 if (func == NULL) {
633 func = "_malloc_error";
634 }
635
636 /* print the malloc error message */
637 dmalloc_message("ERROR: %s: %s (err %d)",
638 func, dmalloc_strerror(dmalloc_errno), dmalloc_errno);
639 }
640
641 /* do I need to abort? */
642 if (BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_ERROR_ABORT)) {
643 _dmalloc_die(0);
644 }
645
646 #if HAVE_FORK
647 /* how about just drop core? */
648 if (BIT_IS_SET(_dmalloc_flags, DMALLOC_DEBUG_ERROR_DUMP)) {
649 if (fork() == 0) {
650 _dmalloc_die(1);
651 }
652 }
653 #endif
654 }
655