summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2016-09-20 18:46:40 +0200
committerLukas Slebodnik <lslebodn@redhat.com>2017-03-14 13:31:47 +0100
commit9a9b5e115b079751422be22fd252c0b283611c62 (patch)
tree6ca3c632c78c3428cb76f879bbf58b0320157af7
parentcab319e2db4b3d85dcadbfdf4c88939df103892e (diff)
downloadsssd-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.am22
-rw-r--r--src/tests/cmocka/test_iobuf.c195
-rw-r--r--src/util/sss_iobuf.c205
-rw-r--r--src/util/sss_iobuf.h118
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_ */