1 /*
2  * String appending functions that take into account buffer limit.
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 holds the compatibility routines necessary for the library to
24  * function just in case your system does not have them.
25  */
26 
27 #if HAVE_STDARG_H
28 # include <stdarg.h>				/* for ... */
29 #endif
30 #if HAVE_STDIO_H
31 # include <stdio.h>				/* for FILE */
32 #endif
33 #if HAVE_STRING_H
34 # include <string.h>                            /* for strlen */
35 #endif
36 
37 #define DMALLOC_DISABLE
38 
39 #include "conf.h"
40 #include "dmalloc.h"
41 
42 #include "append.h"
43 #include "compat.h"
44 #include "dmalloc_loc.h"
45 
46 /*
47  * If we did not specify a precision then we need to stop somewhere
48  * otherwise we get something like 99.0000100000000031741365091872284.
49  * This is somewhat arbitrary.  We could try to autoconf a value based
50  * on float.h and FLT_MAX or something but for internal purposes, this
51  * seems fine.
52  */
53 #define DEFAULT_DECIMAL_PRECISION	10
54 
55 /*
56  * Internal method to process a long or int number and write into a buffer.
57  */
handle_decimal(char * buf,char * limit,const long num,const int base)58 static	char	*handle_decimal(char *buf, char *limit, const long num, const int base)
59 {
60   char *buf_p = buf;
61   buf_p = append_long(buf_p, limit, num, base);
62   append_null(buf_p, limit);
63   return buf_p;
64 }
65 
66 /*
67  * Internal method to process a long or int number and write into a buffer.
68  */
handle_pointer(char * buf,char * limit,const PNT_ARITH_TYPE num,const int base)69 static	char	*handle_pointer(char *buf, char *limit, const PNT_ARITH_TYPE num, const int base)
70 {
71   char *buf_p = buf;
72   buf_p = append_pointer(buf_p, limit, num, base);
73   append_null(buf_p, limit);
74   return buf_p;
75 }
76 
77 /*
78  * Internal method to handle floating point numbers.
79  */
handle_float(char * buf,char * limit,double num,int decimal_precision,const int no_precision)80 static	char	*handle_float(char *buf, char *limit, double num, int decimal_precision,
81 			      const int no_precision)
82 {
83   long long_num;
84   char *buf_p = buf;
85 
86   /*
87    * Doing a float and a double if long_arg hrows a super bad
88    * exception and causes the program to abort.  Not sure if this is
89    * something that we need to autoconf around.
90    */
91   long_num = (long)num;
92   buf_p = append_long(buf_p, limit, long_num, 10);
93   /* remove the int part */
94   num -= (double)long_num;
95   if (num == 0.0 || decimal_precision == 0) {
96     append_null(buf_p, limit);
97     return buf_p;
98   }
99   /* TODO: need to handle different number formats */
100   if (buf_p < limit) {
101     *buf_p++ = '.';
102   }
103 
104   char *non_zero_limit_p = buf_p;
105   while (num > 0.0 && --decimal_precision >= 0) {
106     num *= 10.0;
107     long_num = (long)num;
108     if (long_num >= 10) {
109       long_num = 9;
110     }
111     if (buf_p < limit) {
112       *buf_p++ = '0' + long_num;
113       /* track the last non-0 digit in case we need to truncate ending 000 chars */
114       if (long_num != 0) {
115 	non_zero_limit_p = buf_p;
116       }
117     }
118     num -= (double)long_num;
119   }
120 
121   /* if we did not specify precision then don't end with a bunch of 0s */
122   if (no_precision) {
123     buf_p = non_zero_limit_p;
124   }
125 
126   append_null(buf_p, limit);
127   return buf_p;
128 }
129 
130 /*
131  * Append string argument to destination up to limit pointer.  Pointer
132  * to the end of the characters added will be returned.  No \0
133  * character will be added.
134  */
append_string(char * dest,const char * limit,const char * value)135 char	*append_string(char *dest, const char *limit, const char *value)
136 {
137   while (dest < limit && *value != '\0') {
138     *dest++ = *value++;
139   }
140   return dest;
141 }
142 
143 /*
144  * Append long value argument to destination up to limit pointer.
145  * Pointer to the end of the added characters will be returned.  No \0
146  * character will be added.  Variant of itoa() written by Lukas
147  * Chmela which is released under GPLv3.
148  */
append_long(char * dest,char * limit,long value,int base)149 char	*append_long(char *dest, char *limit, long value, int base)
150 {
151   char buf[30];
152   char *ptr = buf;
153   char *ptr1 = buf;
154   char tmp_char;
155   long tmp_value;
156 
157   /* letters that handle both negative and positive values */
158   char *letters =
159     "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz";
160   int mid = 35;
161   /* build the string with low order digits first: 100 => "001" */
162   do {
163     tmp_value = value;
164     value /= base;
165     *ptr++ = letters[mid + tmp_value - (value * base)];
166   } while (value != 0);
167 
168   /* apply negative sign */
169   if (tmp_value < 0) {
170     *ptr++ = '-';
171   }
172   *ptr-- = '\0';
173   /* now we swap the characters to move high order digits first */
174   while (ptr1 < ptr) {
175     tmp_char = *ptr;
176     *ptr--= *ptr1;
177     *ptr1++ = tmp_char;
178   }
179   return append_string(dest, limit, buf);
180 }
181 
182 /*
183  * Append unsigned long value argument to destination up to limit
184  * pointer.  Pointer to the end of the added characters will be
185  * returned.  No \0 character will be added.  Variant of itoa()
186  * written by Lukas Chmela. Released under GPLv3.
187  */
append_ulong(char * dest,char * limit,unsigned long value,int base)188 char	*append_ulong(char *dest, char *limit, unsigned long value, int base)
189 {
190   char buf[30];
191   char *ptr = buf;
192   char *ptr1 = buf;
193   char tmp_char;
194   unsigned long tmp_value;
195 
196   /* letters that handle both negative and positive values */
197   char *letters =
198     "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz";
199   int mid = 35;
200   /* build the string with low order digits first: 100 => "001" */
201   do {
202     tmp_value = value;
203     value /= base;
204     *ptr++ = letters[mid + tmp_value - (value * base)];
205   } while (value != 0);
206 
207   *ptr-- = '\0';
208   /* now we swap the characters to move high order digits first */
209   while (ptr1 < ptr) {
210     tmp_char = *ptr;
211     *ptr--= *ptr1;
212     *ptr1++ = tmp_char;
213   }
214   return append_string(dest, limit, buf);
215 }
216 
217 /*
218  * Append pointer value argument to destination up to limit pointer.
219  * Pointer to the end of the added characters will be returned.  No \0
220  * character will be added.  Variant of itoa() written by Lukas
221  * Chmela which is released under GPLv3.
222  */
append_pointer(char * dest,char * limit,PNT_ARITH_TYPE value,int base)223 char	*append_pointer(char *dest, char *limit, PNT_ARITH_TYPE value, int base)
224 {
225   char buf[30];
226   char *ptr = buf;
227   char *ptr1 = buf;
228   char tmp_char;
229   PNT_ARITH_TYPE tmp_value;
230 
231   /* letters that handle both negative and positive values */
232   char *letters =
233     "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz";
234   int mid = 35;
235   /* build the string with low order digits first: 100 => "001" */
236   do {
237     tmp_value = value;
238     value /= base;
239     *ptr++ = letters[mid + tmp_value - (value * base)];
240   } while (value != 0);
241 
242   /* apply negative sign */
243   if (tmp_value < 0) {
244     *ptr++ = '-';
245   }
246   *ptr-- = '\0';
247   /* now we swap the characters to move high order digits first */
248   while (ptr1 < ptr) {
249     tmp_char = *ptr;
250     *ptr--= *ptr1;
251     *ptr1++ = tmp_char;
252   }
253   return append_string(dest, limit, buf);
254 }
255 
256 /*
257  * Append a varargs format to destination.  Pointer to the end of the
258  * characters added will be returned.  No \0 character will be added.
259  */
append_vformat(char * dest,char * limit,const char * format,va_list args)260 char	*append_vformat(char *dest, char *limit, const char *format,
261 			va_list args)
262 {
263   const char *format_p = format;
264   char value_buf[64];
265   char *value_limit = value_buf + sizeof(value_buf);
266   char *dest_p = dest;
267   int format_prefix, neg_pad, trunc, long_arg;
268   char pad_char;
269   char *prefix, *prefix_p;
270   int width_len, trunc_len, prefix_len;
271 
272   while (*format_p != '\0' && dest_p < limit) {
273     if (*format_p != '%') {
274       *dest_p++ = *format_p++;
275       continue;
276     }
277     format_p++;
278 
279     format_prefix = 0;
280     neg_pad = 0;
281     trunc = 0;
282     long_arg = 0;
283     pad_char = ' ';
284     prefix = "";
285     width_len = -1;
286     trunc_len = -1;
287     prefix_len = 0;
288     while (1) {
289       char ch = *format_p++;
290       if (ch == '\0') {
291 	break;
292       }
293       if (ch == '%') {
294 	if (dest_p < limit) {
295 	  *dest_p++ = '%';
296 	}
297 	break;
298       }
299       if (ch == '#') {
300 	format_prefix = 1;
301       } else if (ch == '0' && width_len < 0 && trunc_len < 0) {
302 	pad_char = '0';
303       } else if (ch == '-') {
304 	neg_pad = 1;
305       } else if (ch == '.') {
306 	trunc = 1;
307       } else if (ch == '*') {
308 	if (trunc) {
309 	  trunc_len = va_arg(args, int);
310 	} else {
311 	  width_len = va_arg(args, int);
312 	}
313       } else if (ch >= '0' && ch <= '9') {
314 	if (trunc) {
315 	  if (trunc_len < 0) {
316 	    trunc_len = (ch - '0');
317 	  } else {
318 	    trunc_len = trunc_len * 10 + (ch - '0');
319 	  }
320 	} else {
321 	  if (width_len < 0) {
322 	    width_len = (ch - '0');
323 	  } else {
324 	    width_len = width_len * 10 + (ch - '0');
325 	  }
326 	}
327       } else if (ch == 'l') {
328 	long_arg = 1;
329       } else if (ch != 'c' && ch != 'd' && ch != 'f' && ch != 'o' && ch != 'p'
330 		 && ch != 's' && ch != 'u' && ch != 'x') {
331 	continue;
332       }
333 
334       /* process the % format character */
335 
336       const char *value;
337       if (ch == 'c') {
338 	value_buf[0] = (char)va_arg(args, int);
339 	value_buf[1] = '\0';
340 	value = value_buf;
341       } else if (ch == 'd') {
342 	long num;
343 	if (long_arg) {
344 	  num = va_arg(args, long);
345 	} else {
346 	  num = va_arg(args, int);
347 	}
348 	handle_decimal(value_buf, value_limit, num, 10);
349 	value = value_buf;
350       } else if (ch == 'f') {
351 	double num = va_arg(args, double);
352 	if (trunc_len < 0) {
353 	  handle_float(value_buf, value_limit, num, DEFAULT_DECIMAL_PRECISION, 1);
354 	} else {
355 	  handle_float(value_buf, value_limit, num, trunc_len, 0);
356 	}
357 	value = value_buf;
358 	/* special case here, the trunc length is really decimal precision */
359 	trunc_len = -1;
360       } else if (ch == 'o') {
361 	long num;
362 	if (long_arg) {
363 	  num = va_arg(args, long);
364 	} else {
365 	  num = va_arg(args, int);
366 	}
367 	handle_decimal(value_buf, value_limit, num, 8);
368 	value = value_buf;
369 	if (format_prefix) {
370 	  prefix = "0";
371 	  prefix_len = 1;
372 	}
373       } else if (ch == 'p') {
374 	DMALLOC_PNT pnt = va_arg(args, DMALLOC_PNT);
375 	PNT_ARITH_TYPE num = (PNT_ARITH_TYPE)pnt;
376 	handle_pointer(value_buf, value_limit, num, 16);
377 	value = value_buf;
378 	// because %#p throws a gcc warning, I've decreed that %p has a 0x hex prefix
379 	prefix = "0x";
380 	prefix_len = 2;
381       } else if (ch == 's') {
382 	value = va_arg(args, char *);
383       } else if (ch == 'u') {
384 	unsigned long num;
385 	if (long_arg) {
386 	  num = va_arg(args, unsigned long);
387 	} else {
388 	  num = va_arg(args, unsigned int);
389 	}
390 	char *value_buf_p = value_buf;
391 	value_buf_p = append_ulong(value_buf_p, value_limit, num, 10);
392 	append_null(value_buf_p, value_limit);
393 	value = value_buf;
394       } else if (ch == 'x') {
395 	long num;
396 	if (long_arg) {
397 	  num = va_arg(args, long);
398 	} else {
399 	  num = va_arg(args, int);
400 	}
401 	handle_decimal(value_buf, value_limit, num, 16);
402 	if (format_prefix) {
403 	  prefix = "0x";
404 	  prefix_len = 2;
405 	}
406 	value = value_buf;
407       } else {
408 	continue;
409       }
410 
411       /* prepend optional numeric prefix which has to come before padding */
412       if (prefix_len > 0) {
413 	prefix_p = prefix;
414 	while (*prefix_p != '\0' && dest_p < limit) {
415 	  *dest_p++ = *prefix_p++;
416 	}
417       }
418 
419       /* handle our pre-padding with spaces or 0s */
420       int pad = 0;
421       if (width_len >= 0) {
422 	if (trunc_len >= 0) {
423 	  pad = width_len - strnlen(value, trunc_len) - prefix_len;
424 	} else {
425 	  pad = width_len - strlen(value) - prefix_len;
426 	}
427 	if (!neg_pad) {
428 	  while (pad-- > 0 && dest_p < limit) {
429 	    *dest_p++ = pad_char;
430 	  }
431 	}
432       }
433 
434       /* change the limit if we are truncating the string */
435       char *str_limit = limit;
436       if (trunc_len >= 0) {
437 	str_limit = dest_p + trunc_len;
438 	if (str_limit > limit) {
439 	  str_limit = limit;
440 	}
441       }
442 
443       /* copy the value watching our limit */
444       while (*value != '\0' && dest_p < str_limit) {
445 	*dest_p++ = *value++;
446       }
447 
448       /* maybe handle left over padding which would be negative padding */
449       while (pad-- > 0 && dest_p < limit) {
450 	/* always pad at the end with a space */
451 	*dest_p++ = ' ';
452       }
453       break;
454     }
455   }
456   return dest_p;
457 }
458 
459 /*
460  * Append a varargs format to destination.  Pointer to the end of the
461  * added characters will be returned.  No \0 character will be added.
462  */
append_format(char * dest,char * limit,const char * format,...)463 char	*append_format(char *dest, char *limit, const char *format, ...)
464 {
465   va_list args;
466   char *dest_p;
467 
468   va_start(args, format);
469   dest_p = append_vformat(dest, limit, format, args);
470   va_end(args);
471 
472   return dest_p;
473 }
474 
475 /*
476  * Append \0 character to destination.  If dest is => limit then \0
477  * will be written one character before the limit.  Pointer past the
478  * end of the \0 character will be returned.
479  */
append_null(char * dest,char * limit)480 char    *append_null(char *dest, char *limit)
481 {
482   if (dest < limit) {
483     *dest++ = '\0';
484   } else {
485     *(limit - 1) = '\0';
486   }
487   return dest;
488 }
489 
490 /*
491  * Local implementation of snprintf.  We are doing this trying to not
492  * use the system version which often can allocate memory itself
493  * causing the library to go recursive.
494  */
loc_vsnprintf(char * buf,const int size,const char * format,va_list args)495 int	loc_vsnprintf(char *buf, const int size, const char *format,
496 		      va_list args)
497 {
498   char *limit, *buf_p;
499   limit = buf + size;
500   buf_p = append_vformat(buf, limit, format, args);
501   append_null(buf_p, limit);
502   return (int)(buf_p - buf);
503 }
504 
505 /*
506  * Local implementation of snprintf.  We are doing this trying to not
507  * use the system version which often can allocate memory itself
508  * causing the library to go recursive.
509  */
loc_snprintf(char * buf,const int size,const char * format,...)510 int	loc_snprintf(char *buf, const int size, const char *format, ...)
511 {
512   va_list args;
513   int len;
514 
515   va_start(args, format);
516   len = loc_vsnprintf(buf, size, format, args);
517   va_end(args);
518 
519   return len;
520 }
521 
522 /*
523  * Local implementation of printf so we can use %p and other non-standard formats.
524  */
loc_printf(const char * format,...)525 void	loc_printf(const char *format, ...)
526 {
527   va_list args;
528   va_start(args, format);
529   loc_vfprintf(stdout, format, args);
530   va_end(args);
531 }
532 
533 /*
534  * Local implementation of fprintf so we can use %p and other non-standard formats.
535  */
loc_fprintf(FILE * file,const char * format,...)536 void	loc_fprintf(FILE *file, const char *format, ...)
537 {
538   va_list args;
539   va_start(args, format);
540   loc_vfprintf(file, format, args);
541   va_end(args);
542 }
543 
544 /*
545  * Local implementation of vfprintf so we can use %p and other non-standard formats.
546  */
loc_vfprintf(FILE * file,const char * format,va_list args)547 void	loc_vfprintf(FILE *file, const char *format, va_list args)
548 {
549   // these are simple messages so this limit is ok
550   char buf[256];
551   char *buf_p, *limit;
552 
553   limit = buf + sizeof(buf);
554   buf_p = append_vformat(buf, limit, format, args);
555   fwrite(buf, 1, (buf_p - buf), file);
556 }
557