diff options
author | Jakub Hrozek <jhrozek@redhat.com> | 2016-09-20 18:46:40 +0200 |
---|---|---|
committer | Lukas Slebodnik <lslebodn@redhat.com> | 2017-03-14 13:31:47 +0100 |
commit | 9a9b5e115b079751422be22fd252c0b283611c62 (patch) | |
tree | 6ca3c632c78c3428cb76f879bbf58b0320157af7 | |
parent | cab319e2db4b3d85dcadbfdf4c88939df103892e (diff) | |
download | sssd-9a9b5e115b079751422be22fd252c0b283611c62.tar.gz sssd-9a9b5e115b079751422be22fd252c0b283611c62.tar.xz sssd-9a9b5e115b079751422be22fd252c0b283611c62.zip |
UTIL: Add a generic iobuf module
The KCM responder reads bytes and writes bytes from a buffer of bytes.
Instead of letting the caller deal with low-level handling using the
SAFEALIGN macros, this patch adds a new iobuf.c module with more
high-level functions.
The core is a iobuf struct that keeps track of the buffer, its total
capacity and a current read or write position.
There are helper function to read or write a generic buffer with a set
length. Later, we will also add convenience functions to read C data
types using the SAFEALIGN macros.
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
-rw-r--r-- | Makefile.am | 22 | ||||
-rw-r--r-- | src/tests/cmocka/test_iobuf.c | 195 | ||||
-rw-r--r-- | src/util/sss_iobuf.c | 205 | ||||
-rw-r--r-- | src/util/sss_iobuf.h | 118 |
4 files changed, 540 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 46ed3311a..318fd210e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -277,6 +277,7 @@ if HAVE_CMOCKA test_ipa_dn \ simple-access-tests \ krb5_common_test \ + test_iobuf \ $(NULL) if HAVE_LIBRESOLV @@ -658,6 +659,7 @@ dist_noinst_HEADERS = \ src/util/util_sss_idmap.h \ src/util/util_creds.h \ src/util/inotify.h \ + src/util/sss_iobuf.h \ src/monitor/monitor.h \ src/monitor/monitor_interfaces.h \ src/monitor/monitor_iface_generated.h \ @@ -1840,6 +1842,7 @@ krb5_utils_tests_SOURCES = \ src/providers/krb5/krb5_common.c \ src/providers/krb5/krb5_opts.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ src/providers/data_provider_fo.c \ src/providers/data_provider_opts.c \ src/providers/data_provider_callbacks.c \ @@ -2115,6 +2118,7 @@ krb5_child_test_SOURCES = \ src/providers/krb5/krb5_common.c \ src/providers/krb5/krb5_opts.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ src/providers/data_provider_fo.c \ src/providers/data_provider_opts.c \ src/providers/data_provider_callbacks.c \ @@ -2786,6 +2790,7 @@ test_copy_ccache_SOURCES = \ src/tests/cmocka/test_copy_ccache.c \ src/providers/krb5/krb5_ccache.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ $(NULL) test_copy_ccache_CFLAGS = \ $(AM_CFLAGS) \ @@ -2804,6 +2809,7 @@ test_copy_keytab_SOURCES = \ src/tests/cmocka/test_copy_keytab.c \ src/providers/krb5/krb5_keytab.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ $(NULL) test_copy_keytab_CFLAGS = \ $(AM_CFLAGS) \ @@ -3166,6 +3172,19 @@ test_ipa_dn_LDADD = \ libsss_test_common.la \ $(NULL) +test_iobuf_SOURCES = \ + src/util/sss_iobuf.c \ + src/tests/cmocka/test_iobuf.c \ + $(NULL) +test_iobuf_CFLAGS = \ + $(AM_CFLAGS) \ + $(NULL) +test_iobuf_LDADD = \ + $(CMOCKA_LIBS) \ + $(SSSD_LIBS) \ + $(NULL) + + EXTRA_simple_access_tests_DEPENDENCIES = \ $(ldblib_LTLIBRARIES) simple_access_tests_SOURCES = \ @@ -3498,6 +3517,7 @@ libsss_krb5_common_la_SOURCES = \ src/providers/krb5/krb5_init_shared.c \ src/providers/krb5/krb5_ccache.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ src/util/become_user.c \ $(NULL) libsss_krb5_common_la_CFLAGS = \ @@ -3727,6 +3747,7 @@ krb5_child_SOURCES = \ src/providers/dp_pam_data_util.c \ src/util/user_info_msg.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ src/util/find_uid.c \ src/util/atomic_io.c \ src/util/authtok.c \ @@ -3760,6 +3781,7 @@ ldap_child_SOURCES = \ src/providers/ldap/ldap_child.c \ src/providers/krb5/krb5_keytab.c \ src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ src/util/atomic_io.c \ src/util/authtok.c \ src/util/authtok-utils.c \ diff --git a/src/tests/cmocka/test_iobuf.c b/src/tests/cmocka/test_iobuf.c new file mode 100644 index 000000000..796db514d --- /dev/null +++ b/src/tests/cmocka/test_iobuf.c @@ -0,0 +1,195 @@ +/* + SSSD + + test_iobuf - IO buffer tests + + Copyright (C) 2016 Red Hat + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "util/sss_iobuf.h" +#include "util/util.h" + +static void test_sss_iobuf_read(void **state) +{ + errno_t ret; + uint8_t buffer[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0 }; + uint8_t readbuf[64] = { 0 }; + size_t nread; + struct sss_iobuf *rb; + + rb = sss_iobuf_init_readonly(NULL, buffer, sizeof(buffer)); + assert_non_null(rb); + + ret = sss_iobuf_read(rb, 5, readbuf, &nread); + assert_int_equal(ret, EOK); + /* There is enough data in the buffer */ + assert_int_equal(nread, 5); + /* The data matches beginning of the buffer */ + assert_int_equal(strncmp((const char *) readbuf, "Hello", 5), 0); + + memset(readbuf, 0, sizeof(readbuf)); + ret = sss_iobuf_read(rb, 3, readbuf, &nread); + assert_int_equal(ret, EOK); + /* There is enough data in the buffer */ + assert_int_equal(nread, 3); + /* The data matches beginning of the buffer */ + assert_int_equal(strncmp((const char *) readbuf, " wo", 3), 0); + + /* Try to read more than the buffer has */ + memset(readbuf, 0, sizeof(readbuf)); + ret = sss_iobuf_read(rb, 10, readbuf, &nread); + /* This is not a fatal error */ + assert_int_equal(ret, EOK); + /* We just see how much there was */ + assert_int_equal(nread, 4); + /* And get the rest of the buffer back. readbuf includes trailing zero now */ + assert_int_equal(strcmp((const char *) readbuf, "rld"), 0); + + /* Reading a depleted buffer will just yield zero bytes read now */ + ret = sss_iobuf_read(rb, 10, readbuf, &nread); + assert_int_equal(ret, EOK); + assert_int_equal(nread, 0); + + /* Failure cases */ + ret = sss_iobuf_read(NULL, 10, readbuf, &nread); + assert_int_equal(ret, EINVAL); + ret = sss_iobuf_read(rb, 10, NULL, &nread); + assert_int_equal(ret, EINVAL); + + talloc_free(rb); +} + +static void test_sss_iobuf_write(void **state) +{ + struct sss_iobuf *wb; + struct sss_iobuf *rb; + size_t hwlen = sizeof("Hello world"); /* Includes trailing zero */ + uint8_t readbuf[64]; + size_t nread; + errno_t ret; + + /* Exactly fill the capacity */ + wb = sss_iobuf_init_empty(NULL, hwlen, hwlen); + assert_non_null(wb); + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("Hello world"), + sizeof("Hello world")); + assert_int_equal(ret, EOK); + + rb = sss_iobuf_init_readonly(NULL, + sss_iobuf_get_data(wb), + sss_iobuf_get_len(wb)); + talloc_free(wb); + assert_non_null(rb); + + ret = sss_iobuf_read(rb, sizeof(readbuf), readbuf, &nread); + assert_int_equal(ret, EOK); + assert_int_equal(nread, hwlen); + assert_int_equal(strcmp((const char *) readbuf, "Hello world"), 0); + talloc_zfree(rb); + + /* Overflow the capacity by one */ + wb = sss_iobuf_init_empty(NULL, hwlen, hwlen); + assert_non_null(wb); + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("Hello world!"), + sizeof("Hello world!")); + assert_int_not_equal(ret, EOK); + talloc_zfree(wb); + + /* Test resizing exactly up to capacity in several writes */ + wb = sss_iobuf_init_empty(NULL, 2, hwlen); + assert_non_null(wb); + + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("Hello "), + sizeof("Hello ")-1); /* Not the null byte now.. */ + assert_int_equal(ret, EOK); + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("world"), + sizeof("world")); + assert_int_equal(ret, EOK); + + rb = sss_iobuf_init_readonly(NULL, + sss_iobuf_get_data(wb), + sss_iobuf_get_len(wb)); + talloc_free(wb); + assert_non_null(rb); + + ret = sss_iobuf_read(rb, sizeof(readbuf), readbuf, &nread); + assert_int_equal(ret, EOK); + assert_int_equal(nread, hwlen); + assert_int_equal(strcmp((const char *) readbuf, "Hello world"), 0); + talloc_zfree(rb); + + /* Overflow the capacity during a resize by one */ + wb = sss_iobuf_init_empty(NULL, 2, hwlen); + assert_non_null(wb); + + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("Hello "), + sizeof("Hello ")-1); /* Not the null byte now.. */ + assert_int_equal(ret, EOK); + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("world!"), + sizeof("world!")); + assert_int_not_equal(ret, EOK); + talloc_zfree(wb); + + /* Test allocating an unlimited buffer */ + wb = sss_iobuf_init_empty(NULL, 2, 0); + assert_non_null(wb); + + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("Hello "), + sizeof("Hello ")-1); /* Not the null byte now.. */ + assert_int_equal(ret, EOK); + ret = sss_iobuf_write_len(wb, + (uint8_t *) discard_const("world"), + sizeof("world")); + assert_int_equal(ret, EOK); + + rb = sss_iobuf_init_readonly(NULL, + sss_iobuf_get_data(wb), + sss_iobuf_get_len(wb)); + talloc_free(wb); + assert_non_null(rb); + + ret = sss_iobuf_read(rb, sizeof(readbuf), readbuf, &nread); + assert_int_equal(ret, EOK); + assert_int_equal(nread, hwlen); + assert_int_equal(strcmp((const char *) readbuf, "Hello world"), 0); + talloc_zfree(rb); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sss_iobuf_read), + cmocka_unit_test(test_sss_iobuf_write), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/src/util/sss_iobuf.c b/src/util/sss_iobuf.c new file mode 100644 index 000000000..7c72ea94d --- /dev/null +++ b/src/util/sss_iobuf.c @@ -0,0 +1,205 @@ +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_iobuf.h" + +/** + * @brief The iobuf structure that holds the data, its capacity and + * a pointer to the data. + * + * @see sss_iobuf_init_empty() + * @see sss_iobuf_init_readonly() + */ +struct sss_iobuf { + uint8_t *data; /* Start of the data buffer */ + + size_t dp; /* Data pointer */ + size_t size; /* Current data buffer size */ + size_t capacity; /* Maximum capacity */ +}; + +struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx, + size_t size, + size_t capacity) +{ + struct sss_iobuf *iobuf; + uint8_t *buf; + + iobuf = talloc_zero(mem_ctx, struct sss_iobuf); + if (iobuf == NULL) { + return NULL; + } + + buf = talloc_zero_array(iobuf, uint8_t, size); + if (buf == NULL) { + talloc_free(iobuf); + return NULL; + } + + if (capacity == 0) { + capacity = SIZE_MAX / 2; + } + + iobuf->data = buf; + iobuf->size = size; + iobuf->capacity = capacity; + iobuf->dp = 0; + + return iobuf; +} + +struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx, + uint8_t *data, + size_t size) +{ + struct sss_iobuf *iobuf; + + iobuf = sss_iobuf_init_empty(mem_ctx, size, size); + if (iobuf == NULL) { + return NULL; + } + + if (data != NULL) { + memcpy(iobuf->data, data, size); + } + + return iobuf; +} + +size_t sss_iobuf_get_len(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->dp; +} + +size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->capacity; +} + +size_t sss_iobuf_get_size(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->size; +} + +uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return NULL; + } + + return iobuf->data; +} + +static size_t iobuf_get_len(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return (iobuf->size - iobuf->dp); +} + +static errno_t ensure_bytes(struct sss_iobuf *iobuf, + size_t nbytes) +{ + size_t wantsize; + size_t newsize; + uint8_t *newdata; + + if (iobuf == NULL) { + return EINVAL; + } + + wantsize = iobuf->dp + nbytes; + if (wantsize <= iobuf->size) { + /* Enough space already */ + return EOK; + } + + /* Else, try to extend the iobuf */ + if (wantsize > iobuf->capacity) { + /* We will never grow past capacity */ + return ENOBUFS; + } + + /* Double the size until we add at least nbytes, but stop if we double past capacity */ + for (newsize = iobuf->size; + (newsize < wantsize) && (newsize < iobuf->capacity); + newsize *= 2) + ; + + if (newsize > iobuf->capacity) { + newsize = iobuf->capacity; + } + + newdata = talloc_realloc(iobuf, iobuf->data, uint8_t, newsize); + if (newdata == NULL) { + return ENOMEM; + } + + iobuf->data = newdata; + iobuf->size = newsize; + + return EOK; +} + +static inline uint8_t *iobuf_ptr(struct sss_iobuf *iobuf) +{ + return iobuf->data + iobuf->dp; +} + +errno_t sss_iobuf_read(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf, + size_t *_read) +{ + size_t remaining; + + if (iobuf == NULL || _buf == NULL) { + return EINVAL; + } + + remaining = iobuf_get_len(iobuf); + if (len > remaining) { + len = remaining; + } + + safealign_memcpy(_buf, iobuf_ptr(iobuf), len, &iobuf->dp); + if (_read != NULL) { + *_read = len; + } + + return EOK; +} + +errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf, + uint8_t *buf, + size_t len) +{ + errno_t ret; + + if (iobuf == NULL || buf == NULL) { + return EINVAL; + } + + ret = ensure_bytes(iobuf, len); + if (ret != EOK) { + return ret; + } + + safealign_memcpy(iobuf_ptr(iobuf), buf, len, &iobuf->dp); + + return EOK; +} diff --git a/src/util/sss_iobuf.h b/src/util/sss_iobuf.h new file mode 100644 index 000000000..eae357a40 --- /dev/null +++ b/src/util/sss_iobuf.h @@ -0,0 +1,118 @@ +#ifndef __SSS_IOBUF_H_ +#define __SSS_IOBUF_H_ + +#include <talloc.h> +#include <stdint.h> +#include <errno.h> + +#include "util/util_errors.h" + +struct sss_iobuf; + +/* + * @brief Allocate an empty IO buffer + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * + * When this buffer is written into, but the capacity is exceeded, the write + * function will return an error. + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * @param[in] size The size of the data buffer + * @param[in] capacity The maximum capacity the buffer can grow into. + * Use 0 for an 'unlimited' buffer that will grow + * until SIZE_MAX/2. + * + * @return The newly created buffer on success or NULL on an error. + * + */ +struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx, + size_t size, + size_t capacity); + +/* + * @brief Allocate an IO buffer with a fixed size + * + * This function is useful for parsing an input buffer from an existing + * buffer pointed to by data. + * + * The iobuf does not assume ownership of the data buffer in talloc terms, + * but copies the data instead. + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * @param[in] data The data to initialize the IO buffer with. This + * data is copied into the iobuf-owned buffer. + * @param[in] size The size of the data buffer + * + * @return The newly created buffer on success or NULL on an error. + */ +struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx, + uint8_t *data, + size_t size); + +/* + * @brief Returns the number of bytes currently stored in the iobuf + * + * @return The number of bytes (the data pointer offset) + */ +size_t sss_iobuf_get_len(struct sss_iobuf *iobuf); + +/* + * @brief Returns the capacity of the IO buffer + * + * @return The capacity of the IO buffer. Returns zero + * for an unlimited buffer. + */ +size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf); + +/* + * @brief Returns the current size of the IO buffer + */ +size_t sss_iobuf_get_size(struct sss_iobuf *iobuf); + +/* + * @brief Returns the data pointer of the IO buffer + */ +uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf); + +/* + * @brief Read from an IO buffer + * + * Read up to len bytes from an IO buffer. It is not an error to request + * more bytes than the buffer actually has - the function will succeed, but + * return the actual number of bytes read. Reading from an empty buffer just + * returns zero bytes read. + * + * @param[in] iobuf The IO buffer to read from + * @param[in] len The maximum number of bytes to read + * @param[out] _buf The buffer to read data into from iobuf + * @param[out] _read The actual number of bytes read from IO buffer. + * + * @return EOK on success, errno otherwise + */ +errno_t sss_iobuf_read(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf, + size_t *_read); + +/* + * @brief Write into an IO buffer + * + * Attempts to write len bytes into the iobuf. If the capacity is exceeded, + * the iobuf module tries to extend the buffer up to the maximum capacity. + * + * If reallocating the internal buffer fails, the data pointers are not + * touched. + * + * @param[in] iobuf The IO buffer to write to + * @param[in] buf The data to write into the buffer + * @param[in] len The number of bytes to write + * + * @return EOK on success, errno otherwise. Notably returns ENOBUFS if + * the buffer capacity is exceeded. + */ +errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf, + uint8_t *buf, + size_t len); + +#endif /* __SSS_IOBUF_H_ */ |