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