All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
To: u-boot@lists.denx.de
Subject: [PATCH 01/49] Add support for an owned buffer
Date: Tue, 4 May 2021 08:59:40 +0200	[thread overview]
Message-ID: <adc8604f-f9b1-b055-2fa3-a392f905aa8e@prevas.dk> (raw)
In-Reply-To: <20210503171001.1.Iac016f2b531889277f63e4999494a967a823659b@changeid>

On 04/05/2021 01.10, Simon Glass wrote:
> When passing a data buffer back from a function, it is not always clear
> who owns the buffer, i.e. who is responsible for freeing the memory used.
> An example of this is where multiple files are decompressed from the
> firmware image, using a temporary buffer for reading (since the
> compressed data has to live somewhere) and producing a temporary or
> permanent buffer with the resuilts.
> 
> Where the firmware image can be memory-mapped, as on x86, the compressed
> data does not need to be buffered, but the complexity of having a buffer
> which is either allocated or not, makes the code hard to understand.
> 
> Introduce a new 'abuf' which supports simple buffer operations:
> 
> - encapsulating a buffer and its size
> - either allocated with malloc() or not
> - able to be reliably freed if necessary
> - able to be converted to an allocated buffer if needed
> 
> This simple API makes it easier to deal with allocated and memory-mapped
> buffers.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
> Changes in v2:
> - Add new abuf_init_set() function
> 
>  include/abuf.h    | 148 ++++++++++++++++++++++
>  lib/Makefile      |   1 +
>  lib/abuf.c        | 103 ++++++++++++++++
>  test/lib/Makefile |   1 +
>  test/lib/abuf.c   | 303 ++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 556 insertions(+)
>  create mode 100644 include/abuf.h
>  create mode 100644 lib/abuf.c
>  create mode 100644 test/lib/abuf.c
> 
> diff --git a/include/abuf.h b/include/abuf.h
> new file mode 100644
> index 00000000000..3b8f78348dd
> --- /dev/null
> +++ b/include/abuf.h
> @@ -0,0 +1,148 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Handles a buffer that can be allocated and freed
> + *
> + * Copyright 2021 Google LLC
> + * Written by Simon Glass <sjg@chromium.org>
> + */
> +
> +#ifndef __ABUF_H
> +#define __ABUF_H
> +
> +#include <linux/types.h>
> +
> +/**
> + * struct abuf - buffer that can be allocated and freed
> + *
> + * This is useful for a block of data which may be allocated with malloc(), or
> + * not, so that it needs to be freed correctly when finished with.
> + *
> + * For now it has a very simple purpose.
> + *
> + * Using memset() to zero all fields is guaranteed to be equivalent to
> + * abuf_init().
> + *
> + * @data: Pointer to data
> + * @size: Size of data in bytes
> + * @alloced: true if allocated with malloc(), so must be freed after use
> + */
> +struct abuf {
> +	void *data;
> +	size_t size;
> +	bool alloced;
> +};
> +
> +static inline void *abuf_data(struct abuf *abuf)

const struct abuf *abuf?

> +{
> +	return abuf->data;
> +}
> +
> +static inline size_t abuf_size(struct abuf *abuf)

ditto

> +{
> +	return abuf->size;
> +}
> +
> +
> +bool abuf_realloc(struct abuf *abuf, size_t new_size)
> +{
> +	void *ptr;
> +
> +	if (!new_size) {
> +		/* easy case, just need to uninit, freeing any allocation */
> +		abuf_uninit(abuf);
> +	} else if (abuf->alloced) {
> +		/* currently allocated, so need to reallocate */
> +		ptr = realloc(abuf->data, new_size);
> +		if (!ptr)
> +			return false;
> +		abuf->data = ptr;
> +		abuf->size = new_size;
> +	} else if (new_size <= abuf->size) {
> +		/*
> +		 * not currently alloced and new size is no larger. Just update
> +		 * it. Data is lost off the end if new_size < abuf->size
> +		 */
> +		abuf->size = new_size;
> +	} else {
> +		/* not currently allocated and new size is larger. Alloc and
> +		 * copy in data. The new space is not inited.
> +		 */
> +		ptr = malloc(new_size);
> +		if (!ptr)
> +			return false;
> +		memcpy(ptr, abuf->data, min(new_size, abuf->size));

You know new_size > abuf->size, no need for the min().

> +		abuf->data = ptr;
> +		abuf->size = new_size;
> +		abuf->alloced = true;
> +	}
> +
> +	return true;
> +}

I think this would be easier to read if the branches did an early
"return true;" when the case has been handled.

> +
> +void *abuf_uninit_move(struct abuf *abuf, size_t *sizep)
> +{
> +	void *ptr;
> +
> +	if (sizep)
> +		*sizep = abuf->size;
> +	if (!abuf->size)
> +		return NULL;
> +	if (abuf->alloced) {
> +		ptr = abuf->data;
> +	} else {
> +		ptr = malloc(abuf->size);
> +		if (!ptr)
> +			return NULL;
> +		memcpy(ptr, abuf->data, abuf->size);

Don't we have a memdup() function? Hm, no, only a kmemdup(). Might be
worth introducing at some point, quick grepping shows quite a few places
that could use that.

> +	}
> +	/* Clear everything out so there is no record of the data */
> +	abuf_init(abuf);
> +
> +	return ptr;
> +}
> +
> +void abuf_init_set(struct abuf *abuf, void *data, size_t size)
> +{
> +	abuf_init(abuf);
> +	abuf_set(abuf, data, size);
> +}
> +
> +void abuf_uninit(struct abuf *abuf)
> +{
> +	if (abuf->alloced)
> +		free(abuf->data);
> +	abuf_init(abuf);
> +}
> +
> +void abuf_init(struct abuf *abuf)
> +{
> +	abuf->data = NULL;
> +	abuf->size = 0;
> +	abuf->alloced = false;
> +}
> diff --git a/test/lib/Makefile b/test/lib/Makefile
> index aa2e66bc7f4..02e7cda532d 100644
> --- a/test/lib/Makefile
> +++ b/test/lib/Makefile
> @@ -3,6 +3,7 @@
>  # (C) Copyright 2018
>  # Mario Six, Guntermann & Drunck GmbH, mario.six at gdsys.cc
>  obj-y += cmd_ut_lib.o
> +obj-y += abuf.o
>  obj-$(CONFIG_EFI_LOADER) += efi_device_path.o
>  obj-$(CONFIG_EFI_SECURE_BOOT) += efi_image_region.o
>  obj-y += hexdump.o
> diff --git a/test/lib/abuf.c b/test/lib/abuf.c
> new file mode 100644
> index 00000000000..45c5c131b34
> --- /dev/null
> +++ b/test/lib/abuf.c
> @@ -0,0 +1,303 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021 Google LLC
> + * Written by Simon Glass <sjg@chromium.org>
> + */
> +
> +#include <common.h>
> +#include <abuf.h>
> +#include <mapmem.h>
> +#include <test/lib.h>
> +#include <test/test.h>
> +#include <test/ut.h>
> +
> +static char test_data[] = "1234";
> +#define TEST_DATA_LEN	sizeof(test_data)
> +
> +/* Test abuf_set() */
> +static int lib_test_abuf_set(struct unit_test_state *uts)
> +{
> +	struct abuf buf;
> +	ulong start;
> +
> +	start = ut_check_free();
> +
> +	abuf_init(&buf);
> +	abuf_set(&buf, test_data, TEST_DATA_LEN);
> +	ut_asserteq_ptr(test_data, buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	/* Force it to allocate */
> +	ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN + 1));
> +	ut_assertnonnull(buf.data);
> +	ut_asserteq(TEST_DATA_LEN + 1, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +
> +	/* Now set it again, to force it to free */
> +	abuf_set(&buf, test_data, TEST_DATA_LEN);
> +	ut_asserteq_ptr(test_data, buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	/* Check for memory leaks */
> +	ut_assertok(ut_check_delta(start));
> +
> +	return 0;
> +}
> +LIB_TEST(lib_test_abuf_set, 0);
> +
> +/* Test abuf_map_sysmem() */
> +static int lib_test_abuf_map_sysmem(struct unit_test_state *uts)
> +{
> +	struct abuf buf;
> +	ulong addr;
> +
> +	abuf_init(&buf);
> +	addr = 0x100;
> +	abuf_map_sysmem(&buf, addr, TEST_DATA_LEN);
> +
> +	ut_asserteq_ptr(map_sysmem(0x100, 0), buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	return 0;
> +}
> +LIB_TEST(lib_test_abuf_map_sysmem, 0);
> +
> +/* Test abuf_realloc() */
> +static int lib_test_abuf_realloc(struct unit_test_state *uts)
> +{
> +	struct abuf buf;
> +	ulong start;
> +	void *ptr;
> +
> +	/* TODO: crashes on sandbox */
> +	return 0;
> +

Eh?

> +	start = ut_check_free();
> +
> +	abuf_init(&buf);
> +
> +	/* Allocate an empty buffer */
> +	ut_asserteq(true, abuf_realloc(&buf, 0));
> +	ut_assertnull(buf.data);
> +	ut_asserteq(0, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	/* Allocate a non-empty abuf */
> +	ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN));
> +	ut_assertnonnull(buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +	ptr = buf.data;
> +
> +	/* Make it smaller; the pointer should remain the same  */
> +	ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN - 1));
> +	ut_asserteq(TEST_DATA_LEN - 1, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +	ut_asserteq_ptr(ptr, buf.data);

Perhaps this one? In the alloc'ed case, you always do a realloc()
whether or not the new size is larger. realloc() is free to return the
same pointer, or actually trim the excess off, or do a
malloc()+memcpy()+free() under the hood. realloc() isn't even required
to succeed if the new size is smaller than the old. So I don't see how
you can assert anything about how realloc() will behave.

If you change the abuf_realloc() to be a no-op (except for updating
->size) for the case of new_size < abuf->size, then yes, you can of
course check that abuf behaves that way. But as soon as realloc() is
invoked, all bets are off.

> +	/* Make it larger, forcing reallocation */
> +	ut_asserteq(true, abuf_realloc(&buf, 0x1000));
> +	ut_assert(buf.data != ptr);

And this one is similarly flawed - if malloc() originally handed out a
buffer that is actually (much) larger than you requested, only the
malloc implementation knows, but that can very well mean that a later
realloc() does not need to actually do a new allocation - or perhaps,
the original allocation wasn't much too big, but it turns out that the
following chunk of memory is currently on the free list, so the existing
allocation can be expanded in-place.

> +	ut_asserteq(0x1000, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +
> +	/* Free it */
> +	ut_asserteq(true, abuf_realloc(&buf, 0));
> +	ut_assertnull(buf.data);
> +	ut_asserteq(0, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	/* Check for memory leaks */
> +	ut_assertok(ut_check_delta(start));
> +
> +	return 0;
> +}
> +LIB_TEST(lib_test_abuf_realloc, 0);
> +
> +/* Test handling of buffers that are too large */
> +static int lib_test_abuf_large(struct unit_test_state *uts)
> +{
> +	struct abuf buf;
> +	ulong start;
> +	size_t size;
> +	int delta;
> +	void *ptr;
> +
> +	/*
> +	 * This crashes at present due to trying to allocate more memory than
> +	 * available, which breaks something on sandbox.
> +	 */
> +	return 0;
> +
> +	start = ut_check_free();
> +
> +	/* Try an impossible size */
> +	abuf_init(&buf);
> +	ut_asserteq(false, abuf_realloc(&buf, CONFIG_SYS_MALLOC_LEN));
> +	ut_assertnull(buf.data);
> +	ut_asserteq(0, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	abuf_uninit(&buf);
> +	ut_assertnull(buf.data);
> +	ut_asserteq(0, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +
> +	/* Start with a normal size then try to increase it, to check realloc */
> +	ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN));
> +	ut_assertnonnull(buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +	ptr = buf.data;
> +	delta = ut_check_delta(start);
> +	ut_assert(delta > 0);
> +
> +	/* try to increase it */
> +	ut_asserteq(false, abuf_realloc(&buf, CONFIG_SYS_MALLOC_LEN));
> +	ut_asserteq_ptr(ptr, buf.data);
> +	ut_asserteq(TEST_DATA_LEN, buf.size);
> +	ut_asserteq(true, buf.alloced);
> +	ut_asserteq(delta, ut_check_delta(start));
> +
> +	/* Check for memory leaks */
> +	abuf_uninit(&buf);
> +	ut_assertok(ut_check_delta(start));
> +
> +	/* Start with a huge unallocated buf and try to move it */
> +	abuf_init(&buf);
> +	abuf_map_sysmem(&buf, 0, CONFIG_SYS_MALLOC_LEN);
> +	ut_asserteq(CONFIG_SYS_MALLOC_LEN, buf.size);
> +	ut_asserteq(false, buf.alloced);
> +	ut_assertnull(abuf_uninit_move(&buf, &size));
> +
> +	/* Check for memory leaks */
> +	abuf_uninit(&buf);
> +	ut_assertok(ut_check_delta(start));
> +
> +	return 0;
> +}
> +LIB_TEST(lib_test_abuf_large, 0);
> +
> +/* Test abuf_uninit_move() */
> +static int lib_test_abuf_uninit_move(struct unit_test_state *uts)
> +{
> +	void *ptr, *orig_ptr;
> +	struct abuf buf;
> +	size_t size;
> +	ulong start;
> +	int delta;
> +
> +	start = ut_check_free();
> +
> +	/* Move an empty buffer */
> +	abuf_init(&buf);
> +	ut_assertnull(abuf_uninit_move(&buf, &size));
> +	ut_asserteq(0, size);
> +	ut_assertnull(abuf_uninit_move(&buf, NULL));
> +
> +	/* Move an unallocated buffer */
> +	abuf_set(&buf, test_data, TEST_DATA_LEN);

Reading this made me think of something that I think is missing from the
API: Perhaps I have a chunk of memory that I have malloc'ed myself, and
now I want to use an abuf to manage it. How do I gift that to abuf? IOW,
perhaps an abuf_set_alloced(), or if it's better to think of it as a
companion to abuf_uninit_move, perhaps abuf_init_move()?

Rasmus

  reply	other threads:[~2021-05-04  6:59 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-05-03 23:10 [PATCH 00/49] image: Reduce #ifdefs and ad-hoc defines in image code Simon Glass
2021-05-03 23:10 ` Simon Glass
2021-05-03 23:10 ` [PATCH 01/49] Add support for an owned buffer Simon Glass
2021-05-04  6:59   ` Rasmus Villemoes [this message]
2021-05-05 23:37     ` Simon Glass
2021-05-03 23:10 ` [PATCH 02/49] compiler: Add a comment to host_build() Simon Glass
2021-05-04  7:01   ` Rasmus Villemoes
2021-05-03 23:10 ` [PATCH 03/49] zstd: Create a function for use from U-Boot Simon Glass
2021-05-03 23:10 ` [PATCH 04/49] btrfs: Use U-Boot API for decompression Simon Glass
2021-05-03 23:10   ` Simon Glass
2021-05-03 23:25   ` Qu Wenruo
2021-05-03 23:25     ` Qu Wenruo
2021-05-03 23:45   ` Marek Behun
2021-05-03 23:45     ` Marek Behun
2021-05-04 16:58     ` Simon Glass
2021-05-04 16:58       ` Simon Glass
2021-05-03 23:10 ` [PATCH 05/49] image: Avoid switch default in image_decomp() Simon Glass
2021-05-03 23:10 ` [PATCH 06/49] image: Update zstd to avoid reporting error twice Simon Glass
2021-05-03 23:10 ` [PATCH 07/49] gzip: Avoid use of u64 Simon Glass
2021-05-03 23:10 ` [PATCH 08/49] image: Update image_decomp() to avoid ifdefs Simon Glass
2021-05-03 23:10 ` [PATCH 09/49] image: Split board code out into its own file Simon Glass
2021-05-03 23:10 ` [PATCH 10/49] image: Fix up checkpatch warnings in image-board.c Simon Glass
2021-05-03 23:10 ` [PATCH 11/49] image: Split host code out into its own file Simon Glass
2021-05-03 23:10 ` [PATCH 12/49] image: Create a function to do manual relocation Simon Glass
2021-05-04  7:17   ` Rasmus Villemoes
2021-05-07  2:45     ` Simon Glass
2021-05-03 23:11 ` [PATCH 13/49] image: Avoid #ifdefs for " Simon Glass
2021-05-03 23:11 ` [PATCH 14/49] image: Remove ifdefs around image_setup_linux() el at Simon Glass
2021-05-03 23:11 ` [PATCH 15/49] image: Add Kconfig options for FIT in the host build Simon Glass
2021-05-03 23:11 ` [PATCH 16/49] kconfig: Add host support to CONFIG_IS_ENABLED() Simon Glass
2021-05-04  7:28   ` Rasmus Villemoes
2021-05-03 23:11 ` [PATCH 17/49] image: Shorten FIT_ENABLE_SHAxxx_SUPPORT Simon Glass
2021-05-03 23:11 ` [PATCH 18/49] image: Rename SPL_SHAxxx_SUPPORT to SPL_FIT_SHAxxx Simon Glass
2021-05-03 23:11 ` [PATCH 19/49] hash: Use Kconfig to enable hashing in host tools Simon Glass
2021-05-03 23:11 ` [PATCH 20/49] hash: Drop some #ifdefs in hash.c Simon Glass
2021-05-03 23:11 ` [PATCH 21/49] image: Drop IMAGE_ENABLE_FIT Simon Glass
2021-05-03 23:11 ` [PATCH 22/49] image: Drop IMAGE_ENABLE_OF_LIBFDT Simon Glass
2021-05-03 23:11 ` [PATCH 23/49] image: Use Kconfig to enable CONFIG_FIT_VERBOSE on host Simon Glass
2021-05-03 23:11 ` [PATCH 24/49] image: Rename CONFIG_FIT_ENABLE_RSASSA_PSS_SUPPORT Simon Glass
2021-05-03 23:11 ` [PATCH 25/49] image: Use Kconfig to enable FIT_RSASSA_PSS on host Simon Glass
2021-05-03 23:11 ` [PATCH 26/49] Kconfig: Rename SPL_CRC32_SUPPORT to SPL_CRC32 Simon Glass
2021-05-03 23:11 ` [PATCH 27/49] image: Drop IMAGE_ENABLE_CRC32 Simon Glass
2021-05-03 23:11 ` [PATCH 28/49] Kconfig: Rename SPL_MD5_SUPPORT to SPL_MD5 Simon Glass
2021-05-03 23:11 ` [PATCH 29/49] image: Drop IMAGE_ENABLE_MD5 Simon Glass
2021-05-03 23:11 ` [PATCH 30/49] image: Drop IMAGE_ENABLE_SHA1 Simon Glass
2021-05-03 23:11 ` [PATCH 31/49] image: Drop IMAGE_ENABLE_SHAxxx Simon Glass
2021-05-03 23:11 ` [PATCH 32/49] image: Drop IMAGE_BOOT_GET_CMDLINE Simon Glass
2021-05-03 23:11 ` [PATCH 33/49] image: Drop IMAGE_OF_BOARD_SETUP Simon Glass
2021-05-03 23:11 ` [PATCH 34/49] image: Drop IMAGE_OF_SYSTEM_SETUP Simon Glass
2021-05-03 23:11 ` [PATCH 35/49] image: Drop IMAGE_ENABLE_IGNORE Simon Glass
2021-05-03 23:11 ` [PATCH 36/49] image: Drop IMAGE_ENABLE_SIGN/VERIFY defines Simon Glass
2021-05-03 23:11 ` [PATCH 37/49] image: Drop IMAGE_ENABLE_BEST_MATCH Simon Glass
2021-05-03 23:11 ` [PATCH 38/49] image: Drop IMAGE_ENABLE_EN/DECRYPT defines Simon Glass
2021-05-03 23:11 ` [PATCH 39/49] image: Tidy up fit_unsupported_reset() Simon Glass
2021-05-03 23:11 ` [PATCH 40/49] image: Drop unnecessary #ifdefs from image.h Simon Glass
2021-05-03 23:11 ` [PATCH 41/49] image: Drop #ifdefs for fit_print_contents() Simon Glass
2021-05-03 23:11 ` [PATCH 42/49] image: Drop most #ifdefs in image-board.c Simon Glass
2021-05-03 23:11 ` [PATCH 43/49] image: Reduce variable scope in boot_get_ramdisk() Simon Glass
2021-05-03 23:11 ` [PATCH 44/49] image: Split up boot_get_ramdisk() Simon Glass
2021-05-03 23:11 ` [PATCH 45/49] image: Remove #ifdefs from select_ramdisk() Simon Glass
2021-05-03 23:11 ` [PATCH 46/49] image: Remove some #ifdefs from image-fit and image-fit-sig Simon Glass
2021-05-03 23:11 ` [PATCH 47/49] image: Reduce variable scope in boot_get_fdt() Simon Glass
2021-05-03 23:11 ` [PATCH 48/49] image: Split up boot_get_fdt() Simon Glass
2021-05-03 23:11 ` [PATCH 49/49] image: Remove #ifdefs from select_fdt() Simon Glass
2021-05-04 21:40 ` [PATCH 00/49] image: Reduce #ifdefs and ad-hoc defines in image code Tom Rini
2021-05-04 21:40   ` Tom Rini
2021-05-04 21:49   ` Simon Glass
2021-05-04 21:49     ` Simon Glass
2021-05-04 23:24   ` Sean Anderson
2021-05-04 23:24     ` Sean Anderson
2021-05-05  1:11     ` Tom Rini
2021-05-05  1:11       ` Tom Rini
2021-05-05 23:38   ` Simon Glass
2021-05-05 23:38     ` Simon Glass

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=adc8604f-f9b1-b055-2fa3-a392f905aa8e@prevas.dk \
    --to=rasmus.villemoes@prevas.dk \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.