| // Copyright 2025 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // This code was largely generated by Gemini for use in this testsuite only. |
| |
| #include <inttypes.h> |
| #include <stdarg.h> // For va_list |
| #include <string.h> |
| #include <unistd.h> |
| |
| // The file descriptor for Standard Output |
| #define STDOUT_FILENO 1 |
| // Define a reasonable buffer size for the printf wrapper |
| #define PRINTF_BUFFER_SIZE 256 |
| |
| // --- Helper Functions --- |
| |
| /** |
| * @brief Performs the low-level write syscall for the specified architecture. |
| * This replaces the standard write(2) function call. |
| * |
| * @param fd The file descriptor (e.g., STDOUT_FILENO). |
| * @param buf The buffer to write. |
| * @param count The number of bytes to write. |
| * @return The number of bytes written, or -1 on error. |
| */ |
| static ssize_t syscall_write(int fd, const void *buf, size_t count) { |
| ssize_t ret; |
| const long fd_long = (long)fd; |
| const long count_long = (long)count; |
| const long buf_long = (long)buf; |
| |
| // Check for x86-64 (AMD64) |
| #if defined(__x86_64__) |
| __asm__ __volatile__( |
| "movq $1, %%rax\n" // Syscall number 1 (write) -> rax |
| "movq %1, %%rdi\n" // fd -> rdi (Arg 1) |
| "movq %2, %%rsi\n" // buf -> rsi (Arg 2) |
| "movq %3, %%rdx\n" // count -> rdx (Arg 3) |
| "syscall\n" // Execute syscall |
| "movq %%rax, %0\n" // result from rax -> ret |
| : "=r"(ret) // Output: ret |
| : "r"(fd_long), // Input 1: fd |
| "r"(buf_long), // Input 2: buf |
| "r"(count_long) // Input 3: count |
| : "%rax", "%rdi", "%rsi", "%rdx", "memory" // Clobbered registers |
| ); |
| |
| // Check for ARM64 (AArch64) |
| #elif defined(__aarch64__) |
| __asm__ __volatile__( |
| "mov x8, #64\n" // Syscall number 64 (write) -> x8 (Syscall register) |
| "mov x0, %1\n" // fd -> x0 (Arg 1) |
| "mov x1, %2\n" // buf -> x1 (Arg 2) |
| "mov x2, %3\n" // count -> x2 (Arg 3) |
| "svc #0\n" // Execute syscall |
| "mov %0, x0\n" // result from x0 -> ret |
| : "=r"(ret) |
| : "r"(fd_long), "r"(buf_long), "r"(count_long) |
| : "x0", "x1", "x2", "x8", "memory"); |
| |
| // Check for 32-bit ARM (e.g., armv7) |
| #elif defined(__arm__) |
| // Syscall number 4 (write) |
| __asm__ __volatile__( |
| "mov r7, #4\n" // Syscall number 4 (write) -> r7 (Syscall register) |
| "mov r0, %1\n" // fd -> r0 (Arg 1) |
| "mov r1, %2\n" // buf -> r1 (Arg 2) |
| "mov r2, %3\n" // count -> r2 (Arg 3) |
| "svc #0\n" // Execute syscall |
| "mov %0, r0\n" // result from r0 -> ret |
| : "=r"(ret) |
| : "r"(fd_long), "r"(buf_long), "r"(count_long) |
| : "r0", "r1", "r2", "r7", "memory"); |
| |
| // Check for RISC-V 64-bit (RV64) |
| #elif defined(__riscv) && (__riscv_xlen == 64) |
| // Syscall number 64 (write) |
| __asm__ __volatile__( |
| "li a7, 64\n" // Syscall number 64 (write) -> a7 (Syscall register) |
| "mv a0, %1\n" // fd -> a0 (Arg 1) |
| "mv a1, %2\n" // buf -> a1 (Arg 2) |
| "mv a2, %3\n" // count -> a2 (Arg 3) |
| "ecall\n" // Execute syscall |
| "mv %0, a0\n" // result from a0 -> ret |
| : "=r"(ret) |
| : "r"(fd_long), "r"(buf_long), "r"(count_long) |
| : "a0", "a1", "a2", "a7", "memory"); |
| |
| #else |
| ret = -1; |
| #endif |
| |
| return ret; |
| } |
| /** |
| * @brief Manual implementation of string length calculation. |
| * @param s The null-terminated string. |
| * @return The length of the string (excluding null terminator). |
| */ |
| extern "C" size_t strlen(const char *__s) { |
| size_t len = 0; |
| while (*__s++) { |
| len++; |
| } |
| return len; |
| } |
| |
| /** |
| * @brief Models the low-level writing of data to a memory buffer. |
| * |
| * This function serves the role of the low-level I/O operation (like write()) |
| * but targets the memory buffer defined by the vsnprintf signature. |
| * |
| * @param dest The destination memory buffer (str). |
| * @param max_size The maximum size of the destination buffer (including null terminator). |
| * @param current_pos A pointer to track the total required length (required size). |
| * @param data The source data to copy. |
| * @param len The length of the source data. |
| * @return The number of characters successfully written to 'dest' (clipped by max_size). |
| */ |
| static size_t _write_to_buffer(char *dest, size_t max_size, size_t *current_pos, const char *data, |
| size_t len) { |
| size_t written_to_dest = 0; |
| size_t start_pos = *current_pos; |
| size_t i = 0; |
| |
| // Advance the internal counter by the full length of data (tracks the required size) |
| *current_pos += len; |
| |
| // Only write to 'dest' if we haven't hit max_size yet |
| while (i < len) { |
| if (dest && max_size > 0 && start_pos + i < max_size) { |
| // Write to the actual destination buffer |
| dest[start_pos + i] = data[i]; |
| written_to_dest++; |
| } |
| i++; |
| } |
| |
| return written_to_dest; |
| } |
| |
| /** |
| * @brief Manual integer to string conversion (base 10 only). |
| * |
| * @param value The integer value to convert. |
| * @param buffer The temporary buffer to hold the resulting ASCII string. |
| * @param radix The base (must be 10 here). |
| * @param is_unsigned Flag if the value should be treated as unsigned. |
| * @return The length of the resulting string (excluding null terminator). |
| */ |
| static size_t _itoa_manual(long long value, char *buffer, int radix, int is_unsigned) { |
| if (radix != 10) |
| return 0; |
| |
| const char digits[] = "0123456789"; |
| char temp[32]; // Temporary buffer for reverse string |
| int i = 0; |
| int sign = 0; |
| |
| // --- Handle Conversion --- |
| if (!is_unsigned && value < 0) { |
| sign = 1; |
| value = -value; |
| } |
| |
| // Handle 0 case |
| if (value == 0 && !is_unsigned) { |
| temp[i++] = '0'; |
| } else if (is_unsigned) { |
| unsigned long long un = (unsigned long long)value; |
| if (un == 0) { |
| temp[i++] = '0'; |
| } else { |
| while (un != 0) { |
| temp[i++] = digits[un % 10]; |
| un /= 10; |
| } |
| } |
| } else { |
| // Signed positive case |
| long long n = value; |
| while (n != 0) { |
| temp[i++] = digits[n % 10]; |
| n /= 10; |
| } |
| } |
| |
| // Add sign |
| if (sign) { |
| temp[i++] = '-'; |
| } |
| |
| // Reverse the string and copy to the target buffer |
| size_t len = i; |
| for (size_t j = 0; j < len; ++j) { |
| buffer[j] = temp[len - 1 - j]; |
| } |
| |
| return len; |
| } |
| |
| // --- Main vsnprintf Implementation --- |
| |
| /** |
| * @brief Custom vsnprintf implementation using low-level memory operations. |
| * |
| * Supports basic %d/%i (signed int), %u (unsigned int), %s (string), and %c (char). |
| * |
| * @param str The destination buffer. Can be NULL if size is 0. |
| * @param size The maximum number of bytes to be written to str (including the null terminator). |
| * @param format The format string. |
| * @param ap The va_list of arguments. |
| * @return The number of characters that would have been written if size was unlimited |
| * (excluding the null terminator). |
| */ |
| int vsnprintf_custom(char *str, size_t size, const char *format, va_list ap) { |
| size_t total_written_required = 0; |
| const char *p = format; |
| |
| // Initialize the buffer with null termination |
| if (size > 0 && str) { |
| str[0] = '\0'; |
| } |
| |
| // Iterate through the format string |
| while (*p != '\0') { |
| if (*p != '%') { |
| _write_to_buffer(str, size, &total_written_required, p, 1); |
| p++; |
| continue; |
| } |
| |
| p++; // Skip '%' |
| |
| // Handle '%%' escape |
| if (*p == '%') { |
| _write_to_buffer(str, size, &total_written_required, p, 1); |
| p++; |
| continue; |
| } |
| |
| char temp_buffer[64]; // Buffer for formatted numbers |
| size_t len = 0; |
| // Copy va_list to safely handle required promotions for printing |
| va_list current_ap; |
| va_copy(current_ap, ap); |
| |
| switch (*p) { |
| case 'd': |
| case 'i': { |
| // int is promoted to long long in va_arg for safe handling |
| long long val = va_arg(ap, int); |
| len = _itoa_manual(val, temp_buffer, 10, 0); |
| _write_to_buffer(str, size, &total_written_required, temp_buffer, len); |
| break; |
| } |
| case 'u': { |
| // unsigned int is promoted to unsigned long long in va_arg |
| unsigned long long val = va_arg(ap, unsigned int); |
| len = _itoa_manual(val, temp_buffer, 10, 1); |
| _write_to_buffer(str, size, &total_written_required, temp_buffer, len); |
| break; |
| } |
| case 's': { |
| const char *s = va_arg(ap, const char *); |
| if (s == NULL) |
| s = "(null)"; |
| len = strlen(s); |
| _write_to_buffer(str, size, &total_written_required, s, len); |
| break; |
| } |
| case 'c': { |
| // char is promoted to int in va_arg |
| char c = (char)va_arg(ap, int); |
| _write_to_buffer(str, size, &total_written_required, &c, 1); |
| break; |
| } |
| default: |
| // If unknown specifier, print the '%' and the char (e.g., "%x" prints "%x") |
| _write_to_buffer(str, size, &total_written_required, p - 1, 2); |
| break; |
| } |
| va_end(current_ap); |
| |
| p++; // Move past the format specifier |
| } |
| |
| // Null-terminate the string, ensuring we don't write past the buffer size |
| if (size > 0 && str) { |
| // The actual index to place the null terminator |
| size_t null_idx = (total_written_required < size) ? total_written_required : (size - 1); |
| str[null_idx] = '\0'; |
| } |
| |
| // Return the total length that *would* have been written (excluding null terminator) |
| return (int)total_written_required; |
| } |
| |
| // Simple convenience wrapper, akin to the standard snprintf |
| int snprintf_manual(char *str, size_t size, const char *format, ...) { |
| va_list ap; |
| va_start(ap, format); |
| int result = vsnprintf_custom(str, size, format, ap); |
| va_end(ap); |
| return result; |
| } |
| |
| /** |
| * @brief Custom printf wrapper using vsnprintf_custom for formatting and the |
| * write() system call for output (STDOUT_FILENO). |
| * |
| * @param format The format string. |
| * @param ... The variable arguments. |
| * @return The number of characters written to the file descriptor, or -1 on error. |
| */ |
| extern "C" __attribute__((visibility("default"))) int printf(const char *__restrict __format, ...) { |
| char buffer[PRINTF_BUFFER_SIZE]; |
| va_list ap; |
| va_start(ap, __format); |
| |
| // 1. Format the string into the buffer using the custom low-level vsnprintf |
| int required_len = vsnprintf_custom(buffer, PRINTF_BUFFER_SIZE, __format, ap); |
| va_end(ap); |
| |
| // Determine the actual length to write. It is the minimum of the required length |
| // and the buffer's capacity minus the null terminator. |
| size_t actual_write_len = required_len; |
| if (actual_write_len >= PRINTF_BUFFER_SIZE) { |
| actual_write_len = PRINTF_BUFFER_SIZE - 1; // write up to the null terminator |
| } |
| |
| // Check if there's anything to write |
| if (actual_write_len == 0) { |
| return 0; |
| } |
| |
| // 2. Use the low-level write() system call to output the content |
| ssize_t written = syscall_write(STDOUT_FILENO, buffer, actual_write_len); |
| |
| // Return the number of bytes written, or -1 if the write system call failed. |
| if (written < 0) { |
| return -1; |
| } |
| // Note: Standard printf returns the number of characters printed, which is written here. |
| return (int)written; |
| } |