#include "cairoint.h"
#include "cairo-xcb-private.h"
#include <xcb/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
typedef struct _cairo_xcb_shm_mem_block cairo_xcb_shm_mem_block_t;
struct _cairo_xcb_shm_mem_block {
unsigned int bits;
cairo_list_t link;
};
struct _cairo_xcb_shm_mem_pool {
int shmid;
uint32_t shmseg;
char *base;
unsigned int nBlocks;
cairo_xcb_shm_mem_block_t *blocks;
cairo_list_t free[32];
unsigned char *map;
unsigned int min_bits;
unsigned int num_sizes;
size_t free_bytes;
size_t max_bytes;
unsigned int max_free_bits;
cairo_list_t link;
};
#define BITTEST(p, n) ((p)->map[(n) >> 3] & (128 >> ((n) & 7)))
#define BITSET(p, n) ((p)->map[(n) >> 3] |= (128 >> ((n) & 7)))
#define BITCLEAR(p, n) ((p)->map[(n) >> 3] &= ~(128 >> ((n) & 7)))
static void
clear_bits (cairo_xcb_shm_mem_pool_t *pi, size_t first, size_t last)
{
size_t i, n = last;
size_t first_full = (first + 7) & ~7;
size_t past_full = last & ~7;
size_t bytes;
if (n > first_full)
n = first_full;
for (i = first; i < n; i++)
BITCLEAR (pi, i);
if (past_full > first_full) {
bytes = past_full - first_full;
bytes = bytes >> 3;
memset (pi->map + (first_full >> 3), 0, bytes);
}
if (past_full < n)
past_full = n;
for (i = past_full; i < last; i++)
BITCLEAR (pi, i);
}
static void
free_bits (cairo_xcb_shm_mem_pool_t *pi,
size_t start,
unsigned int bits,
cairo_bool_t clear)
{
cairo_xcb_shm_mem_block_t *block;
if (clear)
clear_bits (pi, start, start + (1 << bits));
block = pi->blocks + start;
block->bits = bits;
cairo_list_add (&block->link, &pi->free[bits]);
pi->free_bytes += 1 << (bits + pi->min_bits);
if (bits > pi->max_free_bits)
pi->max_free_bits = bits;
}
static void
free_blocks (cairo_xcb_shm_mem_pool_t *pi,
size_t first,
size_t last,
cairo_bool_t clear)
{
size_t i;
size_t bits = 0;
size_t len = 1;
i = first;
while (i < last) {
while (bits < pi->num_sizes - 1) {
size_t next_bits = bits + 1;
size_t next_len = len << 1;
if (i + next_bits > last) {
break;
}
if (i & (next_len - 1))
break;
bits = next_bits;
len = next_len;
}
do {
if (i + len > last)
continue;
if (i & (len - 1))
continue;
break;
bits--;
len >>=1;
} while (len > 0);
if (len == 0)
break;
free_bits (pi, i, bits, clear);
i += len;
}
}
static cairo_status_t
_cairo_xcb_shm_mem_pool_init (cairo_xcb_shm_mem_pool_t *pi,
size_t bytes,
unsigned int min_bits,
unsigned int num_sizes)
{
size_t setBits;
int i;
assert ((((unsigned long) pi->base) & ((1 << min_bits) - 1)) == 0);
assert (num_sizes < ARRAY_LENGTH (pi->free));
pi->free_bytes = 0;
pi->max_bytes = bytes;
pi->max_free_bits = 0;
setBits = bytes >> min_bits;
pi->blocks = calloc (setBits, sizeof (cairo_xcb_shm_mem_block_t));
if (pi->blocks == NULL)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
pi->nBlocks = setBits;
pi->min_bits = min_bits;
pi->num_sizes = num_sizes;
for (i = 0; i < ARRAY_LENGTH (pi->free); i++)
cairo_list_init (&pi->free[i]);
pi->map = malloc ((setBits + 7) >> 3);
if (pi->map == NULL) {
free (pi->blocks);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
memset (pi->map, -1, (setBits + 7) >> 3);
clear_bits (pi, 0, setBits);
free_blocks (pi, 0, setBits, 1);
return CAIRO_STATUS_SUCCESS;
}
static cairo_xcb_shm_mem_block_t *
get_buddy (cairo_xcb_shm_mem_pool_t *pi,
size_t offset,
unsigned int bits)
{
cairo_xcb_shm_mem_block_t *block;
assert (offset + (1 << bits) <= pi->nBlocks);
if (BITTEST (pi, offset + (1 << bits) - 1))
return NULL;
block = pi->blocks + offset;
if (block->bits != bits)
return NULL;
return block;
}
static void
merge_buddies (cairo_xcb_shm_mem_pool_t *pi,
cairo_xcb_shm_mem_block_t *block,
unsigned int max_bits)
{
size_t block_offset = block_offset = block - pi->blocks;
unsigned int bits = block->bits;
while (bits < max_bits - 1) {
size_t buddy_offset = block_offset ^ (1 << bits);
block = get_buddy (pi, buddy_offset, bits);
if (block == NULL)
break;
cairo_list_del (&block->link);
if (buddy_offset < block_offset)
block_offset = buddy_offset;
bits++;
}
block = pi->blocks + block_offset;
block->bits = bits;
cairo_list_add (&block->link, &pi->free[bits]);
if (bits > pi->max_free_bits)
pi->max_free_bits = bits;
}
static unsigned int
merge_bits (cairo_xcb_shm_mem_pool_t *pi,
unsigned int max_bits)
{
cairo_xcb_shm_mem_block_t *block, *buddy, *next;
unsigned int bits;
for (bits = 0; bits < max_bits - 1; bits++) {
cairo_list_foreach_entry_safe (block, next,
cairo_xcb_shm_mem_block_t,
&pi->free[bits],
link)
{
size_t buddy_offset = (block - pi->blocks) ^ (1 << bits);
buddy = get_buddy (pi, buddy_offset, bits);
if (buddy == NULL)
continue;
if (buddy == next) {
next = cairo_container_of (buddy->link.next,
cairo_xcb_shm_mem_block_t,
link);
}
cairo_list_del (&block->link);
merge_buddies (pi, block, max_bits);
}
}
return pi->max_free_bits;
}
static void *
buddy_malloc (cairo_xcb_shm_mem_pool_t *pi,
unsigned int bits)
{
unsigned int b;
size_t offset;
size_t past;
cairo_xcb_shm_mem_block_t *block;
if (bits > pi->max_free_bits && bits > merge_bits (pi, bits))
return NULL;
block = NULL;
for (b = bits; b <= pi->max_free_bits; b++) {
if (! cairo_list_is_empty (&pi->free[b])) {
block = cairo_list_first_entry (&pi->free[b],
cairo_xcb_shm_mem_block_t,
link);
break;
}
}
assert (block != NULL);
cairo_list_del (&block->link);
while (cairo_list_is_empty (&pi->free[pi->max_free_bits])) {
if (--pi->max_free_bits == 0)
break;
}
offset = block - pi->blocks;
past = offset + (1 << bits);
BITSET (pi, past - 1);
block->bits = bits;
pi->free_bytes -= 1 << (b + pi->min_bits);
free_blocks (pi, past, offset + (1 << b), 0);
return pi->base + ((block - pi->blocks) << pi->min_bits);
}
static void *
_cairo_xcb_shm_mem_pool_malloc (cairo_xcb_shm_mem_pool_t *pi,
size_t bytes)
{
unsigned int bits;
size_t size;
size = 1 << pi->min_bits;
for (bits = 0; size < bytes; bits++)
size <<= 1;
if (bits >= pi->num_sizes)
return NULL;
return buddy_malloc (pi, bits);
}
static void
_cairo_xcb_shm_mem_pool_free (cairo_xcb_shm_mem_pool_t *pi,
char *storage)
{
size_t block_offset;
cairo_xcb_shm_mem_block_t *block;
block_offset = (storage - pi->base) >> pi->min_bits;
block = pi->blocks + block_offset;
BITCLEAR (pi, block_offset + ((1 << block->bits) - 1));
pi->free_bytes += 1 << (block->bits + pi->min_bits);
merge_buddies (pi, block, pi->num_sizes);
}
static void
_cairo_xcb_shm_mem_pool_destroy (cairo_xcb_shm_mem_pool_t *pool)
{
shmdt (pool->base);
cairo_list_del (&pool->link);
free (pool->map);
free (pool->blocks);
free (pool);
}
cairo_int_status_t
_cairo_xcb_connection_allocate_shm_info (cairo_xcb_connection_t *connection,
size_t size,
cairo_xcb_shm_info_t **shm_info_out)
{
cairo_xcb_shm_info_t *shm_info;
cairo_xcb_shm_mem_pool_t *pool, *next;
size_t bytes, maxbits = 16, minbits = 8;
void *mem = NULL;
cairo_status_t status;
assert (connection->flags & CAIRO_XCB_HAS_SHM);
CAIRO_MUTEX_LOCK (connection->shm_mutex);
cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
&connection->shm_pools, link)
{
if (pool->free_bytes > size) {
mem = _cairo_xcb_shm_mem_pool_malloc (pool, size);
if (mem != NULL) {
cairo_list_move (&pool->link, &connection->shm_pools);
goto allocate_shm_info;
}
}
if (pool->free_bytes == pool->max_bytes) {
_cairo_xcb_connection_shm_detach (connection,
pool->shmseg);
_cairo_xcb_shm_mem_pool_destroy (pool);
}
}
pool = malloc (sizeof (cairo_xcb_shm_mem_pool_t));
if (unlikely (pool == NULL)) {
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
bytes = 1 << maxbits;
while (bytes <= size)
bytes <<= 1, maxbits++;
bytes <<= 3;
do {
pool->shmid = shmget (IPC_PRIVATE, bytes, IPC_CREAT | 0600);
if (pool->shmid != -1)
break;
if (errno == EINVAL && bytes > size) {
bytes >>= 1;
continue;
}
} while (FALSE);
if (pool->shmid == -1) {
int err = errno;
if (! (err == EINVAL || err == ENOMEM))
connection->flags &= ~CAIRO_XCB_HAS_SHM;
free (pool);
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
return CAIRO_INT_STATUS_UNSUPPORTED;
}
pool->base = shmat (pool->shmid, NULL, 0);
if (unlikely (pool->base == (char *) -1)) {
shmctl (pool->shmid, IPC_RMID, NULL);
free (pool);
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
status = _cairo_xcb_shm_mem_pool_init (pool,
bytes,
minbits,
maxbits - minbits + 1);
if (unlikely (status)) {
shmdt (pool->base);
free (pool);
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
return status;
}
pool->shmseg = _cairo_xcb_connection_shm_attach (connection, pool->shmid, FALSE);
shmctl (pool->shmid, IPC_RMID, NULL);
cairo_list_add (&pool->link, &connection->shm_pools);
mem = _cairo_xcb_shm_mem_pool_malloc (pool, size);
allocate_shm_info:
shm_info = _cairo_freepool_alloc (&connection->shm_info_freelist);
if (unlikely (shm_info == NULL)) {
_cairo_xcb_shm_mem_pool_free (pool, mem);
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
shm_info->connection = connection;
shm_info->pool = pool;
shm_info->shm = pool->shmseg;
shm_info->offset = (char *) mem - (char *) pool->base;
shm_info->mem = mem;
cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
&connection->shm_pools, link)
{
if (pool->free_bytes == pool->max_bytes) {
_cairo_xcb_connection_shm_detach (connection,
pool->shmseg);
_cairo_xcb_shm_mem_pool_destroy (pool);
}
}
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
*shm_info_out = shm_info;
return CAIRO_STATUS_SUCCESS;
}
void
_cairo_xcb_shm_info_destroy (cairo_xcb_shm_info_t *shm_info)
{
cairo_xcb_connection_t *connection = shm_info->connection;
CAIRO_MUTEX_LOCK (connection->shm_mutex);
_cairo_xcb_shm_mem_pool_free (shm_info->pool, shm_info->mem);
_cairo_freepool_free (&connection->shm_info_freelist, shm_info);
if (! cairo_list_is_singular (&connection->shm_pools) &&
_cairo_xcb_connection_take_socket (connection) == CAIRO_STATUS_SUCCESS)
{
cairo_xcb_shm_mem_pool_t *pool, *next;
cairo_list_t head;
cairo_list_init (&head);
cairo_list_move (connection->shm_pools.next, &head);
cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
&connection->shm_pools, link)
{
if (pool->free_bytes == pool->max_bytes) {
_cairo_xcb_connection_shm_detach (connection, pool->shmseg);
_cairo_xcb_shm_mem_pool_destroy (pool);
}
}
cairo_list_move (head.next, &connection->shm_pools);
}
CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
}
void
_cairo_xcb_connection_shm_mem_pools_fini (cairo_xcb_connection_t *connection)
{
while (! cairo_list_is_empty (&connection->shm_pools)) {
_cairo_xcb_shm_mem_pool_destroy (cairo_list_first_entry (&connection->shm_pools,
cairo_xcb_shm_mem_pool_t,
link));
}
}