view buffer.c @ 849:1c4d7ff0a682

buffer: add buffer_insert & buffer_insert_c Like appending, but at any offset - not just at the end of existing data. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Thu, 16 Sep 2021 22:39:12 -0400
parents 3fa2ebce953c
children 3a53836f2274
line wrap: on
line source

/*
 * Copyright (c) 2017-2021 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdbool.h>

#include <jeffpc/int.h>

#include "buffer_impl.h"

struct buffer *buffer_alloc(size_t expected_size)
{
	struct buffer *buffer;
	int ret;

	buffer = malloc(sizeof(struct buffer));
	if (!buffer)
		return ERR_PTR(-ENOMEM);

	ret = buffer_init_heap(buffer, expected_size);
	if (ret) {
		free(buffer);
		return ERR_PTR(ret);
	}

	buffer->heap = true;

	return buffer;
}

void buffer_free(struct buffer *buffer)
{
	if (!buffer)
		return;

	if (buffer->ops->free)
		buffer->ops->free(buffer->data);

	if (buffer->heap)
		free(buffer);
}

int buffer_init_heap(struct buffer *buffer, size_t expected_size)
{
	buffer->data = malloc(expected_size ? expected_size : 1);
	if (!buffer->data)
		return -ENOMEM;

	buffer->off = 0;
	buffer->size = 0;
	buffer->allocsize = expected_size;
	buffer->ops = &heap_buffer;
	buffer->heap = false;

	return 0;
}

void buffer_init_sink(struct buffer *buffer)
{
	buffer->data = NULL;
	buffer->off = 0;
	buffer->size = 0;
	buffer->allocsize = SIZE_MAX;
	buffer->ops = &sink_buffer;
	buffer->heap = false;
}

void buffer_init_static(struct buffer *buffer, const void *data, size_t size,
			size_t bufsize, bool writable)
{
	ASSERT3U(size, <=, bufsize);

	buffer->data = (void *) data;
	buffer->off = 0;
	buffer->size = size;
	buffer->allocsize = bufsize;
	buffer->ops = writable ? &static_buffer_rw : &static_buffer_ro;
	buffer->heap = false;
}

static int resize(struct buffer *buffer, size_t newsize)
{
	void *tmp;

	if (newsize <= buffer->allocsize)
		return 0;

	if (!buffer->ops->realloc)
		return -ENOTSUP;

	tmp = buffer->ops->realloc(buffer->data, newsize);
	if (!tmp)
		return -ENOMEM;

	buffer->data = tmp;
	buffer->allocsize = newsize;

	return 0;
}

int buffer_insert(struct buffer *buffer, const void *data, size_t size,
		  size_t off)
{
	int ret;

	if (!buffer)
		return -EINVAL;

	if (!data && size)
		return -EINVAL;

	if (buffer->ops->check_insert) {
		ret = buffer->ops->check_insert(buffer, data, size, off);
		if (ret)
			return ret;
	}

	if (off > buffer->size)
		return -EINVAL; /* can't insert past EOF */

	if (!size)
		return 0; /* insert(..., 0, ...) is a no-op */

	ret = resize(buffer, buffer->size + size);
	if (ret)
		return ret;

	if (off < buffer->size)
		buffer->ops->move(buffer, off + size, off, buffer->size - off);

	buffer->ops->copyin(buffer, off, data, size);

	buffer->size += size;

	return 0;
}

int buffer_copy(struct buffer *dst, struct buffer *src, size_t len)
{
	/*
	 * TODO: we could make this more efficient by growing the
	 * destination buffer only once
	 */

	while (len) {
		uint8_t tmp[4096];
		ssize_t ret;

		/* read in a bunch */
		ret = buffer_read(src, tmp, MIN(sizeof(tmp), len));
		if (ret < 0)
			return ret;

		/* decrement now, so we can reuse ret below */
		len -= ret;

		/* and append whatever we read */
		ret = buffer_append(dst, tmp, ret);
		if (ret)
			return ret;
	}

	return 0;
}

ssize_t buffer_seek(struct buffer *buffer, off_t offset, int whence)
{
	size_t newoff;

	if (!buffer)
		return -EINVAL;

	switch (whence) {
		case SEEK_SET:
			if (offset < 0)
				return -EINVAL;
			if (offset > buffer->size)
				return -EINVAL;

			newoff = offset;
			break;
		case SEEK_CUR:
			if ((offset > 0) &&
			    (offset > (buffer->size - buffer->off)))
				return -EINVAL;
			if ((offset < 0) && (-offset > buffer->off))
				return -EINVAL;

			newoff = buffer->off + offset;
			break;
		case SEEK_END:
			if (offset > 0)
				return -EINVAL;
			if (-offset > buffer->size)
				return -EINVAL;

			newoff = buffer->size + offset;
			break;
		default:
			return -EINVAL;
	}

	if (newoff > SSIZE_MAX)
		return -EOVERFLOW;

	if (buffer->ops->check_seek) {
		ssize_t ret;

		ret = buffer->ops->check_seek(buffer, offset, whence, newoff);
		if (ret)
			return ret;
	}

	buffer->off = newoff;

	return newoff;
}

int buffer_truncate(struct buffer *buffer, size_t size)
{
	int ret;

	if (!buffer)
		return -EINVAL;

	if (buffer->ops->check_truncate) {
		ret = buffer->ops->check_truncate(buffer, size);
		if (ret)
			return ret;
	}

	ret = resize(buffer, size);
	if (ret)
		return ret;

	/* clear any expansion */
	if (buffer->size < size)
		buffer->ops->clear(buffer, buffer->size, size - buffer->size);

	buffer->size = size;
	buffer->off = MIN(buffer->off, size);

	return 0;
}

ssize_t buffer_pread(struct buffer *buffer, void *buf, size_t len, size_t off)
{
	ssize_t ret;

	if (!buffer || !buf)
		return -EINVAL;

	if (buffer->ops->check_read) {
		ret = buffer->ops->check_read(buffer, buf, len, off);
		if (ret)
			return ret;
	}

	if (off >= buffer->size)
		ret = 0;
	else if ((off + len) > buffer->size)
		ret = buffer->size - off;
	else
		ret = len;

	if (ret)
		buffer->ops->copyout(buffer, off, buf, ret);

	return ret;
}

ssize_t buffer_pwrite(struct buffer *buffer, const void *buf, size_t len,
		      size_t off)
{
	ssize_t ret;

	if (!buffer || !buf)
		return -EINVAL;

	if (buffer->ops->check_write) {
		ret = buffer->ops->check_write(buffer, buf, len, off);
		if (ret)
			return ret;
	}

	ret = resize(buffer, off + len);
	if (ret)
		return ret;

	/* clear any holes */
	if (buffer->size < off)
		buffer->ops->clear(buffer, buffer->size, off - buffer->size);

	buffer->ops->copyin(buffer, off, buf, len);

	buffer->size = MAX(buffer->size, off + len);

	return len;
}

/*
 * Generic implementations
 */

/* clear implementations */
void generic_buffer_clear_memset(struct buffer *buffer, size_t off, size_t len)
{
	memset(buffer->data + off, 0, len);
}

void generic_buffer_clear_nop(struct buffer *buffer, size_t off, size_t len)
{
}

void generic_buffer_clear_panic(struct buffer *buffer, size_t off, size_t len)
{
	panic("buffer clear called");
}

/* copyin implementations */
void generic_buffer_copyin_memcpy(struct buffer *buffer, size_t off,
				  const void *newdata, size_t newdatalen)
{
	memcpy(buffer->data + off, newdata, newdatalen);
}

void generic_buffer_copyin_nop(struct buffer *buffer, size_t off,
			       const void *newdata, size_t newdatalen)
{
}

void generic_buffer_copyin_panic(struct buffer *buffer, size_t off,
				 const void *newdata, size_t newdatalen)
{
	panic("buffer copyin called");
}

/* copyout implementations */
void generic_buffer_copyout_memcpy(struct buffer *buffer, size_t off,
				   void *data, size_t datalen)
{
	memcpy(data, buffer->data + off, datalen);
}

void generic_buffer_copyout_nop(struct buffer *buffer, size_t off, void *data,
				size_t datalen)
{
}

void generic_buffer_copyout_panic(struct buffer *buffer, size_t off, void *data,
				  size_t datalen)
{
	panic("buffer copyout called");
}

/* move implementations */
void generic_buffer_move_memmove(struct buffer *buffer, size_t dst,
				 size_t src, size_t len)
{
	memmove(buffer->data + dst, buffer->data + src, len);
}

void generic_buffer_move_nop(struct buffer *buffer, size_t dst,
			     size_t src, size_t len)
{
}

void generic_buffer_move_panic(struct buffer *buffer, size_t dst,
			       size_t src, size_t len)
{
	panic("buffer move called");
}