From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 7 Jan 2021 15:15:43 +0000 Subject: [PATCH] kern/buffer: Add variable sized heap buffer Add a new variable sized heap buffer type (grub_buffer_t) with simple operations for appending data, accessing the data and maintaining a read cursor. Signed-off-by: Chris Coulson Reviewed-by: Daniel Kiper --- grub-core/Makefile.core.def | 1 + grub-core/kern/buffer.c | 117 +++++++++++++++++++++++++++++++++++ include/grub/buffer.h | 144 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 grub-core/kern/buffer.c create mode 100644 include/grub/buffer.h diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 0eb7f312b29..6c25cf3e26a 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -112,6 +112,7 @@ kernel = { arm_efi_startup = kern/arm/efi/startup.S; arm64_efi_startup = kern/arm64/efi/startup.S; + common = kern/buffer.c; common = kern/command.c; common = kern/corecmd.c; common = kern/device.c; diff --git a/grub-core/kern/buffer.c b/grub-core/kern/buffer.c new file mode 100644 index 00000000000..9f5f8b86705 --- /dev/null +++ b/grub-core/kern/buffer.c @@ -0,0 +1,117 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2021 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +grub_buffer_t +grub_buffer_new (grub_size_t sz) +{ + struct grub_buffer *ret; + + ret = (struct grub_buffer *) grub_malloc (sizeof (*ret)); + if (ret == NULL) + return NULL; + + ret->data = (grub_uint8_t *) grub_malloc (sz); + if (ret->data == NULL) + { + grub_free (ret); + return NULL; + } + + ret->sz = sz; + ret->pos = 0; + ret->used = 0; + + return ret; +} + +void +grub_buffer_free (grub_buffer_t buf) +{ + grub_free (buf->data); + grub_free (buf); +} + +grub_err_t +grub_buffer_ensure_space (grub_buffer_t buf, grub_size_t req) +{ + grub_uint8_t *d; + grub_size_t newsz = 1; + + /* Is the current buffer size adequate? */ + if (buf->sz >= req) + return GRUB_ERR_NONE; + + /* Find the smallest power-of-2 size that satisfies the request. */ + while (newsz < req) + { + if (newsz == 0) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("requested buffer size is too large")); + newsz <<= 1; + } + + d = (grub_uint8_t *) grub_realloc (buf->data, newsz); + if (d == NULL) + return grub_errno; + + buf->data = d; + buf->sz = newsz; + + return GRUB_ERR_NONE; +} + +void * +grub_buffer_take_data (grub_buffer_t buf) +{ + void *data = buf->data; + + buf->data = NULL; + buf->sz = buf->pos = buf->used = 0; + + return data; +} + +void +grub_buffer_reset (grub_buffer_t buf) +{ + buf->pos = buf->used = 0; +} + +grub_err_t +grub_buffer_advance_read_pos (grub_buffer_t buf, grub_size_t n) +{ + grub_size_t newpos; + + if (grub_add (buf->pos, n, &newpos)) + return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + + if (newpos > buf->used) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("new read is position beyond the end of the written data")); + + buf->pos = newpos; + + return GRUB_ERR_NONE; +} diff --git a/include/grub/buffer.h b/include/grub/buffer.h new file mode 100644 index 00000000000..f4b10cf2810 --- /dev/null +++ b/include/grub/buffer.h @@ -0,0 +1,144 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2021 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_BUFFER_H +#define GRUB_BUFFER_H 1 + +#include +#include +#include +#include +#include + +struct grub_buffer +{ + grub_uint8_t *data; + grub_size_t sz; + grub_size_t pos; + grub_size_t used; +}; + +/* + * grub_buffer_t represents a simple variable sized byte buffer with + * read and write cursors. It currently only implements + * functionality required by the only user in GRUB (append byte[s], + * peeking data at a specified position and updating the read cursor. + * Some things that this doesn't do yet are: + * - Reading a portion of the buffer by copying data from the current + * read position in to a caller supplied destination buffer and then + * automatically updating the read cursor. + * - Dropping the read part at the start of the buffer when an append + * requires more space. + */ +typedef struct grub_buffer *grub_buffer_t; + +/* Allocate a new buffer with the specified initial size. */ +extern grub_buffer_t grub_buffer_new (grub_size_t sz); + +/* Free the buffer and its resources. */ +extern void grub_buffer_free (grub_buffer_t buf); + +/* Return the number of unread bytes in this buffer. */ +static inline grub_size_t +grub_buffer_get_unread_bytes (grub_buffer_t buf) +{ + return buf->used - buf->pos; +} + +/* + * Ensure that the buffer size is at least the requested + * number of bytes. + */ +extern grub_err_t grub_buffer_ensure_space (grub_buffer_t buf, grub_size_t req); + +/* + * Append the specified number of bytes from the supplied + * data to the buffer. + */ +static inline grub_err_t +grub_buffer_append_data (grub_buffer_t buf, const void *data, grub_size_t len) +{ + grub_size_t req; + + if (grub_add (buf->used, len, &req)) + return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + + if (grub_buffer_ensure_space (buf, req) != GRUB_ERR_NONE) + return grub_errno; + + grub_memcpy (&buf->data[buf->used], data, len); + buf->used = req; + + return GRUB_ERR_NONE; +} + +/* Append the supplied character to the buffer. */ +static inline grub_err_t +grub_buffer_append_char (grub_buffer_t buf, char c) +{ + return grub_buffer_append_data (buf, &c, 1); +} + +/* + * Forget and return the underlying data buffer. The caller + * becomes the owner of this buffer, and must free it when it + * is no longer required. + */ +extern void *grub_buffer_take_data (grub_buffer_t buf); + +/* Reset this buffer. Note that this does not deallocate any resources. */ +void grub_buffer_reset (grub_buffer_t buf); + +/* + * Return a pointer to the underlying data buffer at the specified + * offset from the current read position. Note that this pointer may + * become invalid if the buffer is mutated further. + */ +static inline void * +grub_buffer_peek_data_at (grub_buffer_t buf, grub_size_t off) +{ + if (grub_add (buf->pos, off, &off)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected.")); + return NULL; + } + + if (off >= buf->used) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("peek out of range")); + return NULL; + } + + return &buf->data[off]; +} + +/* + * Return a pointer to the underlying data buffer at the current + * read position. Note that this pointer may become invalid if the + * buffer is mutated further. + */ +static inline void * +grub_buffer_peek_data (grub_buffer_t buf) +{ + return grub_buffer_peek_data_at (buf, 0); +} + +/* Advance the read position by the specified number of bytes. */ +extern grub_err_t grub_buffer_advance_read_pos (grub_buffer_t buf, grub_size_t n); + +#endif /* GRUB_BUFFER_H */