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