The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "b_buffer.h"

b_buffer *b_buffer_new(size_t factor) {
    b_buffer *buf;

    if ((buf = malloc(sizeof(*buf))) == NULL) {
        goto error_malloc;
    }

    buf->fd     = 0;
    buf->size   = factor? factor * B_BUFFER_BLOCK_SIZE: B_BUFFER_DEFAULT_FACTOR * B_BUFFER_BLOCK_SIZE;
    buf->unused = buf->size;

    if ((buf->data = malloc(buf->size)) == NULL) {
        goto error_malloc_buf;
    }

    memset(buf->data, 0x00, buf->size);

    return buf;

error_malloc_buf:
    buf->data = NULL;
    buf->fd   = 0;
    buf->size = 0;

    free(buf);

error_malloc:
    return NULL;
}

int b_buffer_get_fd(b_buffer *buf) {
    if (buf == NULL) return 0;

    return buf->fd;
}

void b_buffer_set_fd(b_buffer *buf, int fd) {
    if (buf == NULL) return;

    buf->fd = fd;
}

size_t b_buffer_size(b_buffer *buf) {
    if (buf == NULL) return 0;

    return buf->size;
}

size_t b_buffer_unused(b_buffer *buf) {
    if (buf == NULL) return 0;

    return buf->unused;
}

int b_buffer_full(b_buffer *buf) {
    if (buf == NULL) return 0;

    return buf->unused == 0;
}

static inline size_t padded_size(size_t size) {
    if (size % B_BUFFER_BLOCK_SIZE == 0) {
        return size;
    }

    return size + (B_BUFFER_BLOCK_SIZE - (size % B_BUFFER_BLOCK_SIZE));
}

ssize_t b_buffer_reclaim(b_buffer *buf, size_t used, size_t given) {
    size_t padded_len = padded_size(used);
    ssize_t amount;

    if (buf == NULL || given == 0 || given % B_BUFFER_BLOCK_SIZE) {
        errno = EINVAL;
        return -1;
    }

    amount = given - padded_len;

    buf->unused += amount;

    return amount;
}

void *b_buffer_get_block(b_buffer *buf, size_t len, ssize_t *given) {
    size_t offset;
    size_t padded_len = padded_size(len);

    if (buf == NULL) {
        errno = EINVAL;
        goto error;
    }

    if (len == 0) return NULL;

    if (buf->fd == 0) {
        errno = EBADF;
        goto error;
    }

    /*
     * If the buffer is full prior to allocating a block, then flush the buffer
     * to make room for a new block.
     */
    if (b_buffer_full(buf)) {
        if (b_buffer_flush(buf) < 0) {
            goto error;
        }
    }

    /*
     * Complain if the buffer block request calls for more buffer space than is
     * available.
     */
    if (padded_len > buf->unused) {
        errno = EFBIG;
        goto error;
    }

    /*
     * Determine the physical location of the buffer block to return to the
     * caller.
     */
    offset = buf->size - buf->unused;

    /*
     * Update the buffer to indicate that less fill data is unused.
     */
    buf->unused -= padded_len;

    /*
     * Update the 'given' parameter to indicate how much data was given.
     */
    if (given) {
        *given = padded_len;
    }

    return buf->data + offset;

error:
    if (given) {
        *given = -1;
    }

    return NULL;
}

ssize_t b_buffer_flush(b_buffer *buf) {
    ssize_t ret;

    if (buf == NULL || buf->data == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (buf->fd == 0) {
        errno = EBADF;
        return -1;
    }

    if (buf->size == 0)           return 0;
    if (buf->unused == buf->size) return 0;

    if ((ret = write(buf->fd, buf->data, buf->size)) < 0) {
        return ret;
    }

    memset(buf->data, 0x00, buf->size);

    buf->unused = buf->size;

    return ret;
}

void b_buffer_reset(b_buffer *buf) {
    if (buf == NULL) return;

    buf->fd     = 0;
    buf->unused = buf->size;

    if (buf->data == NULL) return;

    memset(buf->data, 0x00, buf->size);
}

void b_buffer_destroy(b_buffer *buf) {
    if (buf == NULL) return;

    if (buf->data) {
        free(buf->data);
        buf->data = NULL;
    }

    buf->fd     = 0;
    buf->size   = 0;
    buf->unused = 0;

    free(buf);
}