Coverage Report

Created: 2024-02-16 06:12

/src/open62541/deps/mp_printf.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @author (c) Julius Pfrommer
3
 *             2023, Fraunhofer IOSB, Germany
4
 * @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
5
 *             2021-2023, Haifa, Palestine/Israel
6
 * @author (c) Marco Paland (info@paland.com)
7
 *             2014-2019, PALANDesign Hannover, Germany
8
 *
9
 * @note Others have made smaller contributions to this file: see the
10
 * contributors page at https://github.com/eyalroz/printf/graphs/contributors
11
 * or ask one of the authors. The original code for exponential specifiers was
12
 * contributed by Martijn Jasperse <m.jasperse@gmail.com>.
13
 *
14
 * @brief Small stand-alone implementation of the printf family of functions
15
 * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with
16
 * limited resources.
17
 *
18
 * @note the implementations are thread-safe; re-entrant; use no functions from
19
 * the standard library; and do not dynamically allocate any memory.
20
 *
21
 * @license The MIT License (MIT)
22
 *
23
 * Permission is hereby granted, free of charge, to any person obtaining a copy
24
 * of this software and associated documentation files (the "Software"), to deal
25
 * in the Software without restriction, including without limitation the rights
26
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
 * copies of the Software, and to permit persons to whom the Software is
28
 * furnished to do so, subject to the following conditions:
29
 *
30
 * The above copyright notice and this permission notice shall be included in
31
 * all copies or substantial portions of the Software.
32
 *
33
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39
 * THE SOFTWARE.
40
 */
41
42
#include "mp_printf.h"
43
#include "dtoa.h"
44
45
#include <stdint.h>
46
#include <stdbool.h>
47
48
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
49
// numeric number including padded zeros (dynamically created on stack)
50
0
#define PRINTF_INTEGER_BUFFER_SIZE    32
51
52
// size of the fixed (on-stack) buffer for printing individual decimal numbers.
53
// this must be big enough to hold one converted floating-point value including
54
// padded zeros.
55
#define PRINTF_DECIMAL_BUFFER_SIZE    32
56
57
// Default precision for the floating point conversion specifiers (the C
58
// standard sets this at 6)
59
#define PRINTF_DEFAULT_FLOAT_PRECISION  6
60
61
// internal flag definitions
62
0
#define FLAGS_ZEROPAD   (1U <<  0U)
63
0
#define FLAGS_LEFT      (1U <<  1U)
64
0
#define FLAGS_PLUS      (1U <<  2U)
65
0
#define FLAGS_SPACE     (1U <<  3U)
66
0
#define FLAGS_HASH      (1U <<  4U)
67
0
#define FLAGS_UPPERCASE (1U <<  5U)
68
0
#define FLAGS_CHAR      (1U <<  6U)
69
0
#define FLAGS_SHORT     (1U <<  7U)
70
  // Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
71
0
#define FLAGS_LONG      (1U <<  9U)
72
0
#define FLAGS_LONG_LONG (1U << 10U)
73
0
#define FLAGS_PRECISION (1U << 11U)
74
#define FLAGS_ADAPT_EXP (1U << 12U)
75
0
#define FLAGS_POINTER   (1U << 13U)
76
  // Note: Similar, but not identical, effect as FLAGS_HASH
77
0
#define FLAGS_SIGNED    (1U << 14U)
78
79
0
#define BASE_BINARY    2
80
0
#define BASE_OCTAL     8
81
0
#define BASE_DECIMAL  10
82
0
#define BASE_HEX      16
83
84
typedef unsigned int printf_flags_t;
85
typedef uint8_t numeric_base_t;
86
typedef unsigned long long printf_unsigned_value_t;
87
typedef long long printf_signed_value_t;
88
89
// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid
90
// and well-defined, but if you're not careful you can easily trigger undefined
91
// behavior with -LONG_MIN or -LLONG_MIN
92
#define ABS_FOR_PRINTING(_x)                                            \
93
0
    ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x)))
94
95
// internal secure strlen @return The length of the string (excluding the
96
// terminating 0) limited by 'maxsize' @note strlen uses size_t, but wes only
97
// use this function with size_t variables - hence the signature.
98
static size_t
99
0
strnlen_s_(const char *str, size_t maxsize) {
100
0
    for(size_t i = 0; i < maxsize; i++) {
101
0
        if(!str[i])
102
0
            return i;
103
0
    }
104
0
    return maxsize;
105
0
}
106
107
// internal test if char is a digit (0-9)
108
// @return true if char is a digit
109
0
static bool is_digit_(char ch) { return (ch >= '0') && (ch <= '9'); }
110
111
// internal ASCII string to size_t conversion
112
static size_t
113
0
atou_(const char **str) {
114
0
    size_t i = 0U;
115
0
    while(is_digit_(**str)) {
116
0
        i = i * 10U + (size_t)(*((*str)++) - '0');
117
0
    }
118
0
    return i;
119
0
}
120
121
// Output buffer
122
typedef struct {
123
    char *buffer;
124
    size_t pos;
125
    size_t max_chars;
126
} output_t;
127
128
static void
129
0
putchar_(output_t *out, char c) {
130
0
    size_t write_pos = out->pos++;
131
    // We're _always_ increasing pos, so as to count how may characters
132
    // _would_ have been written if not for the max_chars limitation
133
0
    if(write_pos >= out->max_chars)
134
0
        return;
135
    // it must be the case that out->buffer != NULL , due to the constraint
136
    // on output_t ; and note we're relying on write_pos being non-negative.
137
0
    out->buffer[write_pos] = c;
138
0
}
139
140
static void
141
0
out_(output_t *out, const char *buf, size_t len) {
142
0
    if(out->pos < out->max_chars) {
143
0
        size_t write_len = len;
144
0
        if(out->pos + len > out->max_chars)
145
0
            write_len = out->max_chars - out->pos;
146
0
        for(size_t i = 0; i < write_len; i++)
147
0
            out->buffer[out->pos + i] = buf[i];
148
0
    }
149
0
    out->pos += len; // Always increase pos by len
150
0
}
151
152
// output the specified string in reverse, taking care of any zero-padding
153
static void
154
out_rev_(output_t *output, const char *buf, size_t len, size_t width,
155
0
         printf_flags_t flags) {
156
0
    const size_t start_pos = output->pos;
157
158
    // pad spaces up to given width
159
0
    if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
160
0
        for(size_t i = len; i < width; i++) {
161
0
            putchar_(output, ' ');
162
0
        }
163
0
    }
164
165
    // reverse string
166
0
    while(len) {
167
0
        putchar_(output, buf[--len]);
168
0
    }
169
170
    // append pad spaces up to given width
171
0
    if(flags & FLAGS_LEFT) {
172
0
        while(output->pos - start_pos < width) {
173
0
            putchar_(output, ' ');
174
0
        }
175
0
    }
176
0
}
177
178
// Invoked by print_integer after the actual number has been printed, performing
179
// necessary work on the number's prefix (as the number is initially printed in
180
// reverse order)
181
static void
182
print_integer_finalization(output_t *output, char *buf, size_t len, bool negative,
183
                           numeric_base_t base, size_t precision, size_t width,
184
0
                           printf_flags_t flags) {
185
0
    size_t unpadded_len = len;
186
187
    // pad with leading zeros
188
0
    if(!(flags & FLAGS_LEFT)) {
189
0
        if(width && (flags & FLAGS_ZEROPAD) &&
190
0
           (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
191
0
            width--;
192
0
        }
193
0
        while((flags & FLAGS_ZEROPAD) && (len < width) &&
194
0
              (len < PRINTF_INTEGER_BUFFER_SIZE)) {
195
0
            buf[len++] = '0';
196
0
        }
197
0
    }
198
199
0
    while((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
200
0
        buf[len++] = '0';
201
0
    }
202
203
0
    if(base == BASE_OCTAL && (len > unpadded_len)) {
204
        // Since we've written some zeros, we've satisfied the alternative format
205
        // leading space requirement
206
0
        flags &= ~FLAGS_HASH;
207
0
    }
208
209
    // handle hash
210
0
    if(flags & (FLAGS_HASH | FLAGS_POINTER)) {
211
0
        if(!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) {
212
            // Let's take back some padding digits to fit in what will eventually be
213
            // the format-specific prefix
214
0
            if(unpadded_len < len) {
215
0
                len--;  // This should suffice for BASE_OCTAL
216
0
            }
217
0
            if(len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) {
218
0
                len--;  // ... and an extra one for 0x or 0b
219
0
            }
220
0
        }
221
0
        if((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) &&
222
0
           (len < PRINTF_INTEGER_BUFFER_SIZE)) {
223
0
            buf[len++] = 'x';
224
0
        } else if((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) &&
225
0
                  (len < PRINTF_INTEGER_BUFFER_SIZE)) {
226
0
            buf[len++] = 'X';
227
0
        } else if((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
228
0
            buf[len++] = 'b';
229
0
        }
230
0
        if(len < PRINTF_INTEGER_BUFFER_SIZE) {
231
0
            buf[len++] = '0';
232
0
        }
233
0
    }
234
235
0
    if(len < PRINTF_INTEGER_BUFFER_SIZE) {
236
0
        if(negative) {
237
0
            buf[len++] = '-';
238
0
        } else if(flags & FLAGS_PLUS) {
239
0
            buf[len++] = '+';  // ignore the space if the '+' exists
240
0
        } else if(flags & FLAGS_SPACE) {
241
0
            buf[len++] = ' ';
242
0
        }
243
0
    }
244
245
0
    out_rev_(output, buf, len, width, flags);
246
0
}
247
248
// An internal itoa-like function
249
static void
250
print_integer(output_t *output, printf_unsigned_value_t value, bool negative,
251
0
              numeric_base_t base, size_t precision, size_t width, printf_flags_t flags) {
252
0
    char buf[PRINTF_INTEGER_BUFFER_SIZE];
253
0
    size_t len = 0U;
254
255
0
    if(!value) {
256
0
        if(!(flags & FLAGS_PRECISION)) {
257
0
            buf[len++] = '0';
258
0
            flags &= ~FLAGS_HASH;
259
            // We drop this flag this since either the alternative and regular modes
260
            // of the specifier don't differ on 0 values, or (in the case of octal)
261
            // we've already provided the special handling for this mode.
262
0
        } else if(base == BASE_HEX) {
263
0
            flags &= ~FLAGS_HASH;
264
            // We drop this flag this since either the alternative and regular modes
265
            // of the specifier don't differ on 0 values
266
0
        }
267
0
    } else {
268
0
        do {
269
0
            const char digit = (char)(value % base);
270
0
            buf[len++] =
271
0
                (char)(digit < 10 ? '0' + digit
272
0
                                  : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10);
273
0
            value /= base;
274
0
        } while(value && (len < PRINTF_INTEGER_BUFFER_SIZE));
275
0
    }
276
277
0
    print_integer_finalization(output, buf, len, negative, base, precision, width, flags);
278
0
}
279
280
static void
281
print_floating_point(output_t *output, double value, size_t precision,
282
0
                     size_t width, printf_flags_t flags) {
283
0
    if((flags & FLAGS_PLUS) && value > 0.0)
284
0
        putchar_(output, '+');
285
286
    // set default precision, if not set explicitly
287
    //if(!(flags & FLAGS_PRECISION) || precision > PRINTF_DECIMAL_BUFFER_SIZE - 5)
288
    //    precision = PRINTF_DEFAULT_FLOAT_PRECISION;
289
290
0
    char buf[PRINTF_DECIMAL_BUFFER_SIZE];
291
0
    unsigned len = dtoa(value, buf); // Fill the buffer (TODO: Consider precision)
292
0
    out_(output, buf, len); // Print the buffer
293
0
}
294
295
// Advances the format pointer past the flags, and returns the parsed flags
296
// due to the characters passed
297
static printf_flags_t
298
0
parse_flags(const char **format) {
299
0
    printf_flags_t flags = 0U;
300
0
    do {
301
0
        switch(**format) {
302
0
        case '0': flags |= FLAGS_ZEROPAD; break;
303
0
        case '-': flags |= FLAGS_LEFT; break;
304
0
        case '+': flags |= FLAGS_PLUS; break;
305
0
        case ' ': flags |= FLAGS_SPACE; break;
306
0
        case '#': flags |= FLAGS_HASH; break;
307
0
        default: return flags;
308
0
        }
309
0
        (*format)++;
310
0
    } while(true);
311
0
}
312
313
#define ADVANCE_IN_FORMAT_STRING(cptr_)                                 \
314
0
    do {                                                                \
315
0
        (cptr_)++;                                                      \
316
0
        if(!*(cptr_))                                                   \
317
0
            return;                                                     \
318
0
    } while(0)
319
320
static void
321
0
format_string_loop(output_t *output, const char *format, va_list args) {
322
0
    while(*format) {
323
0
        if(*format != '%') {
324
            // A regular content character
325
0
            putchar_(output, *format);
326
0
            format++;
327
0
            continue;
328
0
        }
329
        // We're parsing a format specifier: %[flags][width][.precision][length]
330
0
        ADVANCE_IN_FORMAT_STRING(format);
331
332
0
        printf_flags_t flags = parse_flags(&format);
333
334
        // evaluate width field
335
0
        size_t width = 0U;
336
0
        if(is_digit_(*format)) {
337
0
            width = atou_(&format);
338
0
        } else if(*format == '*') {
339
0
            const int w = va_arg(args, int);
340
0
            if(w < 0) {
341
0
                flags |= FLAGS_LEFT;  // reverse padding
342
0
                width = (size_t)-w;
343
0
            } else {
344
0
                width = (size_t)w;
345
0
            }
346
0
            ADVANCE_IN_FORMAT_STRING(format);
347
0
        }
348
349
        // evaluate precision field
350
0
        size_t precision = 0U;
351
0
        if(*format == '.') {
352
0
            flags |= FLAGS_PRECISION;
353
0
            ADVANCE_IN_FORMAT_STRING(format);
354
0
            if(is_digit_(*format)) {
355
0
                precision = atou_(&format);
356
0
            } else if(*format == '*') {
357
0
                const int precision_ = va_arg(args, int);
358
0
                precision = precision_ > 0 ? (size_t)precision_ : 0U;
359
0
                ADVANCE_IN_FORMAT_STRING(format);
360
0
            }
361
0
        }
362
363
        // evaluate length field
364
0
        switch(*format) {
365
0
            case 'l':
366
0
                flags |= FLAGS_LONG;
367
0
                ADVANCE_IN_FORMAT_STRING(format);
368
0
                if(*format == 'l') {
369
0
                    flags |= FLAGS_LONG_LONG;
370
0
                    ADVANCE_IN_FORMAT_STRING(format);
371
0
                }
372
0
                break;
373
0
            case 'h':
374
0
                flags |= FLAGS_SHORT;
375
0
                ADVANCE_IN_FORMAT_STRING(format);
376
0
                if(*format == 'h') {
377
0
                    flags |= FLAGS_CHAR;
378
0
                    ADVANCE_IN_FORMAT_STRING(format);
379
0
                }
380
0
                break;
381
0
            case 't':
382
0
                flags |=
383
0
                    (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
384
0
                ADVANCE_IN_FORMAT_STRING(format);
385
0
                break;
386
0
            case 'j':
387
0
                flags |=
388
0
                    (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
389
0
                ADVANCE_IN_FORMAT_STRING(format);
390
0
                break;
391
0
            case 'z':
392
0
                flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
393
0
                ADVANCE_IN_FORMAT_STRING(format);
394
0
                break;
395
0
            default:
396
0
                break;
397
0
        }
398
399
        // evaluate specifier
400
0
        switch(*format) {
401
0
            case 'd':
402
0
            case 'i':
403
0
            case 'u':
404
0
            case 'x':
405
0
            case 'X':
406
0
            case 'o':
407
0
            case 'b': {
408
0
                if(*format == 'd' || *format == 'i') {
409
0
                    flags |= FLAGS_SIGNED;
410
0
                }
411
412
0
                numeric_base_t base;
413
0
                if(*format == 'x' || *format == 'X') {
414
0
                    base = BASE_HEX;
415
0
                } else if(*format == 'o') {
416
0
                    base = BASE_OCTAL;
417
0
                } else if(*format == 'b') {
418
0
                    base = BASE_BINARY;
419
0
                } else {
420
0
                    base = BASE_DECIMAL;
421
0
                    flags &=
422
0
                        ~FLAGS_HASH;  // decimal integers have no alternative presentation
423
0
                }
424
425
0
                if(*format == 'X') {
426
0
                    flags |= FLAGS_UPPERCASE;
427
0
                }
428
429
0
                format++;
430
                // ignore '0' flag when precision is given
431
0
                if(flags & FLAGS_PRECISION) {
432
0
                    flags &= ~FLAGS_ZEROPAD;
433
0
                }
434
435
0
                if(flags & FLAGS_SIGNED) {
436
                    // A signed specifier: d, i or possibly I + bit size if enabled
437
0
                    if(flags & FLAGS_LONG_LONG) {
438
0
                        const long long value = va_arg(args, long long);
439
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
440
0
                                      precision, width, flags);
441
0
                    } else if(flags & FLAGS_LONG) {
442
0
                        const long value = va_arg(args, long);
443
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
444
0
                                      precision, width, flags);
445
0
                    } else {
446
                        // We never try to interpret the argument as something
447
                        // potentially-smaller than int, due to integer promotion rules:
448
                        // Even if the user passed a short int, short unsigned etc. -
449
                        // these will come in after promotion, as int's (or unsigned for
450
                        // the case of short unsigned when it has the same size as int)
451
0
                        const int value =
452
0
                            (flags & FLAGS_CHAR)    ? (signed char)va_arg(args, int)
453
0
                            : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int)
454
0
                                                    : va_arg(args, int);
455
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
456
0
                                      precision, width, flags);
457
0
                    }
458
0
                } else {
459
                    // An unsigned specifier: u, x, X, o, b
460
0
                    flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
461
462
0
                    if(flags & FLAGS_LONG_LONG) {
463
0
                        print_integer(output, (printf_unsigned_value_t)
464
0
                                      va_arg(args, unsigned long long),
465
0
                                      false, base, precision, width, flags);
466
0
                    } else if(flags & FLAGS_LONG) {
467
0
                        print_integer(output, (printf_unsigned_value_t)
468
0
                                      va_arg(args, unsigned long),
469
0
                                      false, base, precision, width, flags);
470
0
                    } else {
471
0
                        const unsigned int value = (flags & FLAGS_CHAR)
472
0
                            ? (unsigned char)va_arg(args, unsigned int)
473
0
                            : (flags & FLAGS_SHORT)
474
0
                            ? (unsigned short int)va_arg(args, unsigned int)
475
0
                            : va_arg(args, unsigned int);
476
0
                        print_integer(output, (printf_unsigned_value_t)value, false, base,
477
0
                                      precision, width, flags);
478
0
                    }
479
0
                }
480
0
                break;
481
0
            }
482
483
0
            case 'f':
484
0
            case 'F':
485
0
                if(*format == 'F')
486
0
                    flags |= FLAGS_UPPERCASE;
487
0
                print_floating_point(output, (double)va_arg(args, double),
488
0
                                     precision, width, flags);
489
0
                format++;
490
0
                break;
491
492
0
            case 'c': {
493
0
                size_t l = 1U;
494
                // pre padding
495
0
                if(!(flags & FLAGS_LEFT)) {
496
0
                    while(l++ < width) {
497
0
                        putchar_(output, ' ');
498
0
                    }
499
0
                }
500
                // char output
501
0
                putchar_(output, (char)va_arg(args, int));
502
                // post padding
503
0
                if(flags & FLAGS_LEFT) {
504
0
                    while(l++ < width) {
505
0
                        putchar_(output, ' ');
506
0
                    }
507
0
                }
508
0
                format++;
509
0
                break;
510
0
            }
511
512
0
            case 's': {
513
0
                const char *p = va_arg(args, char *);
514
0
                if(p == NULL) {
515
0
                    out_rev_(output, ")llun(", 6, width, flags);
516
0
                } else {
517
                    // string length
518
0
                    size_t l = strnlen_s_(p, precision ? precision : INT32_MAX);
519
0
                    if(flags & FLAGS_PRECISION) {
520
0
                        l = (l < precision ? l : precision);
521
0
                    }
522
523
                    // pre padding
524
0
                    if(!(flags & FLAGS_LEFT)) {
525
0
                        for(size_t i = 0; l + i < width; i++) {
526
0
                            putchar_(output, ' ');
527
0
                        }
528
0
                    }
529
530
                    // string output
531
0
                    out_(output, p, l);
532
533
                    // post padding
534
0
                    if(flags & FLAGS_LEFT) {
535
0
                        for(size_t i = 0; l + i < width; i++) {
536
0
                            putchar_(output, ' ');
537
0
                        }
538
0
                    }
539
0
                }
540
0
                format++;
541
0
                break;
542
0
            }
543
544
0
            case 'p': {
545
0
                width =
546
0
                    sizeof(void *) * 2U + 2;  // 2 hex chars per byte + the "0x" prefix
547
0
                flags |= FLAGS_ZEROPAD | FLAGS_POINTER;
548
0
                uintptr_t value = (uintptr_t)va_arg(args, void *);
549
0
                (value == (uintptr_t)NULL)
550
0
                    ? out_rev_(output, ")lin(", 5, width, flags)
551
0
                    : print_integer(output, (printf_unsigned_value_t)value, false,
552
0
                                    BASE_HEX, precision, width, flags);
553
0
                format++;
554
0
                break;
555
0
            }
556
557
0
            case '%':
558
0
                putchar_(output, '%');
559
0
                format++;
560
0
                break;
561
562
0
            default:
563
0
                putchar_(output, *format);
564
0
                format++;
565
0
                break;
566
0
        }
567
0
    }
568
0
}
569
570
int
571
0
mp_vsnprintf(char *s, size_t n, const char *format, va_list arg) {
572
    // Check that the inputs are sane
573
0
    if(!s || n < 1)
574
0
        return -1;
575
576
    // Format the string
577
0
    output_t out = {s, 0, n};
578
0
    format_string_loop(&out, format, arg);
579
580
    // Write the string-terminating '\0' character
581
0
    size_t null_char_pos = out.pos < out.max_chars ? out.pos : out.max_chars - 1;
582
0
    out.buffer[null_char_pos] = '\0';
583
584
    // Return written chars without terminating \0
585
0
    return (int)out.pos;
586
0
}
587
588
int
589
0
mp_snprintf(char *s, size_t n, const char *format, ...) {
590
0
    va_list args;
591
0
    va_start(args, format);
592
0
    const int ret = mp_vsnprintf(s, n, format, args);
593
0
    va_end(args);
594
0
    return ret;
595
0
}