Luna/libs/libc/src/printf.cpp
apio 8d46c9bbe2 Kernel, libc: Fix a big bug in printf()
Every time printf flushes the buffer to us in sprintf() or snprintf(), we call strncat to append the data.

But we want to start from the beginning in the first flush. What if there was data already there?
Well, we just append to the old data. Which is not good, and breaks snprintf()'s maximum size policy.

This fix sets the first byte of str to NULL, to avoid this.
2022-10-30 09:53:23 +01:00

325 lines
8.6 KiB
C++

#include <errno.h>
#include <luna.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
typedef long int ssize_t;
static void printf_strrev(char* arr, int size)
{
int left = 0;
int right = size - 1;
for (int i = left; i < right; i++)
{
char temp = arr[i];
arr[i] = arr[right];
arr[right] = temp;
right--;
}
}
template <typename IntegerType> static char* printf_unsigned_to_string(IntegerType number, char* arr, int base)
{
int i = 0;
if (number == 0)
{
arr[i] = '0';
arr[i + 1] = '\0';
return arr;
}
while (number != 0)
{
IntegerType r = number % (IntegerType)base;
arr[i] = (char)((r > 9) ? (r - 10) + 'a' : r + '0');
i++;
number /= base;
}
printf_strrev(arr, i);
arr[i] = '\0';
return arr;
}
template <typename IntegerType> static char* printf_signed_to_string(IntegerType number, char* arr, int base)
{
int i = 0, negative = 0;
if (number == 0)
{
arr[i] = '0';
arr[i + 1] = '\0';
return arr;
}
if (number < 0 && base == 10)
{
number *= -1;
negative = 1;
}
while (number != 0)
{
IntegerType r = number % base;
arr[i] = (char)((r > 9) ? (r - 10) + 'a' : r + '0');
i++;
number /= base;
}
if (negative)
{
arr[i] = '-';
i++;
}
printf_strrev(arr, i);
arr[i] = '\0';
return arr;
}
#pragma GCC push_options
#pragma GCC optimize("O0")
template <typename PutString>
static int internal_printf(const char* format, PutString put_string_callback, ssize_t max, va_list ap)
{
char buffer[1025]; // 1024 with null terminator
size_t format_size = strlen(format);
size_t format_index = 0;
size_t buffer_insert_index = 0;
ssize_t max_remaining = max;
size_t written = 0;
auto flush_buffer = [&]() {
size_t buffer_length = buffer_insert_index;
written += buffer_length;
buffer_insert_index = 0;
if (max_remaining < 0)
{
buffer[buffer_length] = 0;
put_string_callback(buffer);
return;
}
if (max_remaining == 0) { return; }
if (buffer_length <= (size_t)max_remaining)
{
max_remaining -= buffer_length;
buffer[buffer_length] = 0;
put_string_callback(buffer);
}
else
{
buffer[max_remaining] = 0;
max_remaining = 0;
put_string_callback(buffer);
}
};
auto append_string = [&](const char* str) {
while (strlen(str) > 1024)
{
flush_buffer();
memcpy(buffer, str, 1024);
str += 1024;
buffer_insert_index = 1024;
}
if (buffer_insert_index + strlen(str) > 1024) flush_buffer();
memcpy(buffer + buffer_insert_index, str, strlen(str));
buffer_insert_index += strlen(str);
if (buffer_insert_index == 1024) flush_buffer();
};
auto append_char = [&](char c) {
buffer[buffer_insert_index++] = c;
if (buffer_insert_index == 1024) flush_buffer();
};
bool is_long = false;
bool is_unsigned_long = false;
bool preserve_format = false;
while (format_index < format_size)
{
char current_char = format[format_index];
if (current_char == '%' || preserve_format)
{
if (!preserve_format && format_index + 1 == format_size) // end of format string
{
format_index++;
continue;
}
else
{
if (!preserve_format) format_index++;
preserve_format = false;
current_char = format[format_index];
switch (current_char)
{
case 'c': {
append_char((char)va_arg(ap, int));
break;
}
case '%': {
append_char('%');
break;
}
case 'z': {
is_unsigned_long = true;
preserve_format = true;
break;
}
case 'l': {
is_long = true;
preserve_format = true;
break;
}
case 'd': {
char result[32];
if (is_unsigned_long)
{
printf_unsigned_to_string(va_arg(ap, uint64_t), result, 10);
is_unsigned_long = is_long = false;
}
else if (is_long)
{
printf_signed_to_string(va_arg(ap, int64_t), result, 10);
is_unsigned_long = is_long = false;
}
else { printf_signed_to_string(va_arg(ap, int32_t), result, 10); }
append_string(result);
break;
}
case 'u': {
char result[32];
if (is_unsigned_long || is_long)
{
printf_unsigned_to_string(va_arg(ap, uint64_t), result, 10);
is_unsigned_long = is_long = false;
}
else { printf_unsigned_to_string(va_arg(ap, uint32_t), result, 10); }
append_string(result);
break;
}
case 'x': {
char result[32];
if (is_unsigned_long || is_long)
{
printf_unsigned_to_string(va_arg(ap, uint64_t), result, 16);
is_unsigned_long = is_long = false;
}
else { printf_unsigned_to_string(va_arg(ap, uint32_t), result, 16); }
append_string(result);
break;
}
case 'p': {
char result[32];
printf_unsigned_to_string(va_arg(ap, uint64_t), result, 16);
append_string(result);
break;
}
case 'm': {
append_string(strerror(errno));
break;
}
case 's': {
append_string(va_arg(ap, const char*));
break;
}
default: {
NOT_IMPLEMENTED("internal_printf: unknown format specifier");
}
}
}
}
else { append_char(current_char); }
format_index++;
}
if (buffer_insert_index > 0) flush_buffer();
return (int)written;
}
#pragma GCC pop_options
extern "C"
{
int vprintf(const char* format, va_list ap)
{
return vfprintf(stdout, format, ap);
}
int vsprintf(char* str, const char* format, va_list ap)
{
if (str) *str = 0; // so strncat starts from the beginning
return internal_printf(
format,
[&](const char* s) {
if (str) strncat(str, s, 1025);
},
-1, ap);
}
int vsnprintf(char* str, size_t max, const char* format, va_list ap)
{
if (max && str) *str = 0; // so strncat starts from the beginning
return internal_printf(
format,
[&](const char* s) {
if (str) strncat(str, s, 1025);
},
max == 0 ? 0 : max - 1, ap);
}
int snprintf(char* str, size_t max, const char* format, ...)
{
va_list ap;
va_start(ap, format);
int written = vsnprintf(str, max, format, ap);
va_end(ap);
return written;
}
int sprintf(char* str, const char* format, ...)
{
va_list ap;
va_start(ap, format);
int written = vsprintf(str, format, ap);
va_end(ap);
return written;
}
int printf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
int written = vfprintf(stdout, format, ap);
va_end(ap);
return written;
}
int fprintf(FILE* stream, const char* format, ...)
{
va_list ap;
va_start(ap, format);
int written = vfprintf(stream, format, ap);
va_end(ap);
return written;
}
int vfprintf(FILE* stream, const char* format, va_list ap)
{
return internal_printf(
format, [&](const char* s) { fwrite(s, strlen(s), 1, stream); }, -1, ap);
}
}