minipaxtar.c

More...

Functions

Name
mptar_size_t mptar_strlen(const char * str)
mptar_size_t mptar_strnlen(const char * str, mptar_size_t max_limit)
char * mptar_strncpy(char * dst, const char * src, mptar_size_t n)
void mptar_memcpy(void * dest, const void * src, mptar_size_t n)
void mptar_memset(void * data, char value, mptar_size_t amount)
int mptar_memcmp(const void * s1, const void * s2, mptar_size_t size)

Detailed Description

Author: Vulpi 78976654+imvulpi@users.noreply.github.com SPDX-License-Identifier: MIT

Copyright: Copyright (c) 2026 Vulpi

Functions Documentation

function mptar_strlen

mptar_size_t mptar_strlen(
    const char * str
)

function mptar_strnlen

mptar_size_t mptar_strnlen(
    const char * str,
    mptar_size_t max_limit
)

function mptar_strncpy

char * mptar_strncpy(
    char * dst,
    const char * src,
    mptar_size_t n
)

function mptar_memcpy

void mptar_memcpy(
    void * dest,
    const void * src,
    mptar_size_t n
)

function mptar_memset

void mptar_memset(
    void * data,
    char value,
    mptar_size_t amount
)

function mptar_memcmp

int mptar_memcmp(
    const void * s1,
    const void * s2,
    mptar_size_t size
)

Source code

#include "minipaxtar.h"

#ifndef MPTAR_NO_STD

#include <string.h>
#include <stdlib.h>

#define mptar_strlen  strlen

#define mptar_strnlen strnlen

#define mptar_strncpy strncpy

#define mptar_memcpy  memcpy

#define mptar_memset  memset

#define mptar_memcmp  memcmp

#else

#ifndef MPTAR_CUSTOM_STRLEN
static mptar_size_t mptar_strlen(const char* str) {
    if (!str) return 0;
    mptar_size_t len = 0;
    while (str[len] != '\0') {
        len++;
    }
    return len;
}
#else
extern mptar_size_t mptar_strlen(const char* str);
#endif

#ifndef MPTAR_CUSTOM_STRNLEN
static mptar_size_t mptar_strnlen(const char* str, mptar_size_t max_limit) {
    if (!str) return 0;
    mptar_size_t count = 0;
    while (count < max_limit && str[count] != '\0') {
        count++;
    }
    return count;
}
#else
extern mptar_size_t mptar_strnlen(const char* str, mptar_size_t max_limit);
#endif

#ifndef MPTAR_CUSTOM_STRNCPY
static char* mptar_strncpy(char* dst, const char* src, mptar_size_t n) {
    if (dst == MPTAR_NULL || n == 0) return dst;

    mptar_size_t i = 0;

    if (src != MPTAR_NULL) {
        while (i < n && src[i] != '\0') {
            dst[i] = src[i];
            i++;
        }
    }

    while (i < n) {
        dst[i] = '\0';
        i++;
    }

    return dst;
}
#else
extern char* mptar_strncpy(char* dst, const char* src, mptar_size_t n);
#endif

#ifndef MPTAR_CUSTOM_MEMCPY
static void mptar_memcpy(void* dest, const void* src, mptar_size_t n) {
    if (!dest || !src || n == 0) return;

    char* d = (char*)dest;
    const char* s = (const char*)src;

    mptar_size_t i = 0;
    for ( ; i < n; i++) {
        d[i] = s[i];
    }
}
#else
extern void mptar_memcpy(void* dest, const void* src, mptar_size_t n);
#endif

#ifndef MPTAR_CUSTOM_MEMSET
static void mptar_memset(void* data, char value, mptar_size_t amount) {
    if (!data || amount == 0) return;

    char* bytes = (char*)data;
    mptar_size_t i = 0;
    for ( ; i < amount; i++) {
        bytes[i] = value;
    }
}
#else
extern void mptar_memset(void* data, char value, mptar_size_t amount);
#endif

#ifndef MPTAR_CUSTOM_MEMCMP
static int mptar_memcmp(const void* s1, const void* s2, mptar_size_t size) {
    if (!s1 || !s2 || size == 0) return 0;

    const unsigned char* p1 = (const unsigned char*)s1;
    const unsigned char* p2 = (const unsigned char*)s2;

    mptar_size_t i = 0; 
    for (; i < size; i++) {
        if (p1[i] != p2[i]) {
            return (p1[i] < p2[i]) ? -1 : 1;
        }
    }

    return 0;
}
#else
extern int mptar_memcmp(const void* s1, const void* s2, mptar_size_t size);
#endif

#endif /* MPTAR_NO_STD */

#ifndef MPTAR_NO_WRITE

#if defined(MPTAR_CUSTOM_IU64TOA)
    #define MPTAR_CUSTOM_U64TOA
    #define MPTAR_CUSTOM_I64TOA
#endif

#ifndef MPTAR_CUSTOM_U64TOA
static char* mptar_u64toa(mptar_uint64 value, char* str, mptar_size_t str_size, int *out_err){
    if (out_err) *out_err = MPTAR_OK;
    if (str == MPTAR_NULL || str_size < 2) {
        if (out_err) *out_err = MPTAR_ERR_INVALID_ARG;
        return MPTAR_NULL;
    }

    mptar_size_t i = 0;
    if (value == 0) {
        str[0] = '0';
        str[1] = '\0';
        return str;
    }

    while (value > 0 && i < (str_size - 1)) {
        str[i++] = (char)((value % 10) + '0');
        value /= 10;
    }

    if (value > 0) {
        if (out_err) *out_err = MPTAR_ERR_OVERFLOW;
        str[str_size - 1] = '\0';
        return str;
    }

    str[i] = '\0';

    mptar_size_t start = 0;
    mptar_size_t end = i - 1;
    while (start < end) {
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }

    return str;
}

#else

extern char* mptar_u64toa(mptar_uint64 value, char* str, mptar_size_t str_size, int *out_err);

#endif

#ifndef MPTAR_CUSTOM_I64TOA
static char* mptar_i64toa(mptar_int64 value, char* buf, mptar_size_t buf_size, int *out_err) {
    if (out_err) *out_err = MPTAR_OK;

    if (buf == MPTAR_NULL || buf_size <= 1) {
        if (out_err) *out_err = MPTAR_ERR_INVALID_ARG;
        return MPTAR_NULL;
    }

    if (value < 0) {
        mptar_uint64 abs_val;
        if (value == MPTAR_INT64_MIN) {
            abs_val = (mptar_uint64)MPTAR_INT64_MAX + 1ULL;
        } else {
            abs_val = (mptar_uint64)(-value);
        }

        char* result = mptar_u64toa(abs_val, buf + 1, buf_size - 1, out_err);
        if (result == MPTAR_NULL) {
            return MPTAR_NULL;
        }

        buf[0] = '-';
        return buf;
    } else {
        return mptar_u64toa((mptar_uint64)value, buf, buf_size, out_err);
    }
}

#else

extern char* mptar_i64toa(mptar_int64 value, char* buf, mptar_size_t buf_size, int *out_err);

#endif

#ifndef MPTAR_CUSTOM_STREAM_WRITE_ZEROES
static int mptar_stream_write_zeroes(mptar_writer* ctx, mptar_size_t amount) {
    if (!ctx) return MPTAR_ERR_INVALID_ARG;
    if (amount == 0) return MPTAR_OK;

    const char zero_chunk[16] = {0}; 
    mptar_size_t total_written = 0;

    while (total_written < amount) {
        mptar_size_t remaining = amount - total_written;
        mptar_size_t chunk_size = (remaining > sizeof(zero_chunk)) ? sizeof(zero_chunk) : remaining;

        mptar_size_t res = ctx->write(ctx->write_user_data, zero_chunk, chunk_size);
        if (res == 0 || res > chunk_size) return MPTAR_ERR_IO_WRITE;
        total_written += chunk_size;
    }

    return MPTAR_OK;
}
#else
extern int mptar_stream_write_zeroes(mptar_writer* ctx, mptar_size_t padding_needed);
#endif

#ifndef MPTAR_CUSTOM_PAX_CALCULATE_RECORD_LEN
static mptar_uint32 mptar_pax_calculate_record_len(mptar_uint32 data_len) {
    if (data_len > MPTAR_PAX_MAX_DATA_LEN) return 0; /* Would overflow */

    mptar_uint32 digits = 1;
    if      (data_len >= 1000000000U) digits = 10;
    else if (data_len >= 100000000U)  digits = 9;
    else if (data_len >= 10000000U)   digits = 8;
    else if (data_len >= 1000000U)    digits = 7;
    else if (data_len >= 100000U)     digits = 6;
    else if (data_len >= 10000U)      digits = 5;
    else if (data_len >= 1000U)       digits = 4;
    else if (data_len >= 100U)        digits = 3;
    else if (data_len >= 10U)         digits = 2;

    mptar_uint32 total = data_len + digits;

    mptar_uint32 final_digits = 1;
    if      (total >= 1000000000U) final_digits = 10;
    else if (total >= 100000000U)  final_digits = 9;
    else if (total >= 10000000U)   final_digits = 8;
    else if (total >= 1000000U)    final_digits = 7;
    else if (total >= 100000U)     final_digits = 6;
    else if (total >= 10000U)      final_digits = 5;
    else if (total >= 1000U)       final_digits = 4;
    else if (total >= 100U)        final_digits = 3;
    else if (total >= 10U)         final_digits = 2;

    return data_len + final_digits;
}
#else
extern mptar_uint32 mptar_pax_calculate_record_len(mptar_uint32 data_len);
#endif

#ifndef MPTAR_CUSTOM_PAX_FORMAT_TIME
static char* mptar_pax_format_time(mptar_int64 sec, mptar_uint32 nsec, char* str, mptar_size_t size, int *out_err){
    if(out_err) *out_err = MPTAR_OK;
    if(str == MPTAR_NULL || size == 0){
        if(out_err) *out_err = MPTAR_ERR_INVALID_ARG;
        return MPTAR_NULL;
    }

    int error = MPTAR_OK;
    mptar_i64toa(sec, str, size, &error);
    if (error != MPTAR_OK) {
        if(out_err) *out_err = error;
        return MPTAR_NULL;
    }

    if (nsec == 0) {
        return str;
    }

    mptar_size_t int_size = mptar_strlen(str);

    char nsec_buf[10] = {0}; /* Space for 9 digits + \0 */
    int nsec_err = MPTAR_OK;
    mptar_u64toa(nsec, nsec_buf, sizeof(nsec_buf), &nsec_err);
    if (nsec_err != MPTAR_OK) {
        if(out_err) *out_err = MPTAR_ERR_NS_CONVERSION_FAILED;
        return str; 
    }

    mptar_size_t nsec_len = mptar_strlen(nsec_buf);
    char formatted_nsec[sizeof(nsec_buf)];
    mptar_size_t leading_zeros = sizeof(formatted_nsec) - 1 - nsec_len;

    if (leading_zeros > 0) mptar_memset(formatted_nsec, '0', leading_zeros);
    if (nsec_len > 0) mptar_memcpy(&formatted_nsec[leading_zeros], nsec_buf, nsec_len);
    formatted_nsec[sizeof(formatted_nsec) - 1] = '\0';

    int end = sizeof(formatted_nsec) - 2;
    while (end > 0 && formatted_nsec[end] == '0') {
        formatted_nsec[end] = '\0';
        end--;
    }

    mptar_size_t final_nsec_len = mptar_strlen(formatted_nsec);
    mptar_size_t required_bytes = int_size + final_nsec_len + MPTAR_PAX_NSEC_OVERHEAD;
    if (required_bytes > size) {
        if(out_err) *out_err = MPTAR_ERR_BUFFER_TOO_SMALL;
        return str;
    }

    str[int_size] = '.';
    mptar_memcpy(&str[int_size + 1], formatted_nsec, final_nsec_len);
    str[int_size + 1 + final_nsec_len] = '\0';

    return str;
}
#else
extern char* mptar_pax_format_time(mptar_int64 sec, mptar_uint32 nsec, char* str, mptar_size_t size, int *out_err);
#endif

#ifndef MPTAR_CUSTOM_PAX_STREAM_RECORD
static int mptar_pax_stream_record(mptar_writer* ctx, mptar_uint32 record_size, const char* keyword, const char* value, mptar_size_t value_len) {
    char size_str[12];
    mptar_u64toa(record_size, size_str, sizeof(size_str), MPTAR_NULL);
    mptar_size_t size_len = mptar_strlen(size_str);
    mptar_size_t kw_len = mptar_strlen(keyword);

    if (ctx->write(ctx->write_user_data, size_str, size_len) != size_len) return MPTAR_ERR_IO_WRITE;
    if (ctx->write(ctx->write_user_data, " ", 1) != 1) return MPTAR_ERR_IO_WRITE;
    if (ctx->write(ctx->write_user_data, keyword, kw_len) != kw_len) return MPTAR_ERR_IO_WRITE;
    if (ctx->write(ctx->write_user_data, "=", 1) != 1) return MPTAR_ERR_IO_WRITE;
    if (ctx->write(ctx->write_user_data, value, value_len) != value_len) return MPTAR_ERR_IO_WRITE;
    if (ctx->write(ctx->write_user_data, "\n", 1) != 1) return MPTAR_ERR_IO_WRITE;

    return MPTAR_OK;
}
#else
extern int mptar_pax_stream_record(mptar_writer* ctx, mptar_uint32 record_size, const char* keyword, const char* value, mptar_size_t value_len);
#endif

#ifndef MPTAR_CUSTOM_WRITE_OCTAL_FIELD
static void mptar_write_octal_field(char* dst, mptar_uint64 value, mptar_size_t size)
{
    if(dst == MPTAR_NULL || size == 0) return;

    mptar_size_t octal_digits = size - 1; 
    mptar_uint64 max_octal_val = 0;

    if (octal_digits >= MPTAR_MAX_OCTAL_DIGITS_64) {
        max_octal_val = MPTAR_UINT64_MAX;
    } else {
        max_octal_val = ((mptar_uint64)1 << (octal_digits * 3)) - 1;
    }

    if (value > max_octal_val) {
        mptar_size_t i = size;

        while(i > 0){
            dst[--i] = (char)(value & 0xFF);
            value >>= 8;
        }

        dst[0] |= MPTAR_OCTAL_BINARY_FLAG;
        return;
    }

    mptar_memset(dst, '0', size);
    dst[size - 1] = '\0';

    mptar_size_t i = octal_digits;
    while (i > 0) {
        dst[--i] = '0' + (value & 7);
        value >>= 3;
    }

    dst[octal_digits] = '\0';
}
#else
extern void mptar_write_octal_field(char* dst, mptar_uint64 value, mptar_size_t size);
#endif

#ifndef MPTAR_CUSTOM_CALCULATE_HEADER_CHECKSUM
static mptar_uint32 mptar_calculate_header_checksum(char* header_block){
    if (header_block == MPTAR_NULL) return 0;

    const unsigned char* block = (const unsigned char*)header_block;
    mptar_uint32 sum = 0;    

    mptar_uint16 i = 0;
    for ( ; i < MPTAR_CHECKSUM_OFFSET; i++) {
        sum += block[i];
    }

    /* Hardcoded constant value for the 8 space characters (8 * 32) */
    sum += MPTAR_SPACE_CHECKSUM_VAL; 

    for (i = MPTAR_CHECKSUM_END_OFFSET; i < MPTAR_USTAR_HEADER_SIZE; i++) {
        sum += block[i];
    }

    return sum;
}
#else
extern mptar_uint32 mptar_calculate_header_checksum(char* header_block);
#endif

#ifndef MPTAR_CUSTOM_WRITE_HEADER_CHECKSUM
static void mptar_write_header_checksum(char* header_block) {
    if (header_block == MPTAR_NULL) return;
    mptar_uint32 sum = mptar_calculate_header_checksum(header_block);
    mptar_write_octal_field(header_block + MPTAR_CHECKSUM_OFFSET, sum, 7);
    header_block[MPTAR_CHECKSUM_END_OFFSET - 1] = ' ';
}
#else
extern void mptar_write_header_checksum(char* header_block);
#endif

#ifndef MPTAR_CUSTOM_CAN_PATH_FIT_USTAR
static bool mptar_can_path_fit_ustar(const char* path, mptar_size_t len) {
    if (!path) return false;

    if (len <= MPTAR_USTAR_SIZE_NAME) {
        return true;
    }else if (len > (MPTAR_USTAR_SIZE_PREFIX + 1 + MPTAR_USTAR_SIZE_NAME)) {
        return false; 
    }

    mptar_size_t i = len - 1;
    for ( ; i > 0; i--) {
        mptar_size_t name_len = len - i - 1;

        if (name_len > MPTAR_USTAR_SIZE_NAME) {
            break; 
        }

        if (path[i] == '/') {
            mptar_size_t prefix_len = i;

            if (prefix_len <= MPTAR_USTAR_SIZE_PREFIX && name_len <= MPTAR_USTAR_SIZE_NAME) {
                return true; 
            }
        }
    }

    return false;
}
#else
extern bool mptar_can_path_fit_ustar(const char* path, mptar_size_t len);
#endif

#ifndef MPTAR_CUSTOM_WRITE_USTAR_PATH
static int mptar_write_ustar_path(char* header_name, char* header_prefix, const char* full_path)
{
    if(header_name == MPTAR_NULL || full_path == MPTAR_NULL) return MPTAR_ERR_INVALID_ARG; /* header_prefix can be null. */

    mptar_size_t path_len = mptar_strlen(full_path);
    if (path_len <= MPTAR_USTAR_SIZE_NAME) {
        mptar_memcpy(header_name, full_path, path_len);
        if (path_len < MPTAR_USTAR_SIZE_NAME) {
            header_name[path_len] = '\0';
        }
        if (header_prefix != MPTAR_NULL) {
            header_prefix[0] = '\0';
        }
        return MPTAR_OK;
    }

    if (header_prefix == MPTAR_NULL) {
        mptar_memcpy(header_name, full_path, MPTAR_USTAR_SIZE_NAME);
        return MPTAR_NEEDS_PAX;
    }

    if (path_len > (MPTAR_USTAR_SIZE_PREFIX + 1 + MPTAR_USTAR_SIZE_NAME)) {
        mptar_memcpy(header_name, full_path, MPTAR_USTAR_SIZE_NAME);
        return MPTAR_NEEDS_PAX;
    }

    int split_index = -1;
    mptar_size_t i = 0;
    for ( ; i < path_len; i++) {
        if (full_path[i] == '/') {
            mptar_size_t prefix_part_len = i;
            mptar_size_t name_part_len = path_len - i - 1;

            if (prefix_part_len <= MPTAR_USTAR_SIZE_PREFIX && name_part_len <= MPTAR_USTAR_SIZE_NAME) {
                if (name_part_len == 0 && split_index != -1) {
                    continue; 
                }
                split_index = (int)i;
            }
        }
    }

    if (split_index == -1) {
        mptar_memcpy(header_name, full_path, MPTAR_USTAR_SIZE_NAME);
        return MPTAR_NEEDS_PAX;
    }

    mptar_size_t final_prefix_len = (mptar_size_t)split_index;
    mptar_size_t final_name_len = path_len - final_prefix_len - 1;

    mptar_memcpy(header_prefix, full_path, final_prefix_len);
    if (final_prefix_len < MPTAR_USTAR_SIZE_PREFIX) {
        header_prefix[final_prefix_len] = '\0';
    }

    mptar_memcpy(header_name, full_path + final_prefix_len + 1, final_name_len);
    if (final_name_len < MPTAR_USTAR_SIZE_NAME) {
        header_name[final_name_len] = '\0';
    }

    return MPTAR_OK;
}
#else
extern int mptar_write_ustar_path(char* header_name, char* header_prefix, const char* full_path);
#endif

#ifndef MPTAR_CUSTOM_WRITE_USTAR_HEADER
static int mptar_write_ustar_header(mptar_header* header, const mptar_metadata* meta){
    if (header == MPTAR_NULL || meta == MPTAR_NULL) {
        return MPTAR_ERR_INVALID_ARG;
    }
    if (meta->path == MPTAR_NULL) {
        return MPTAR_ERR_INVALID_ARG;
    }

    mptar_memset(header, 0, MPTAR_USTAR_HEADER_SIZE);

    int result_status = MPTAR_OK;

    if (meta->typeflag == '1' || meta->typeflag == '2') {
        if (meta->link_target != MPTAR_NULL) {
            int link_res = mptar_write_ustar_path(header->linkname, MPTAR_NULL, meta->link_target);
            if (link_res == MPTAR_NEEDS_PAX) {
                result_status = MPTAR_NEEDS_PAX;
            }
        }
    }

    int path_res = mptar_write_ustar_path(header->name, header->prefix, meta->path);
    if (path_res == MPTAR_NEEDS_PAX) {
        result_status = MPTAR_NEEDS_PAX;
    }

    if (meta->uname != MPTAR_NULL) {
        mptar_strncpy(header->uname, meta->uname, sizeof(header->uname));
        header->uname[(sizeof(header->uname) - 1)] = '\0';
    }

    if (meta->gname != MPTAR_NULL) {
        mptar_strncpy(header->gname, meta->gname, sizeof(header->gname));
        header->gname[(sizeof(header->gname) - 1)] = '\0';
    }

    mptar_write_octal_field(header->gid, meta->gid, sizeof(header->gid));
    mptar_write_octal_field(header->uid, meta->uid, sizeof(header->uid));
    mptar_write_octal_field(header->mode, (mptar_uint64)meta->mode, sizeof(header->mode));

#ifdef MPTAR_SUPPORT_SPECIAL
    if (meta->typeflag == '3' || meta->typeflag == '4') {
        mptar_write_octal_field(header->devmajor, meta->devmajor, sizeof(header->devmajor));
        mptar_write_octal_field(header->devminor, meta->devminor, sizeof(header->devminor));
    }
#endif

    mptar_uint64 size_to_write = meta->size;
    if (size_to_write >= MPTAR_USTAR_MAX_OCTAL_12B) {
        size_to_write = 0;
        result_status = MPTAR_NEEDS_PAX;
    }
    mptar_write_octal_field(header->size, size_to_write, sizeof(header->size));

    if(meta->mtime.has_value){
        mptar_write_octal_field(header->mtime, (mptar_uint64)meta->mtime.value.sec, sizeof(header->mtime));
    }

    header->typeflag = meta->typeflag;
    mptar_memcpy(header->magic, "ustar\0", sizeof(header->magic));
    mptar_memcpy(header->version, "00", sizeof(header->version));
    mptar_write_header_checksum((char*)header);

    return result_status;
}
#else
extern int mptar_write_ustar_header(mptar_header* header, const mptar_metadata* meta);
#endif

#ifndef MPTAR_CUSTOM_WRITE_PAX_HEADER
static int mptar_write_pax_header(mptar_writer* ctx, mptar_uint64 size, const mptar_metadata* meta){
    if (ctx == MPTAR_NULL || meta == MPTAR_NULL) {
        return MPTAR_ERR_INVALID_ARG;
    }

    char* header_bytes = (char*)ctx->memory.alloc(ctx->memory.alloc_user_data, MPTAR_USTAR_HEADER_SIZE);
    if(header_bytes == MPTAR_NULL){
        return MPTAR_ERR_ALLOC;
    }

    mptar_header* header = (mptar_header*)header_bytes;
    mptar_metadata temp_meta = {0};

    temp_meta.path = "PaxHeader/dump";
    temp_meta.size = size; /* Size of pax payload */
    temp_meta.mode = meta->mode;
    temp_meta.uid = meta->uid;
    temp_meta.gid = meta->gid;
    temp_meta.mtime = meta->mtime;
    temp_meta.typeflag = 'x';

    mptar_write_ustar_header(header, &temp_meta);

    mptar_size_t bytes_written = ctx->write(ctx->write_user_data, header_bytes, MPTAR_USTAR_HEADER_SIZE);
    ctx->memory.free(ctx->memory.alloc_user_data, header_bytes);

    if (bytes_written != MPTAR_USTAR_HEADER_SIZE) {
        return MPTAR_ERR_IO_WRITE;
    }

    return MPTAR_OK;
}
#else
extern int mptar_write_pax_header(mptar_writer* ctx, mptar_uint64 size, const mptar_metadata* meta);
#endif

int mptar_write_header(mptar_writer* ctx, const mptar_metadata* meta){
    if (ctx == MPTAR_NULL || meta == MPTAR_NULL || meta->path == MPTAR_NULL || 
        ctx->memory.alloc == MPTAR_NULL || ctx->memory.free == MPTAR_NULL || ctx->write == MPTAR_NULL) {
        return MPTAR_ERR_INVALID_ARG;
    }

    if(ctx->bytes_left > 0) return MPTAR_ERR_INCOMPLETE_PAYLOAD;

#ifndef MPTAR_SUPPORT_SPECIAL
    if (meta->typeflag == '3' || meta->typeflag == '4') {
        return MPTAR_ERR_UNSUPPORTED_TYPE;
    }
#endif

    mptar_size_t path_len = mptar_strlen(meta->path);
    mptar_size_t link_target_size = (meta->link_target == MPTAR_NULL) ? 0 : mptar_strlen(meta->link_target);
    mptar_size_t uname_len = (meta->uname == MPTAR_NULL) ? 0 : mptar_strlen(meta->uname);
    mptar_size_t gname_len = (meta->gname == MPTAR_NULL) ? 0 : mptar_strlen(meta->gname);

    bool large_path = !mptar_can_path_fit_ustar(meta->path, path_len);
    bool large_size = ((ctx->flags & MPTAR_CTX_ALLOW_PAX_FOR_OCTAL) != 0) && (meta->size >= MPTAR_USTAR_MAX_OCTAL_12B);
    bool large_link_target = (link_target_size > MPTAR_USTAR_MAX_LEN_LINKNAME);
    bool uid_needs_pax = (meta->uid > MPTAR_USTAR_MAX_OCTAL_8B) && (((ctx->flags & MPTAR_CTX_ALLOW_PAX_FOR_OCTAL) != 0) || meta->uid > MPTAR_BINARY_MAX_8B);
    bool gid_needs_pax = (meta->gid > MPTAR_USTAR_MAX_OCTAL_8B) && (((ctx->flags & MPTAR_CTX_ALLOW_PAX_FOR_OCTAL) != 0) || meta->gid > MPTAR_BINARY_MAX_8B);
    bool uname_needs_pax = (uname_len > MPTAR_USTAR_MAX_LEN_UNAME);
    bool gname_needs_pax = (gname_len > MPTAR_USTAR_MAX_LEN_GNAME);

    bool mtime_needs_pax = meta->mtime.has_value && 
        (meta->mtime.value.sec < 0 || meta->mtime.value.nsec > 0
        || (meta->mtime.value.sec > (mptar_int64)MPTAR_USTAR_MAX_OCTAL_12B && ((ctx->flags & MPTAR_CTX_ALLOW_PAX_FOR_OCTAL) != 0)));

    #ifdef MPTAR_SUPPORT_EXTRA_TIMES
        bool times_need_pax = mtime_needs_pax || meta->atime.has_value || meta->ctime.has_value;
    #else
        bool times_need_pax = mtime_needs_pax;
    #endif

    int res = MPTAR_OK;
    bool need_pax = large_path || large_size || large_link_target || uid_needs_pax || gid_needs_pax || uname_needs_pax || gname_needs_pax || times_need_pax;
    if(need_pax) {
        mptar_uint64 pax_header_size = 0;
        char size_str_buf[MPTAR_UINT64_STR_BUF_SIZE] = {0};
        mptar_size_t size_str_len = 0;

        mptar_uint32 pax_path_size = 0;
        mptar_uint32 pax_size_size = 0;
        mptar_uint32 pax_link_target_size = 0;
        mptar_uint32 pax_mtime_size = 0;
        char mtime_str_buf[MPTAR_TIMESPEC_STR_BUF_SIZE];
        mptar_size_t mtime_str_len = 0;
        char uid_str_buf[MPTAR_UINT64_STR_BUF_SIZE];
        char gid_str_buf[MPTAR_UINT64_STR_BUF_SIZE];
        mptar_size_t uid_str_len = 0;
        mptar_size_t gid_str_len = 0;

        mptar_uint32 pax_uid_size = 0;
        mptar_uint32 pax_gid_size = 0;
        mptar_uint32 pax_uname_size = 0;
        mptar_uint32 pax_gname_size = 0;

#ifdef MPTAR_SUPPORT_EXTRA_TIMES
        mptar_uint32 pax_atime_size = 0;
        mptar_uint32 pax_ctime_size = 0;
        char atime_str_buf[MPTAR_TIMESPEC_STR_BUF_SIZE];
        char ctime_str_buf[MPTAR_TIMESPEC_STR_BUF_SIZE];
        mptar_size_t atime_str_len = 0;
        mptar_size_t ctime_str_len = 0;
#endif

        if (large_path) {
            pax_path_size = mptar_pax_calculate_record_len((mptar_uint32)path_len + MPTAR_PAX_OVERHEAD_PATH);
            pax_header_size += pax_path_size;
        }

        if (large_size) {
            mptar_u64toa(meta->size, size_str_buf, sizeof(size_str_buf), &res);
            if (res != MPTAR_OK) return res;
            size_str_len = mptar_strlen(size_str_buf);
            pax_size_size = mptar_pax_calculate_record_len((mptar_uint32)size_str_len + MPTAR_PAX_OVERHEAD_SIZE);
            pax_header_size += pax_size_size;
        }

        if (large_link_target) {
            pax_link_target_size = mptar_pax_calculate_record_len((mptar_uint32)link_target_size + MPTAR_PAX_OVERHEAD_LINK);
            pax_header_size += pax_link_target_size;
        }

        if (mtime_needs_pax) {
            mptar_pax_format_time(meta->mtime.value.sec, meta->mtime.value.nsec, mtime_str_buf, sizeof(mtime_str_buf), &res);
            if (res != MPTAR_OK) return res;
            mtime_str_len = mptar_strlen(mtime_str_buf);
            pax_mtime_size = mptar_pax_calculate_record_len((mptar_uint32)mtime_str_len + MPTAR_PAX_OVERHEAD_TIME);
            pax_header_size += pax_mtime_size;
        }

#ifdef MPTAR_SUPPORT_EXTRA_TIMES
        if (meta->atime.has_value) {
            mptar_pax_format_time(meta->atime.value.sec, meta->atime.value.nsec, atime_str_buf, sizeof(atime_str_buf), &res);
            if (res != MPTAR_OK) return res;
            atime_str_len = mptar_strlen(atime_str_buf);
            pax_atime_size = mptar_pax_calculate_record_len((mptar_uint32)atime_str_len + MPTAR_PAX_OVERHEAD_TIME);
            pax_header_size += pax_atime_size;
        }

        if (meta->ctime.has_value) {
            mptar_pax_format_time(meta->ctime.value.sec, meta->ctime.value.nsec, ctime_str_buf, sizeof(ctime_str_buf), &res);
            if (res != MPTAR_OK) return res;
            ctime_str_len = mptar_strlen(ctime_str_buf);
            pax_ctime_size = mptar_pax_calculate_record_len((mptar_uint32)ctime_str_len + MPTAR_PAX_OVERHEAD_TIME);
            pax_header_size += pax_ctime_size;
        }
#endif

        if (uid_needs_pax) {
            mptar_u64toa(meta->uid, uid_str_buf, sizeof(uid_str_buf), &res);
            if (res != MPTAR_OK) return res;
            uid_str_len = mptar_strlen(uid_str_buf);
            pax_uid_size = mptar_pax_calculate_record_len((mptar_uint32)uid_str_len + MPTAR_PAX_OVERHEAD_UID);
            pax_header_size += pax_uid_size;
        }

        if (gid_needs_pax) {
            mptar_u64toa(meta->gid, gid_str_buf, sizeof(gid_str_buf), &res);
            if (res != MPTAR_OK) return res;
            gid_str_len = mptar_strlen(gid_str_buf);
            pax_gid_size = mptar_pax_calculate_record_len((mptar_uint32)gid_str_len + MPTAR_PAX_OVERHEAD_GID);
            pax_header_size += pax_gid_size;
        }

        if (uname_needs_pax) {
            pax_uname_size = mptar_pax_calculate_record_len((mptar_uint32)uname_len + MPTAR_PAX_OVERHEAD_UNAME);
            pax_header_size += pax_uname_size;
        }

        if (gname_needs_pax) {
            pax_gname_size = mptar_pax_calculate_record_len((mptar_uint32)gname_len + MPTAR_PAX_OVERHEAD_GNAME);
            pax_header_size += pax_gname_size;
        }

        res = mptar_write_pax_header(ctx, pax_header_size, meta);
        if (res != MPTAR_OK) return res;

        if (large_path) {
            res = mptar_pax_stream_record(ctx, pax_path_size, "path", meta->path, path_len);
            if (res != MPTAR_OK) return res;
        }

        if (large_size) {
            res = mptar_pax_stream_record(ctx, pax_size_size, "size", size_str_buf, size_str_len);
            if (res != MPTAR_OK) return res;
        }

        if (large_link_target) {
            res = mptar_pax_stream_record(ctx, pax_link_target_size, "linkpath", meta->link_target, link_target_size);
            if (res != MPTAR_OK) return res;
        }

        if (mtime_needs_pax) {
            res = mptar_pax_stream_record(ctx, pax_mtime_size, "mtime", mtime_str_buf, mtime_str_len);
            if (res != MPTAR_OK) return res;
        }

#ifdef MPTAR_SUPPORT_EXTRA_TIMES
        if (meta->atime.has_value) {
            res = mptar_pax_stream_record(ctx, pax_atime_size, "atime", atime_str_buf, atime_str_len);
            if (res != MPTAR_OK) return res;
        }

        if (meta->ctime.has_value) {
            res = mptar_pax_stream_record(ctx, pax_ctime_size, "ctime", ctime_str_buf, ctime_str_len);
            if (res != MPTAR_OK) return res;
        }
#endif

        if (uid_needs_pax) {
            res = mptar_pax_stream_record(ctx, pax_uid_size, "uid", uid_str_buf, uid_str_len);
            if (res != MPTAR_OK) return res;
        }

        if (gid_needs_pax) {
            res = mptar_pax_stream_record(ctx, pax_gid_size, "gid", gid_str_buf, gid_str_len);
            if (res != MPTAR_OK) return res;
        }

        if (uname_needs_pax) {
            res = mptar_pax_stream_record(ctx, pax_uname_size, "uname", meta->uname, uname_len);
            if (res != MPTAR_OK) return res;
        }

        if (gname_needs_pax) {
            res = mptar_pax_stream_record(ctx, pax_gname_size, "gname", meta->gname, gname_len);
            if (res != MPTAR_OK) return res;
        }

        mptar_size_t remainder = (mptar_size_t)(pax_header_size % MPTAR_BLOCK_SIZE);
        mptar_size_t padding_needed = (remainder > 0) ? (MPTAR_BLOCK_SIZE - remainder) : 0;
        if (padding_needed > 0) {
            res = mptar_stream_write_zeroes(ctx, padding_needed);
            if (res != MPTAR_OK) return res;
        }
    }

    char* header_bytes = (char*)ctx->memory.alloc(ctx->memory.alloc_user_data, MPTAR_USTAR_HEADER_SIZE);
    if (header_bytes == MPTAR_NULL) {
        return MPTAR_ERR_ALLOC;
    }

    mptar_header* header = (mptar_header*)header_bytes;
    mptar_write_ustar_header(header, meta);

    mptar_size_t written = ctx->write(ctx->write_user_data, header_bytes, MPTAR_USTAR_HEADER_SIZE);
    ctx->memory.free(ctx->memory.alloc_user_data, header_bytes);

    if (written != MPTAR_USTAR_HEADER_SIZE) {
        return MPTAR_ERR_IO_WRITE;
    }

    ctx->bytes_left = meta->size;

    return MPTAR_OK;    
}

mptar_size_t mptar_write_data_chunk(mptar_writer *ctx, const void *buffer, mptar_size_t size, int *out_err)
{
    if(out_err) *out_err = MPTAR_OK;
    if (size == 0 || ctx->bytes_left == 0) return 0;
    if (!ctx || !buffer) {
        if (out_err) *out_err = MPTAR_ERR_INVALID_ARG;
        return 0;
    }

    mptar_size_t bytes_to_write = size;
    if (bytes_to_write > ctx->bytes_left) {
        bytes_to_write = ctx->bytes_left;
    }

    mptar_size_t written = ctx->write(ctx->write_user_data, buffer, bytes_to_write);

    if (written == 0 || written > bytes_to_write) {
        if (out_err) *out_err = MPTAR_ERR_IO_WRITE;
        return 0; 
    }

    ctx->bytes_left -= written;
    return written;
}

int mptar_write_finalize(mptar_writer *ctx, const mptar_metadata *meta)
{
    if (!ctx || !meta) return MPTAR_ERR_INVALID_ARG;
    if (ctx->bytes_left > 0) {
        return MPTAR_ERR_INCOMPLETE_PAYLOAD;
    }

    mptar_size_t remainder = (mptar_size_t)(meta->size % MPTAR_BLOCK_SIZE);
    mptar_size_t padding_needed = (remainder > 0) ? (MPTAR_BLOCK_SIZE - remainder) : 0;

    if (padding_needed > 0) {
        int err = mptar_stream_write_zeroes(ctx, padding_needed);
        if (err != MPTAR_OK) {
            return err;
        }
    }

    return MPTAR_OK;
}

int mptar_close_archive(mptar_writer *ctx)
{
    if (!ctx) return MPTAR_ERR_INVALID_ARG;

    int err = mptar_stream_write_zeroes(ctx, MPTAR_EOA_MARKER_SIZE);
    if (err != MPTAR_OK) {
        return err;
    }

    return MPTAR_OK;
}

#endif /* MPTAR_NO_WRITE */

#ifndef MPTAR_NO_READ

#if defined(MPTAR_CUSTOM_ATOIU64)
    #define MPTAR_CUSTOM_ATOU64
    #define MPTAR_CUSTOM_ATOI64
#endif

#ifndef MPTAR_CUSTOM_ATOU64
static mptar_uint64 mptar_atou64(const char* str, mptar_size_t len, int* err) {
    if(err) *err = MPTAR_OK;
    if(str == MPTAR_NULL || len == 0){
        if(err) *err = MPTAR_ERR_INVALID_ARG;
        return 0;
    }

    const unsigned char* bytes = (const unsigned char*)str;
    mptar_uint64 val = 0;
    mptar_size_t digits_parsed = 0;

    mptar_size_t i = 0;
    for ( ; i < len; i++) {
        unsigned char c = bytes[i];
        if (c < '0' || c > '9') break;

        if (val > (MPTAR_UINT64_MAX / 10)) {
            if(err) *err = MPTAR_ERR_OVERFLOW;
            return MPTAR_UINT64_MAX;
        }

        mptar_uint64 next_val = val * 10;
        unsigned char digit = c - '0';

        if (next_val > (MPTAR_UINT64_MAX - digit)) {
            if(err) *err = MPTAR_ERR_OVERFLOW;
            return MPTAR_UINT64_MAX;
        }

        val = next_val + digit;
        digits_parsed++;
    }

    if(digits_parsed == 0){
        if(err) *err = MPTAR_ERR_MALFORMED;
        return 0;
    }

    return val;
}

#else

extern mptar_uint64 mptar_atou64(const char* str, mptar_size_t len, int* err);

#endif

#ifndef MPTAR_CUSTOM_ATOI64
static mptar_int64 mptar_atoi64(const char* str, mptar_size_t len, int* err) {
    if (err) *err = MPTAR_OK;
    if (str == MPTAR_NULL || len == 0) {
        if(err) *err = MPTAR_ERR_INVALID_ARG;
        return 0;
    }

    bool is_negative = false;
    mptar_size_t start_idx = 0;

    if (str[0] == '-') {
        is_negative = true;
        start_idx = 1;
    } else if (str[0] == '+') {
        start_idx = 1;
    }

    if (start_idx >= len) {
        if (err) *err = MPTAR_ERR_MALFORMED;
        return 0;
    }

    int atou_error = 0;
    mptar_uint64 u_val = mptar_atou64(str + start_idx, len - start_idx, &atou_error);
    if (err) *err = atou_error;
    if (atou_error != MPTAR_OK && atou_error != MPTAR_ERR_OVERFLOW) return 0;

    if (is_negative) {
        mptar_uint64 max_negative_abs = (mptar_uint64)MPTAR_INT64_MAX + 1ULL;

        if (atou_error == MPTAR_ERR_OVERFLOW || u_val >= max_negative_abs) {
            return MPTAR_INT64_MIN;
        }

        return -(mptar_int64)u_val;
    } else {
        if (atou_error == MPTAR_ERR_OVERFLOW || u_val > (mptar_uint64)MPTAR_INT64_MAX) {
            return MPTAR_INT64_MAX;
        }

        return (mptar_int64)u_val;
    }
}

#else

extern mptar_int64 mptar_atoi64(const char* str, mptar_size_t len, int* err);

#endif

#ifndef MPTAR_CUSTOM_CONSUME_STREAM
static mptar_size_t mptar_consume_stream(mptar_reader* reader, mptar_size_t count) {
    if (count == 0) return 0;

    char discard_buf[128];
    mptar_size_t total_consumed = 0;

    while (total_consumed < count) {
        mptar_size_t remaining = count - total_consumed;
        mptar_size_t chunk = (remaining > sizeof(discard_buf)) ? sizeof(discard_buf) : remaining;

        mptar_size_t bytes_read = reader->read(reader->read_user_data, discard_buf, chunk);
        if (bytes_read == 0) {
            break;
        }

        total_consumed += bytes_read;
        reader->offset += bytes_read;
    }

    return total_consumed;
}
#else
extern mptar_size_t mptar_consume_stream(mptar_reader* reader, mptar_size_t count);
#endif

#ifndef MPTAR_CUSTOM_ALIGN_STREAM_BY_DISCARD
static int mptar_align_stream_by_discard(mptar_reader* reader) {
    if (reader->bytes_left != 0) return MPTAR_OK;

    mptar_size_t remainder = reader->offset % MPTAR_BLOCK_SIZE;
    if (remainder > 0) {
        mptar_size_t padding_needed = MPTAR_BLOCK_SIZE - remainder;
        mptar_size_t skipped = mptar_consume_stream(reader, padding_needed);

        if (skipped < padding_needed) {
            return MPTAR_ERR_IO_READ;
        }
    }
    return MPTAR_OK;
}
#else
extern int mptar_align_stream_by_discard(mptar_reader* reader);
#endif

#ifndef MPTAR_CUSTOM_ALIGN_STREAM_BY_SKIP
static int mptar_align_stream_by_skip(mptar_reader* reader) {
    if (reader->bytes_left != 0) return MPTAR_OK;

    mptar_size_t remainder = reader->offset % MPTAR_BLOCK_SIZE;
    if (remainder > 0) {
        mptar_size_t padding_needed = MPTAR_BLOCK_SIZE - remainder;
        reader->offset += padding_needed;
    }
    return MPTAR_OK;
}
#else
extern int mptar_align_stream_by_skip(mptar_reader* reader);
#endif

#ifndef MPTAR_CUSTOM_PARSE_OCTAL_FIELD
static mptar_uint64 mptar_parse_octal_field(const char* str, mptar_size_t str_size, int* err) {    
    if (str == MPTAR_NULL || str_size == 0) {
        if(err) *err = MPTAR_ERR_INVALID_ARG;
        return 0;
    }

    const unsigned char* bytes = (const unsigned char*)str;

    if((bytes[0] & MPTAR_OCTAL_BINARY_FLAG) != 0){
        mptar_uint64 result = 0;
        result = bytes[0] & 0x7F;

        mptar_size_t i = 1;
        for ( ; i < str_size; i++)
        {
            if (result > (MPTAR_UINT64_MAX >> 8)) {
                if (err) *err = MPTAR_ERR_OVERFLOW;
                return MPTAR_UINT64_MAX;
            }

            result = (result << 8) | bytes[i];
        }

        if (err) *err = MPTAR_OK;
        return result;
    }

    mptar_uint64 result = 0;
    mptar_size_t i = 0;

    while (i < str_size && (bytes[i] == ' ' || bytes[i] == '0')) {
        i++;
    }

    while (i < str_size) {
        unsigned char c = bytes[i];

        if (c == '\0' || c == ' ') {
            break;
        }

        if (c < '0' || c > '7') {
            if(err) *err = MPTAR_ERR_MALFORMED;
            return result; 
        }

        if (result > (MPTAR_UINT64_MAX >> 3)) {
            if(err) *err = MPTAR_ERR_OVERFLOW;
            return MPTAR_UINT64_MAX; 
        }

        result = (result << 3) + (c - '0');
        i++;
    }

    while (i < str_size) {
        unsigned char c = bytes[i];
        if (c != '\0' && c != ' ') {
            if(err) *err = MPTAR_ERR_MALFORMED;
            return result; 
        }
        i++;
    }

    if(err) *err = MPTAR_OK;
    return result;
}
#else
extern mptar_uint64 mptar_parse_octal_field(const char* str, mptar_size_t str_size, int* err);
#endif

#ifndef MPTAR_CUSTOM_VERIFY_HEADER_CHECKSUM
static int mptar_verify_header_checksum(const mptar_header* header) {
    if(header == MPTAR_NULL) return MPTAR_ERR_INVALID_ARG;

    const unsigned char* bytes = (const unsigned char*)header;

    mptar_uint32 stored_checksum = (mptar_uint32)mptar_parse_octal_field(header->checksum, sizeof(header->checksum), MPTAR_NULL);
    if (header->typeflag == '\0' && stored_checksum == 0 && header->name[0] == '\0') {
        static const char zero_magic_version[sizeof(header->magic) + sizeof(header->version)] = {0};
        if (mptar_memcmp(header->magic, zero_magic_version, sizeof(header->magic) + sizeof(header->version)) == 0) {
            return MPTAR_EOF;
        }
    }

    mptar_uint32 unsigned_sum = 0;
    mptar_int32 signed_sum = 0;

    mptar_uint16 i = 0;
    for ( ; i < MPTAR_CHECKSUM_OFFSET; i++) {
        unsigned_sum += bytes[i];
        signed_sum += (signed char)bytes[i];
    }

    /* Hardcoded constant value for the 8 space characters (8 * 32) */
    unsigned_sum += MPTAR_SPACE_CHECKSUM_VAL; 
    signed_sum += MPTAR_SPACE_CHECKSUM_VAL;

    for (i = MPTAR_CHECKSUM_END_OFFSET; i < MPTAR_USTAR_HEADER_SIZE; i++) {
        unsigned_sum += bytes[i];
        signed_sum += (signed char)bytes[i];
    }

    if (unsigned_sum == stored_checksum || (mptar_uint32)signed_sum == stored_checksum) {
        return MPTAR_OK;
    }

    return MPTAR_ERR_CHECKSUM;
}
#else
extern int mptar_verify_header_checksum(const mptar_header* header);
#endif

#ifndef MPTAR_CUSTOM_PARSE_PAX_TIME
static int mptar_parse_pax_time(const char* val_str, mptar_size_t val_len, mptar_opt_time* out_time) {
    if (val_str == MPTAR_NULL || out_time == MPTAR_NULL || val_len == 0) {
        return MPTAR_ERR_INVALID_ARG;
    }

    const unsigned char* bytes = (const unsigned char*)val_str;
    mptar_size_t dot_i = 0;
    while (dot_i < val_len && bytes[dot_i] != '.') {
        dot_i++;
    }

    int status = MPTAR_OK;
    out_time->value.sec = mptar_atoi64(val_str, dot_i, &status);
    out_time->value.nsec = 0;
    out_time->has_value = true;

    if (dot_i < val_len && bytes[dot_i] == '.') {
        mptar_size_t frac_start = dot_i + 1;
        mptar_uint32 multiplier = MPTAR_PAX_NSEC_INITIAL_MULTIPLIER;
        mptar_uint32 nsec = 0;
        mptar_size_t i = frac_start;

        while (i < val_len && (i - frac_start) < MPTAR_PAX_NSEC_MAX_DIGITS) {
            unsigned char c = bytes[i];
            if (c < '0' || c > '9') {
                status = (status == MPTAR_OK) ? MPTAR_ERR_MALFORMED : status;
                out_time->value.nsec = nsec;
                return status;
            }

            nsec += (mptar_uint32)(c - '0') * multiplier;
            multiplier /= 10;
            i++;
        }

        while (i < val_len) {
            unsigned char c = bytes[i];
            if (c < '0' || c > '9') {
                status = (status == MPTAR_OK) ? MPTAR_ERR_MALFORMED : status;
                out_time->value.nsec = nsec;
                return status;
            }
            i++;
        }

        out_time->value.nsec = nsec;
    }

    return status;
}
#else
extern int mptar_parse_pax_time(const char* val_str, mptar_size_t val_len, mptar_opt_time* out_time);
#endif

#ifndef MPTAR_CUSTOM_RECONSTRUCT_USTAR_PATH
static int mptar_reconstruct_ustar_path(mptar_reader* reader, const mptar_header* header, mptar_metadata* out_meta) {
    if(reader == MPTAR_NULL || header == MPTAR_NULL || out_meta == MPTAR_NULL) return MPTAR_ERR_INVALID_ARG;
    out_meta->path = MPTAR_NULL;

    mptar_size_t name_len = mptar_strnlen(header->name, sizeof(header->name));
    mptar_size_t prefix_len = mptar_strnlen(header->prefix, sizeof(header->prefix));

    if (prefix_len > 0) {
        mptar_size_t full_len = prefix_len + 1 + name_len; /* 1 for a forward slash that joins the prefix and name */
        char* allocated_path = (char*)reader->memory.alloc(reader->memory.alloc_user_data, full_len + 1);
        if (!allocated_path) return MPTAR_ERR_ALLOC;

        mptar_memcpy(allocated_path, header->prefix, prefix_len);
        allocated_path[prefix_len] = '/';
        mptar_memcpy(allocated_path + prefix_len + 1, header->name, name_len);
        allocated_path[full_len] = '\0';
        out_meta->path = allocated_path;
    } else if (name_len > 0) {
        char* allocated_path = (char*)reader->memory.alloc(reader->memory.alloc_user_data, name_len + 1);
        if (!allocated_path) return MPTAR_ERR_ALLOC;

        mptar_memcpy(allocated_path, header->name, name_len);
        allocated_path[name_len] = '\0';
        out_meta->path = allocated_path;
    }else{
        return MPTAR_ERR_MALFORMED;
    }

    return MPTAR_OK;
}
#else
extern int mptar_reconstruct_ustar_path(mptar_reader* reader, const mptar_header* header, mptar_metadata* out_meta);
#endif

#ifndef MPTAR_CUSTOM_ALLOC_PAX_STRING
static const char* mptar_alloc_pax_string(mptar_memory_cfg* mem, const char* src, mptar_size_t len) {
    if (mem == MPTAR_NULL || src == MPTAR_NULL || len >= (mptar_size_t)-1) {
        return MPTAR_NULL;
    }

    char* dst = (char*)mem->alloc(mem->alloc_user_data, len + 1);
    if (!dst) return MPTAR_NULL;

    mptar_memcpy(dst, src, len);
    dst[len] = '\0';

    return dst;
}
#else
extern const char* mptar_alloc_pax_string(mptar_memory_cfg* mem, const char* src, mptar_size_t len);
#endif

#ifndef MPTAR_CUSTOM_APPLY_PAX_STRING
static void mptar_apply_pax_string(mptar_memory_cfg* mem, const char* val, mptar_size_t val_len, const char** out_str, mptar_uint32* pax_flags, mptar_uint32 flag) {
#ifdef MPTAR_PAX_LAST_WINS
    if (*out_str != MPTAR_NULL && mem->free) {
        mem->free(mem->alloc_user_data, (void*)*out_str);
        *out_str = MPTAR_NULL;
    }
#else
    if (*pax_flags & flag) return;
#endif

    const char* allocated = mptar_alloc_pax_string(mem, val, val_len);
    if (allocated != MPTAR_NULL) {
        *out_str = allocated;
        *pax_flags |= flag;
    } else {
        *pax_flags &= ~flag; 
    }
}
#else
extern void mptar_apply_pax_string(mptar_memory_cfg* mem, const char* val, mptar_size_t val_len, const char** out_str, mptar_uint32* pax_flags, mptar_uint32 flag);
#endif

#ifndef MPTAR_CUSTOM_APPLY_PAX_U64
static void mptar_apply_pax_u64(const char* val, mptar_size_t val_len, mptar_uint64* out_val, mptar_uint32* pax_flags, mptar_uint32 flag) {
#ifndef MPTAR_PAX_LAST_WINS
    if (*pax_flags & flag) return;
#endif

    int error = MPTAR_OK;
    mptar_uint64 parsed = mptar_atou64(val, val_len, &error);
    if (error == MPTAR_OK || error == MPTAR_ERR_OVERFLOW) {
        *out_val = parsed;
        *pax_flags |= flag;
    }
}
#else
extern void mptar_apply_pax_u64(const char* val, mptar_size_t val_len, mptar_uint64* out_val, mptar_uint32* pax_flags, mptar_uint32 flag);
#endif

#ifndef MPTAR_CUSTOM_APPLY_PAX_TIME
static void mptar_apply_pax_time(const char* val, mptar_size_t val_len, mptar_opt_time* out_time, mptar_uint32* pax_flags, mptar_uint32 flag) {
#ifndef MPTAR_PAX_LAST_WINS
    if (*pax_flags & flag) return;
#endif

    int error = mptar_parse_pax_time(val, val_len, out_time);
    if (error == MPTAR_OK || error == MPTAR_ERR_MALFORMED) {
        *pax_flags |= flag;
    }
}
#else
extern void mptar_apply_pax_time(const char* val, mptar_size_t val_len, mptar_opt_time* out_time, mptar_uint32* pax_flags, mptar_uint32 flag);
#endif

#ifndef MPTAR_CUSTOM_APPLY_PAX_KV
static void mptar_apply_pax_kv(mptar_reader* reader, const char* key, mptar_size_t key_len, 
    const char* val, mptar_size_t val_len, mptar_metadata* meta, mptar_uint32* pax_flags) {
    if (!reader || !key || !val || !meta || !pax_flags) {
        return;
    }

    if (key_len == 4 && mptar_memcmp(key, "path", 4) == 0) {
        mptar_apply_pax_string(&reader->memory, val, val_len, &meta->path, pax_flags, MPTAR_PAX_HAS_PATH);
    } 
    else if (key_len == 8 && mptar_memcmp(key, "linkpath", 8) == 0) {
        mptar_apply_pax_string(&reader->memory, val, val_len, &meta->link_target, pax_flags, MPTAR_PAX_HAS_LINK);
    }
    else if (key_len == 5 && mptar_memcmp(key, "uname", 5) == 0) {
        mptar_apply_pax_string(&reader->memory, val, val_len, &meta->uname, pax_flags, MPTAR_PAX_HAS_UNAME);
    }
    else if (key_len == 5 && mptar_memcmp(key, "gname", 5) == 0) {
        mptar_apply_pax_string(&reader->memory, val, val_len, &meta->gname, pax_flags, MPTAR_PAX_HAS_GNAME);
    }
    else if (key_len == 4 && mptar_memcmp(key, "size", 4) == 0) {
        mptar_apply_pax_u64(val, val_len, &meta->size, pax_flags, MPTAR_PAX_HAS_SIZE);
    } 
    else if (key_len == 3 && mptar_memcmp(key, "uid", 3) == 0) {
        mptar_apply_pax_u64(val, val_len, &meta->uid, pax_flags, MPTAR_PAX_HAS_UID);
    } 
    else if (key_len == 3 && mptar_memcmp(key, "gid", 3) == 0) {
        mptar_apply_pax_u64(val, val_len, &meta->gid, pax_flags, MPTAR_PAX_HAS_GID);
    }
    else if (key_len == 5 && mptar_memcmp(key, "mtime", 5) == 0) {
        mptar_apply_pax_time(val, val_len, &meta->mtime, pax_flags, MPTAR_PAX_HAS_MTIME);
    }
#ifdef MPTAR_SUPPORT_EXTRA_TIMES
    else if (key_len == 5 && mptar_memcmp(key, "atime", 5) == 0) {
        mptar_apply_pax_time(val, val_len, &meta->atime, pax_flags, MPTAR_PAX_HAS_ATIME);
    } 
    else if (key_len == 5 && mptar_memcmp(key, "ctime", 5) == 0) {
        mptar_apply_pax_time(val, val_len, &meta->ctime, pax_flags, MPTAR_PAX_HAS_CTIME);
    }
#endif
}
#else
extern void mptar_apply_pax_kv(mptar_reader* reader, const char* key, mptar_size_t key_len, 
    const char* val, mptar_size_t val_len, mptar_metadata* meta, mptar_uint32* pax_flags);
#endif

#ifndef MPTAR_CUSTOM_PARSE_PAX_BLOCK
static int mptar_parse_pax_block(mptar_reader* reader, mptar_uint64 total_pax_size, mptar_metadata* meta, mptar_uint32* pax_flags) {
    if (!reader || !meta || !pax_flags) return MPTAR_ERR_INVALID_ARG;
    if (total_pax_size == 0) return MPTAR_OK;

    mptar_size_t size_t_pax = (mptar_size_t)total_pax_size;
    if (size_t_pax != total_pax_size || size_t_pax > MPTAR_MAX_PAX_SIZE) {
        return MPTAR_ERR_OVERFLOW;
    }

    char* buffer = (char*)reader->memory.alloc(reader->memory.alloc_user_data, size_t_pax);
    if (!buffer) return MPTAR_ERR_ALLOC;

    if (reader->read(reader->read_user_data, buffer, size_t_pax) != size_t_pax) {
        reader->memory.free(reader->memory.alloc_user_data, buffer);
        return MPTAR_ERR_IO_READ;
    }
    reader->offset += size_t_pax;

    /* Reads remaining block padding */
    mptar_uint32 remainder = total_pax_size % MPTAR_BLOCK_SIZE;
    if (remainder > 0) {
        char dummy[MPTAR_USTAR_HEADER_SIZE];
        mptar_size_t pad = MPTAR_BLOCK_SIZE - (mptar_size_t)remainder;
        mptar_size_t read = reader->read(reader->read_user_data, dummy, pad);
        if (read != pad) {
            reader->memory.free(reader->memory.alloc_user_data, buffer);
            return MPTAR_ERR_IO_READ;
        }
        reader->offset += pad;
    }

    int res = MPTAR_OK;
    mptar_size_t i = 0;
    while (i < size_t_pax) {
        mptar_size_t len_start = i;
        while (i < size_t_pax && buffer[i] != ' ') {
            i++;
        }

        if (i >= size_t_pax) {
            res = MPTAR_ERR_MALFORMED;
            goto error_cleanup;
        }

        mptar_uint64 record_len = mptar_atou64(&buffer[len_start], i - len_start, &res);
        if (res != MPTAR_OK) {
            goto error_cleanup;
        } else if (record_len > (mptar_uint64)size_t_pax || record_len == 0) {
            res = MPTAR_ERR_MALFORMED;
            goto error_cleanup;
        }

        i++; /* Advances past ' ' */

        mptar_size_t key_start = i;
        while (i < size_t_pax && buffer[i] != '=') {
            i++;
        }

        if (i >= size_t_pax) {
            res = MPTAR_ERR_MALFORMED;
            goto error_cleanup;
        }

        mptar_size_t key_len = i - key_start;
        i++; /* Advances past '=' */

        mptar_size_t val_start = i;
        mptar_size_t target_next_record = len_start + (mptar_size_t)record_len;

        if (target_next_record > size_t_pax || target_next_record <= val_start) {
            res = MPTAR_ERR_MALFORMED;
            goto error_cleanup;
        } else if (buffer[target_next_record - 1] != '\n') {
            res = MPTAR_ERR_MALFORMED;
            goto error_cleanup;
        }

        mptar_size_t val_len = target_next_record - val_start - 1;
        mptar_apply_pax_kv(reader, &buffer[key_start], key_len, &buffer[val_start], val_len, meta, pax_flags);

        i = target_next_record;
    }

    reader->memory.free(reader->memory.alloc_user_data, buffer);
    return MPTAR_OK;

error_cleanup:
    reader->memory.free(reader->memory.alloc_user_data, buffer);
    mptar_reader_free_metadata(reader, meta);
    return res;
}
#else
extern int mptar_parse_pax_block(mptar_reader* reader, mptar_uint64 total_pax_size, mptar_metadata* meta, mptar_uint32* pax_flags);
#endif

#ifndef MPTAR_CUSTOM_PARSE_USTAR_HEADER
static int mptar_parse_ustar_header(const mptar_header* header, mptar_metadata* out_meta) {
    if (!header || !out_meta) return MPTAR_ERR_INVALID_ARG;

    int error = MPTAR_OK;

    out_meta->size = mptar_parse_octal_field(header->size, sizeof(header->size), &error);
    if (error != MPTAR_OK) return error;

    out_meta->typeflag = header->typeflag;

    error = MPTAR_OK;
    out_meta->mode = (mptar_uint32)mptar_parse_octal_field(header->mode, sizeof(header->mode), &error);
    if (error != MPTAR_OK) {
        out_meta->mode = (header->typeflag == '5') ? MPTAR_MODE_DIR : MPTAR_MODE_REG;
    }

    error = MPTAR_OK;
    out_meta->uid = mptar_parse_octal_field(header->uid, sizeof(header->uid), &error);
    if (error != MPTAR_OK) {
        out_meta->uid = 0;
    }

    error = MPTAR_OK;
    out_meta->gid = mptar_parse_octal_field(header->gid, sizeof(header->gid), &error);
    if (error != MPTAR_OK) {
        out_meta->gid = 0;
    }

    error = MPTAR_OK;
    out_meta->mtime.value.sec = (mptar_int64)mptar_parse_octal_field(header->mtime, sizeof(header->mtime), &error);
    out_meta->mtime.value.nsec = 0;
    if (error != MPTAR_OK) {
        out_meta->mtime.value.sec = 0;
    }
    out_meta->mtime.has_value = true;

#ifdef MPTAR_SUPPORT_EXTRA_TIMES
    out_meta->atime.has_value = false;
    out_meta->ctime.has_value = false;
#endif

#ifdef MPTAR_SUPPORT_SPECIAL
    error = MPTAR_OK;
    out_meta->devmajor = (mptar_uint32)mptar_parse_octal_field(header->devmajor, sizeof(header->devmajor), &error);
    if (error != MPTAR_OK) {
        out_meta->devmajor = 0;
    }

    error = MPTAR_OK;
    out_meta->devminor = (mptar_uint32)mptar_parse_octal_field(header->devminor, sizeof(header->devminor), &error);
    if (error != MPTAR_OK) {
        out_meta->devminor = 0;
    }
#endif

    return MPTAR_OK;
}
#else
extern int mptar_parse_ustar_header(const mptar_header* header, mptar_metadata* out_meta);
#endif

int mptar_read_header(mptar_reader *reader, mptar_metadata *out_meta) {
    if (!reader || !out_meta) return MPTAR_ERR_INVALID_ARG;

    out_meta->path = MPTAR_NULL;
    out_meta->link_target = MPTAR_NULL;
    out_meta->uname = MPTAR_NULL;
    out_meta->gname = MPTAR_NULL;
    out_meta->internal_alloc = true;

    mptar_uint32 pax_flags = 0;
    char header_bytes[MPTAR_USTAR_HEADER_SIZE];
    int res = MPTAR_OK;

    while (1) {
        if (reader->read(reader->read_user_data, header_bytes, MPTAR_USTAR_HEADER_SIZE) != MPTAR_USTAR_HEADER_SIZE) {
            res = MPTAR_ERR_IO_READ;
            goto error_cleanup;
        }

        reader->offset += MPTAR_USTAR_HEADER_SIZE;

        mptar_header* header = (mptar_header*)header_bytes;
        res = mptar_verify_header_checksum(header);
        if (res != MPTAR_OK) {
            goto error_cleanup;
        }

        if (header->typeflag == 'x') {
            res = MPTAR_OK;
            mptar_uint64 pax_size = mptar_parse_octal_field(header->size, sizeof(header->size), &res);
            if (res != MPTAR_OK) {
                goto error_cleanup;
            }

            res = mptar_parse_pax_block(reader, pax_size, out_meta, &pax_flags);
            if (res != MPTAR_OK) {
                goto error_cleanup;
            }

            continue;
        }

        if (header->typeflag != '\0' && (header->typeflag < '0' || header->typeflag > '7')) {
            res = MPTAR_OK;
            mptar_uint64 ext_size = mptar_parse_octal_field(header->size, sizeof(header->size), &res);

            if (res != MPTAR_OK) {
                /* We assume the extension we are trying to skip has no payload (size = 0)
                   We can skip it because we already loaded the 512B of the extension
                   The specification states if size is unsed the extension has no payload. */
                ext_size = 0;
                continue;
            }

            mptar_size_t remainder = (mptar_size_t)(ext_size % MPTAR_BLOCK_SIZE);
            mptar_size_t total_skip = (mptar_size_t)ext_size + ((remainder > 0) ? (MPTAR_BLOCK_SIZE - remainder) : 0);                        
            if (total_skip > 0) {
                mptar_size_t skipped = mptar_consume_stream(reader, total_skip);        
                if (skipped != total_skip) {
                    res = MPTAR_ERR_IO_READ;
                    goto error_cleanup;
                }
            }

            continue;
        }

        mptar_metadata pax_staged = *out_meta;
        res = mptar_parse_ustar_header(header, out_meta);
        if (res != MPTAR_OK) {
            goto error_cleanup;
        }

        if (pax_flags & MPTAR_PAX_HAS_SIZE)  { out_meta->size = pax_staged.size; }
        if (pax_flags & MPTAR_PAX_HAS_UID)   { out_meta->uid  = pax_staged.uid;  }
        if (pax_flags & MPTAR_PAX_HAS_GID)   { out_meta->gid  = pax_staged.gid;  }
        if (pax_flags & MPTAR_PAX_HAS_MTIME) { 
            out_meta->mtime = pax_staged.mtime; 
        }

#ifdef MPTAR_SUPPORT_EXTRA_TIMES
        if (pax_flags & MPTAR_PAX_HAS_ATIME) { out_meta->atime = pax_staged.atime; }
        if (pax_flags & MPTAR_PAX_HAS_CTIME) { out_meta->ctime = pax_staged.ctime; }
#endif

        if (pax_flags & MPTAR_PAX_HAS_PATH) {
            out_meta->path = pax_staged.path;
        } else {
            res = mptar_reconstruct_ustar_path(reader, header, out_meta);
            if (res != MPTAR_OK) {
                goto error_cleanup;
            }
        }

        if (pax_flags & MPTAR_PAX_HAS_LINK) {
            out_meta->link_target = pax_staged.link_target;
        } else {
            mptar_size_t link_len = mptar_strnlen(header->linkname, sizeof(header->linkname));
            if (link_len > 0) {
                char* allocated_link = (char*)reader->memory.alloc(reader->memory.alloc_user_data, link_len + 1);
                if (!allocated_link) {
                    res = MPTAR_ERR_ALLOC;
                    goto error_cleanup;
                }
                mptar_memcpy(allocated_link, header->linkname, link_len);
                allocated_link[link_len] = '\0';
                out_meta->link_target = allocated_link;
            }else{
                out_meta->uname = MPTAR_NULL;
            }            
        }

        if (pax_flags & MPTAR_PAX_HAS_UNAME) {
            out_meta->uname = pax_staged.uname;
        } else {
            mptar_size_t uname_len = mptar_strnlen(header->uname, sizeof(header->uname));
            if (uname_len > 0) {
                char* allocated_uname = (char*)reader->memory.alloc(reader->memory.alloc_user_data, uname_len + 1);
                if (!allocated_uname) {
                    res = MPTAR_ERR_ALLOC;
                    goto error_cleanup;
                }
                mptar_memcpy(allocated_uname, header->uname, uname_len);
                allocated_uname[uname_len] = '\0';
                out_meta->uname = allocated_uname;
            } else {
                out_meta->uname = MPTAR_NULL;
            }
        }

        if (pax_flags & MPTAR_PAX_HAS_GNAME) {
            out_meta->gname = pax_staged.gname;
        } else {
            mptar_size_t gname_len = mptar_strnlen(header->gname, sizeof(header->gname));
            if (gname_len > 0) {
                char* allocated_gname = (char*)reader->memory.alloc(reader->memory.alloc_user_data, gname_len + 1);
                if (!allocated_gname) {
                    res = MPTAR_ERR_ALLOC;
                    goto error_cleanup;
                }
                mptar_memcpy(allocated_gname, header->gname, gname_len);
                allocated_gname[gname_len] = '\0';
                out_meta->gname = allocated_gname;
            } else {
                out_meta->gname = MPTAR_NULL;
            }
        }

        reader->bytes_left = out_meta->size;
        break;
    }

    return MPTAR_OK;

error_cleanup:
    mptar_reader_free_metadata(reader, out_meta);
    return res;
}

mptar_size_t mptar_read_data_chunk(mptar_reader* reader, void* buffer, mptar_size_t size, int *out_err) {
    if (out_err) *out_err = MPTAR_OK;

    if (!reader || !buffer) {
        if (out_err) *out_err = MPTAR_ERR_INVALID_ARG;
        return 0;
    }

    int align_err = mptar_align_stream_by_discard(reader);
    if (align_err != MPTAR_OK) {
        if (out_err) *out_err = align_err;
        return 0;
    }

    if (size == 0 || reader->bytes_left == 0) return 0;

    mptar_size_t bytes_to_read = size;
    if (bytes_to_read > reader->bytes_left) {
        bytes_to_read = reader->bytes_left;
    }

    mptar_size_t bytes_read = reader->read(reader->read_user_data, buffer, bytes_to_read);
    if (bytes_read == 0 || bytes_read > bytes_to_read) {
        if (out_err) *out_err = MPTAR_ERR_IO_READ;
        return 0;
    }

    reader->bytes_left -= bytes_read;
    reader->offset += bytes_read;

    align_err = mptar_align_stream_by_discard(reader);
    if (align_err != MPTAR_OK && out_err) {
        *out_err = align_err;
    }

    return bytes_read;
}

int mptar_discard_data(mptar_reader* reader) {
    if (!reader) {
        return MPTAR_ERR_INVALID_ARG;
    }

    if (reader->bytes_left > 0) {
        mptar_size_t skipped = mptar_consume_stream(reader, reader->bytes_left);        
        reader->bytes_left -= skipped;

        if (skipped < reader->bytes_left + skipped) {
            return MPTAR_ERR_IO_READ; 
        }
    }

    int align_err = mptar_align_stream_by_discard(reader);
    if (align_err != MPTAR_OK) {
        return align_err;
    }

    return MPTAR_OK;
}

int mptar_skip_data(mptar_reader* reader) {
    if (!reader) {
        return MPTAR_ERR_INVALID_ARG;
    }

    if (reader->bytes_left > 0) {
        mptar_uint64 bytes_to_skip = reader->bytes_left;
        reader->offset += bytes_to_skip;
        reader->bytes_left = 0;   
    }

    mptar_align_stream_by_skip(reader);

    return MPTAR_OK;
}

void mptar_reader_free_metadata(mptar_reader *reader, mptar_metadata *meta)
{
    if (!reader || !meta || !meta->internal_alloc) return;

MPTAR_SUPPRESS_WARNING_CAST_QUAL_BEGIN /* Reason: metadata strings in reading api are allocated dynamically. */
    if (meta->path) {
        reader->memory.free(reader->memory.alloc_user_data, (void*)meta->path);
        meta->path = MPTAR_NULL;
    }
    if (meta->link_target) {
        reader->memory.free(reader->memory.alloc_user_data, (void*)meta->link_target);
        meta->link_target = MPTAR_NULL;
    }
    if (meta->uname) {
        reader->memory.free(reader->memory.alloc_user_data, (void*)meta->uname);
        meta->uname = MPTAR_NULL;
    }
    if (meta->gname) {
        reader->memory.free(reader->memory.alloc_user_data, (void*)meta->gname);
        meta->gname = MPTAR_NULL;
    }

    meta->internal_alloc = false;

MPTAR_SUPPRESS_WARNING_CAST_QUAL_END
}

#endif /* MPTAR_NO_READ */

Updated on 2026-07-23 at 20:07:34 +0000