summaryrefslogtreecommitdiffstats
path: root/src/shctx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shctx.c')
-rw-r--r--src/shctx.c320
1 files changed, 320 insertions, 0 deletions
diff --git a/src/shctx.c b/src/shctx.c
new file mode 100644
index 0000000..be59053
--- /dev/null
+++ b/src/shctx.c
@@ -0,0 +1,320 @@
+/*
+ * shctx.c - shared context management functions for SSL
+ *
+ * Copyright (C) 2011-2012 EXCELIANCE
+ *
+ * Author: Emeric Brun - emeric@exceliance.fr
+ *
+ * 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
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <sys/mman.h>
+#include <arpa/inet.h>
+#include <import/ebmbtree.h>
+#include <haproxy/list.h>
+#include <haproxy/shctx.h>
+
+/*
+ * Reserve a new row if <first> is null, put it in the hotlist, set the refcount to 1
+ * or append new blocks to the row with <first> as first block if non null.
+ *
+ * Reserve blocks in the avail list and put them in the hot list
+ * Return the first block put in the hot list or NULL if not enough blocks available
+ */
+struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx,
+ struct shared_block *first, int data_len)
+{
+ struct shared_block *last = NULL, *block, *sblock;
+ struct shared_block *ret = first;
+ int remain = 1;
+
+ BUG_ON(data_len < 0);
+
+ /* Check the object size limit. */
+ if (shctx->max_obj_size > 0) {
+ if ((first && first->len + data_len > shctx->max_obj_size) ||
+ (!first && data_len > shctx->max_obj_size))
+ goto out;
+ }
+
+ if (first) {
+ /* Check that there is some block to reserve.
+ * In this first block of code we compute the remaining room in the
+ * current list of block already reserved for this object.
+ * We return asap if there is enough room to copy <data_len> bytes.
+ */
+ last = first->last_reserved;
+ /* Remaining room. */
+ remain = (shctx->block_size * first->block_count - first->len);
+ if (remain) {
+ if (remain > data_len) {
+ return last ? last : first;
+ } else {
+ data_len -= remain;
+ if (data_len <= 0)
+ return last ? last : first;
+ }
+ }
+ }
+
+ shctx_wrlock(shctx);
+
+ /* not enough usable blocks */
+ if (data_len > shctx->nbav * shctx->block_size) {
+ shctx_wrunlock(shctx);
+ goto out;
+ }
+
+
+ if (data_len <= 0 || LIST_ISEMPTY(&shctx->avail)) {
+ ret = NULL;
+ shctx_wrunlock(shctx);
+ goto out;
+ }
+
+ list_for_each_entry_safe(block, sblock, &shctx->avail, list) {
+
+ /* release callback */
+ if (block->len && shctx->free_block)
+ shctx->free_block(block, shctx->cb_data);
+ block->len = 0;
+
+ if (ret) {
+ shctx_block_append_hot(shctx, ret, block);
+ if (!remain) {
+ first->last_append = block;
+ remain = 1;
+ }
+ } else {
+ ret = shctx_block_detach(shctx, block);
+ ret->len = 0;
+ ret->block_count = 0;
+ ret->last_append = NULL;
+ ret->refcount = 1;
+ }
+
+ ++ret->block_count;
+
+ data_len -= shctx->block_size;
+
+ if (data_len <= 0) {
+ ret->last_reserved = block;
+ break;
+ }
+ }
+
+ shctx_wrunlock(shctx);
+
+ if (shctx->reserve_finish)
+ shctx->reserve_finish(shctx);
+
+out:
+ return ret;
+}
+
+/*
+ * if the refcount is 0 move the row to the hot list. Increment the refcount
+ */
+void shctx_row_detach(struct shared_context *shctx, struct shared_block *first)
+{
+ if (first->refcount <= 0) {
+
+ BUG_ON(!first->last_reserved);
+
+ /* Detach row from avail list, link first item's prev to last
+ * item's next. This allows to use the LIST_SPLICE_END_DETACHED
+ * macro. */
+ first->list.p->n = first->last_reserved->list.n;
+ first->last_reserved->list.n->p = first->list.p;
+
+ first->list.p = &first->last_reserved->list;
+ first->last_reserved->list.n = &first->list;
+
+ shctx->nbav -= first->block_count;
+ }
+
+ first->refcount++;
+}
+
+/*
+ * decrement the refcount and move the row at the end of the avail list if it reaches 0.
+ */
+void shctx_row_reattach(struct shared_context *shctx, struct shared_block *first)
+{
+ first->refcount--;
+
+ if (first->refcount <= 0) {
+
+ BUG_ON(!first->last_reserved);
+
+ /* Reattach to avail list */
+ first->list.p = &first->last_reserved->list;
+ LIST_SPLICE_END_DETACHED(&shctx->avail, &first->list);
+
+ shctx->nbav += first->block_count;
+ }
+}
+
+
+/*
+ * Append data in the row if there is enough space.
+ * The row should be in the hot list
+ *
+ * Return the amount of appended data if ret >= 0
+ * or how much more space it needs to contains the data if < 0.
+ */
+int shctx_row_data_append(struct shared_context *shctx, struct shared_block *first,
+ unsigned char *data, int len)
+{
+ int remain, start;
+ struct shared_block *block;
+
+ /* return -<len> needed to work */
+ if (len > first->block_count * shctx->block_size - first->len)
+ return (first->block_count * shctx->block_size - first->len) - len;
+
+ block = first->last_append ? first->last_append : first;
+ do {
+ /* end of copy */
+ if (len <= 0)
+ break;
+
+ /* remaining written bytes in the current block. */
+ remain = (shctx->block_size * first->block_count - first->len) % shctx->block_size;
+ BUG_ON(remain < 0);
+
+ /* if remain == 0, previous buffers are full, or first->len == 0 */
+ if (!remain) {
+ remain = shctx->block_size;
+ start = 0;
+ }
+ else {
+ /* start must be calculated before remain is modified */
+ start = shctx->block_size - remain;
+ BUG_ON(start < 0);
+ }
+
+ /* must not try to copy more than len */
+ remain = MIN(remain, len);
+
+ memcpy(block->data + start, data, remain);
+
+ data += remain;
+ len -= remain;
+ first->len += remain; /* update len in the head of the row */
+ first->last_append = block;
+
+ block = LIST_ELEM(block->list.n, struct shared_block*, list);
+ } while (block != first);
+
+ return len;
+}
+
+/*
+ * Copy <len> data from a row of blocks, return the remaining data to copy
+ * If 0 is returned, the full data has successfully been copied
+ *
+ * The row should be in the hot list
+ */
+int shctx_row_data_get(struct shared_context *shctx, struct shared_block *first,
+ unsigned char *dst, int offset, int len)
+{
+ int count = 0, size = 0, start = -1;
+ struct shared_block *block;
+
+ /* can't copy more */
+ if (len > first->len)
+ len = first->len;
+
+ block = first;
+ count = 0;
+ /* Pass through the blocks to copy them */
+ do {
+ if (count >= first->block_count || len <= 0)
+ break;
+
+ count++;
+ /* continue until we are in right block
+ corresponding to the offset */
+ if (count < offset / shctx->block_size + 1)
+ continue;
+
+ /* on the first block, data won't possibly began at offset 0 */
+ if (start == -1)
+ start = offset - (count - 1) * shctx->block_size;
+
+ BUG_ON(start < 0);
+
+ /* size can be lower than a block when copying the last block */
+ size = MIN(shctx->block_size - start, len);
+ BUG_ON(size < 0);
+
+ memcpy(dst, block->data + start, size);
+ dst += size;
+ len -= size;
+ start = 0;
+
+ block = LIST_ELEM(block->list.n, struct shared_block*, list);
+ } while (block != first);
+ return len;
+}
+
+/* Allocate shared memory context.
+ * <maxblocks> is maximum blocks.
+ * If <maxblocks> is set to less or equal to 0, ssl cache is disabled.
+ * Returns: -1 on alloc failure, <maxblocks> if it performs context alloc,
+ * and 0 if cache is already allocated.
+ */
+int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize,
+ unsigned int maxobjsz, int extra)
+{
+ int i;
+ struct shared_context *shctx;
+ int ret;
+ void *cur;
+ int maptype = MAP_SHARED;
+
+ if (maxblocks <= 0)
+ return 0;
+
+ /* make sure to align the records on a pointer size */
+ blocksize = (blocksize + sizeof(void *) - 1) & -sizeof(void *);
+ extra = (extra + sizeof(void *) - 1) & -sizeof(void *);
+
+ shctx = (struct shared_context *)mmap(NULL, sizeof(struct shared_context) + extra + (maxblocks * (sizeof(struct shared_block) + blocksize)),
+ PROT_READ | PROT_WRITE, maptype | MAP_ANON, -1, 0);
+ if (!shctx || shctx == MAP_FAILED) {
+ shctx = NULL;
+ ret = SHCTX_E_ALLOC_CACHE;
+ goto err;
+ }
+
+ shctx->nbav = 0;
+
+ LIST_INIT(&shctx->avail);
+ HA_RWLOCK_INIT(&shctx->lock);
+
+ shctx->block_size = blocksize;
+ shctx->max_obj_size = maxobjsz == (unsigned int)-1 ? 0 : maxobjsz;
+
+ /* init the free blocks after the shared context struct */
+ cur = (void *)shctx + sizeof(struct shared_context) + extra;
+ for (i = 0; i < maxblocks; i++) {
+ struct shared_block *cur_block = (struct shared_block *)cur;
+ cur_block->len = 0;
+ cur_block->refcount = 0;
+ cur_block->block_count = 1;
+ LIST_APPEND(&shctx->avail, &cur_block->list);
+ shctx->nbav++;
+ cur += sizeof(struct shared_block) + blocksize;
+ }
+ ret = maxblocks;
+
+err:
+ *orig_shctx = shctx;
+ return ret;
+}
+