/* * Copyright (C) 2010-2011 Red Hat, Inc. * * Author: Angus Salkeld * * This file is part of libqb. * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "ringbuffer_int.h" #include #include /* * #define CRAZY_DEBUG_PRINTFS 1 */ #ifdef CRAZY_DEBUG_PRINTFS #define DEBUG_PRINTF(format, args...) \ do { \ printf(format, ##args); \ } while(0) #else #define DEBUG_PRINTF(format, args...) #endif /* CRAZY_DEBUG_PRINTFS */ /* the chunk header is two words * 1) the chunk data size * 2) the magic number */ #define QB_RB_CHUNK_HEADER_WORDS 2 #define QB_RB_CHUNK_HEADER_SIZE (sizeof(uint32_t) * QB_RB_CHUNK_HEADER_WORDS) #define QB_RB_CHUNK_MAGIC 0xAAAAAAAA #define QB_RB_CHUNK_SIZE_GET(rb, pointer) \ rb->shared_data[pointer] #define QB_RB_CHUNK_MAGIC_GET(rb, pointer) \ rb->shared_data[(pointer + 1) % rb->shared_hdr->size] #define QB_RB_WRITE_PT_INDEX (rb->shared_hdr->size) #define QB_RB_READ_PT_INDEX (rb->shared_hdr->size + 1) #define idx_step(idx) \ do { \ if (idx > (rb->shared_hdr->size - 1)) { \ idx = ((idx) % (rb->shared_hdr->size)); \ } \ } while (0) /* * move the write pointer to the next 32 byte boundary * write_pt goes in 4 bytes (sizeof(uint32_t)) */ /* * #define USE_CACHE_LINE_ALIGNMENT 1 */ #ifdef USE_CACHE_LINE_ALIGNMENT #define idx_cache_line_step(idx) \ do { \ if (idx % 8) { \ idx += (8 - (idx % 8)); \ } \ if (idx > (rb->shared_hdr->size - 1)) { \ idx = ((idx) % (rb->shared_hdr->size)); \ } \ } while (0) #else #define idx_cache_line_step(idx) \ do { \ if (idx > (rb->shared_hdr->size - 1)) { \ idx = ((idx) % (rb->shared_hdr->size)); \ } \ } while (0) #endif static void qb_rb_chunk_check(qb_ringbuffer_t * rb, uint32_t pointer); qb_ringbuffer_t * qb_rb_open(const char *name, size_t size, uint32_t flags, size_t shared_user_data_size) { struct qb_ringbuffer_s *rb; size_t real_size; size_t shared_size; char path[PATH_MAX]; int32_t fd_hdr; int32_t fd_data; uint32_t file_flags = O_RDWR; char filename[PATH_MAX]; int32_t error = 0; void *shm_addr; long page_size = sysconf(_SC_PAGESIZE); #ifdef QB_FORCE_SHM_ALIGN page_size = QB_MAX(page_size, 16 * 1024); #endif /* QB_FORCE_SHM_ALIGN */ real_size = QB_ROUNDUP(size, page_size); shared_size = sizeof(struct qb_ringbuffer_shared_s) + shared_user_data_size; if (flags & QB_RB_FLAG_CREATE) { file_flags |= O_CREAT | O_TRUNC; } rb = calloc(1, sizeof(struct qb_ringbuffer_s)); if (rb == NULL) { return NULL; } /* * Create a shared_hdr memory segment for the header. */ snprintf(filename, PATH_MAX, "qb-%s-header", name); fd_hdr = qb_sys_mmap_file_open(path, filename, shared_size, file_flags); if (fd_hdr < 0) { error = fd_hdr; qb_util_log(LOG_ERR, "couldn't create file for mmap"); goto cleanup_hdr; } rb->shared_hdr = mmap(0, shared_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_hdr, 0); if (rb->shared_hdr == MAP_FAILED) { error = -errno; qb_util_log(LOG_ERR, "couldn't create mmap for header"); goto cleanup_hdr; } rb->flags = flags; /* * create the semaphore */ if (flags & QB_RB_FLAG_CREATE) { rb->shared_data = NULL; /* rb->shared_hdr->size tracks data by ints and not bytes/chars. */ rb->shared_hdr->size = real_size / sizeof(uint32_t); rb->shared_hdr->write_pt = 0; rb->shared_hdr->read_pt = 0; strncpy(rb->shared_hdr->hdr_path, path, PATH_MAX); } error = qb_rb_sem_create(rb, flags); if (error < 0) { qb_util_perror(LOG_ERR, "couldn't get a semaphore"); goto cleanup_hdr; } /* Create the shared_data memory segment for the actual ringbuffer. * They have to be separate. */ if (flags & QB_RB_FLAG_CREATE) { snprintf(filename, PATH_MAX, "qb-%s-data", name); fd_data = qb_sys_mmap_file_open(path, filename, real_size, file_flags); strncpy(rb->shared_hdr->data_path, path, PATH_MAX); } else { fd_data = qb_sys_mmap_file_open(path, rb->shared_hdr->data_path, real_size, file_flags); } if (fd_data < 0) { error = fd_data; qb_util_log(LOG_ERR, "couldn't create file for mmap"); goto cleanup_hdr; } qb_util_log(LOG_DEBUG, "shm size:%zd; real_size:%zd; rb->size:%d", size, real_size, rb->shared_hdr->size); error = qb_sys_circular_mmap(fd_data, &shm_addr, real_size); rb->shared_data = shm_addr; if (error != 0) { qb_util_log(LOG_ERR, "couldn't create circular mmap on %s", rb->shared_hdr->data_path); goto cleanup_data; } if (flags & QB_RB_FLAG_CREATE) { memset(rb->shared_data, 0, real_size); rb->shared_data[rb->shared_hdr->size] = 5; rb->shared_hdr->ref_count = 1; } else { qb_atomic_int_inc(&rb->shared_hdr->ref_count); } close(fd_hdr); close(fd_data); return rb; cleanup_data: close(fd_data); if (flags & QB_RB_FLAG_CREATE) { unlink(rb->shared_hdr->data_path); } cleanup_hdr: if (fd_hdr >= 0) { close(fd_hdr); } if (rb && (flags & QB_RB_FLAG_CREATE)) { unlink(rb->shared_hdr->hdr_path); (void)rb->sem_destroy_fn(rb); } if (rb && (rb->shared_hdr != MAP_FAILED && rb->shared_hdr != NULL)) { munmap(rb->shared_hdr, sizeof(struct qb_ringbuffer_shared_s)); } free(rb); errno = -error; return NULL; } void qb_rb_close(qb_ringbuffer_t * rb) { if (rb == NULL) { return; } (void)qb_atomic_int_dec_and_test(&rb->shared_hdr->ref_count); if (rb->flags & QB_RB_FLAG_CREATE) { (void)rb->sem_destroy_fn(rb); unlink(rb->shared_hdr->data_path); unlink(rb->shared_hdr->hdr_path); qb_util_log(LOG_DEBUG, "Free'ing ringbuffer: %s", rb->shared_hdr->hdr_path); } else { qb_util_log(LOG_DEBUG, "Closing ringbuffer: %s", rb->shared_hdr->hdr_path); } munmap(rb->shared_data, (rb->shared_hdr->size * sizeof(uint32_t)) << 1); munmap(rb->shared_hdr, sizeof(struct qb_ringbuffer_shared_s)); free(rb); } void qb_rb_force_close(qb_ringbuffer_t * rb) { if (rb == NULL) { return; } (void)rb->sem_destroy_fn(rb); unlink(rb->shared_hdr->data_path); unlink(rb->shared_hdr->hdr_path); qb_util_log(LOG_DEBUG, "Force free'ing ringbuffer: %s", rb->shared_hdr->hdr_path); munmap(rb->shared_data, (rb->shared_hdr->size * sizeof(uint32_t)) << 1); munmap(rb->shared_hdr, sizeof(struct qb_ringbuffer_shared_s)); free(rb); } char * qb_rb_name_get(qb_ringbuffer_t * rb) { if (rb == NULL) { return NULL; } return rb->shared_hdr->hdr_path; } void * qb_rb_shared_user_data_get(qb_ringbuffer_t * rb) { if (rb == NULL) { return NULL; } return rb->shared_hdr->user_data; } int32_t qb_rb_refcount_get(qb_ringbuffer_t * rb) { if (rb == NULL) { return -EINVAL; } return qb_atomic_int_get(&rb->shared_hdr->ref_count); } ssize_t qb_rb_space_free(qb_ringbuffer_t * rb) { uint32_t write_size; uint32_t read_size; size_t space_free = 0; if (rb == NULL) { return -EINVAL; } write_size = rb->shared_hdr->write_pt; /* * TODO idx_cache_line_step (write_size); */ read_size = rb->shared_hdr->read_pt; if (write_size > read_size) { space_free = (read_size - write_size + rb->shared_hdr->size) - 1; } else if (write_size < read_size) { space_free = (read_size - write_size) - 1; } else { space_free = rb->shared_hdr->size; } /* word -> bytes */ return (space_free * sizeof(uint32_t)); } ssize_t qb_rb_space_used(qb_ringbuffer_t * rb) { uint32_t write_size; uint32_t read_size; size_t space_used; if (rb == NULL) { return -EINVAL; } write_size = rb->shared_hdr->write_pt; read_size = rb->shared_hdr->read_pt; if (write_size > read_size) { space_used = write_size - read_size; } else if (write_size < read_size) { space_used = (write_size - read_size + rb->shared_hdr->size) - 1; } else { space_used = 0; } /* word -> bytes */ return (space_used * sizeof(uint32_t)); } ssize_t qb_rb_chunks_used(struct qb_ringbuffer_s *rb) { if (rb == NULL) { return -EINVAL; } return rb->sem_getvalue_fn(rb); } void * qb_rb_chunk_alloc(qb_ringbuffer_t * rb, size_t len) { uint32_t write_pt; if (rb == NULL) { return NULL; } /* * Reclaim data if we are over writing and we need space */ if (rb->flags & QB_RB_FLAG_OVERWRITE) { while (qb_rb_space_free(rb) < (len + QB_RB_CHUNK_HEADER_SIZE + 4)) { qb_rb_chunk_reclaim(rb); } } else { if (qb_rb_space_free(rb) < (len + QB_RB_CHUNK_HEADER_SIZE + 4)) { errno = EAGAIN; return NULL; } } write_pt = rb->shared_hdr->write_pt; /* * insert the chunk header */ rb->shared_data[write_pt++] = 0; idx_step(write_pt); rb->shared_data[write_pt++] = QB_RB_CHUNK_MAGIC; idx_step(write_pt); /* * return a pointer to the beginning of the chunk data */ return (void *)&rb->shared_data[write_pt]; } static uint32_t qb_rb_chunk_step(qb_ringbuffer_t * rb, uint32_t pointer) { uint32_t chunk_size = QB_RB_CHUNK_SIZE_GET(rb, pointer); /* * skip over the chunk header */ pointer += QB_RB_CHUNK_HEADER_WORDS; idx_step(pointer); /* * skip over the user's data. */ pointer += (chunk_size / sizeof(uint32_t)); /* make allowance for non-word sizes */ if ((chunk_size % sizeof(uint32_t)) != 0) { pointer++; } idx_cache_line_step(pointer); return pointer; } int32_t qb_rb_chunk_commit(qb_ringbuffer_t * rb, size_t len) { uint32_t old_write_pt; if (rb == NULL) { return -EINVAL; } /* * commit the magic & chunk_size */ old_write_pt = rb->shared_hdr->write_pt; rb->shared_data[old_write_pt] = len; rb->shared_data[old_write_pt + 1] = QB_RB_CHUNK_MAGIC; /* * commit the new write pointer */ rb->shared_hdr->write_pt = qb_rb_chunk_step(rb, old_write_pt); DEBUG_PRINTF("%s: read: %u, write: %u (was:%u)\n", __func__, rb->shared_hdr->read_pt, rb->shared_hdr->write_pt, old_write_pt); /* * post the notification to the reader */ return rb->sem_post_fn(rb); } ssize_t qb_rb_chunk_write(qb_ringbuffer_t * rb, const void *data, size_t len) { char *dest = qb_rb_chunk_alloc(rb, len); int32_t res = 0; if (rb == NULL) { return -EINVAL; } if (dest == NULL) { return -errno; } memcpy(dest, data, len); res = qb_rb_chunk_commit(rb, len); if (res < 0) { return res; } return len; } void qb_rb_chunk_reclaim(qb_ringbuffer_t * rb) { uint32_t old_read_pt; if (rb == NULL || qb_rb_space_used(rb) == 0) { return; } old_read_pt = rb->shared_hdr->read_pt; qb_rb_chunk_check(rb, old_read_pt); rb->shared_hdr->read_pt = qb_rb_chunk_step(rb, old_read_pt); /* * clear the header */ rb->shared_data[old_read_pt] = 0; rb->shared_data[old_read_pt + 1] = 0; DEBUG_PRINTF("%s: read: %u (was:%u), write: %u\n", __func__, rb->shared_hdr->read_pt, old_read_pt, rb->shared_hdr->write_pt); } ssize_t qb_rb_chunk_peek(qb_ringbuffer_t * rb, void **data_out, int32_t timeout) { uint32_t read_pt; uint32_t chunk_size; uint32_t chunk_magic; int32_t res; if (rb == NULL) { return -EINVAL; } res = rb->sem_timedwait_fn(rb, timeout); if (res < 0 && res != -EIDRM) { if (res != -ETIMEDOUT) { qb_util_perror(LOG_ERR, "sem_timedwait"); } return res; } read_pt = rb->shared_hdr->read_pt; chunk_size = QB_RB_CHUNK_SIZE_GET(rb, read_pt); chunk_magic = QB_RB_CHUNK_MAGIC_GET(rb, read_pt); *data_out = &rb->shared_data[read_pt + QB_RB_CHUNK_HEADER_WORDS]; if (chunk_magic != QB_RB_CHUNK_MAGIC) { errno = ENOMSG; return 0; } else { return chunk_size; } } ssize_t qb_rb_chunk_read(qb_ringbuffer_t * rb, void *data_out, size_t len, int32_t timeout) { uint32_t read_pt; uint32_t chunk_size; int32_t res = 0; if (rb == NULL) { return -EINVAL; } if (rb->sem_timedwait_fn) { res = rb->sem_timedwait_fn(rb, timeout); } if (res < 0 && res != -EIDRM) { if (res != -ETIMEDOUT) { qb_util_perror(LOG_ERR, "sem_timedwait"); } return res; } if (qb_rb_space_used(rb) == 0) { return -ENOMSG; } read_pt = rb->shared_hdr->read_pt; qb_rb_chunk_check(rb, read_pt); chunk_size = QB_RB_CHUNK_SIZE_GET(rb, read_pt); if (len < chunk_size) { return -ENOBUFS; } memcpy(data_out, &rb->shared_data[read_pt + QB_RB_CHUNK_HEADER_WORDS], chunk_size); qb_rb_chunk_reclaim(rb); return chunk_size; } static void print_header(qb_ringbuffer_t * rb) { printf("Ringbuffer: \n"); if (rb->flags & QB_RB_FLAG_OVERWRITE) { printf(" ->OVERWRITE\n"); } else { printf(" ->NORMAL\n"); } printf(" ->write_pt [%d]\n", rb->shared_hdr->write_pt); printf(" ->read_pt [%d]\n", rb->shared_hdr->read_pt); printf(" ->size [%d words]\n", rb->shared_hdr->size); #ifndef S_SPLINT_S printf(" =>free [%zu bytes]\n", qb_rb_space_free(rb)); printf(" =>used [%zu bytes]\n", qb_rb_space_used(rb)); #endif /* S_SPLINT_S */ } static void qb_rb_chunk_check(qb_ringbuffer_t * rb, uint32_t pointer) { uint32_t chunk_size; uint32_t chunk_magic = QB_RB_CHUNK_MAGIC_GET(rb, pointer); if (chunk_magic != QB_RB_CHUNK_MAGIC) { chunk_size = QB_RB_CHUNK_SIZE_GET(rb, pointer); printf("size: %x\n", chunk_size); printf("magic: %x\n", chunk_magic); print_header(rb); assert(0); } } ssize_t qb_rb_write_to_file(qb_ringbuffer_t * rb, int32_t fd) { ssize_t result; ssize_t written_size = 0; if (rb == NULL) { return -EINVAL; } print_header(rb); result = write(fd, &rb->shared_hdr->size, sizeof(uint32_t)); if (result != sizeof(uint32_t)) { return -errno; } written_size += result; result = write(fd, rb->shared_data, rb->shared_hdr->size * sizeof(uint32_t)); if (result != rb->shared_hdr->size * sizeof(uint32_t)) { return -errno; } written_size += result; /* * store the read & write pointers */ result = write(fd, (void *)&rb->shared_hdr->write_pt, sizeof(uint32_t)); if (result != sizeof(uint32_t)) { return -errno; } written_size += result; result = write(fd, (void *)&rb->shared_hdr->read_pt, sizeof(uint32_t)); if (result != sizeof(uint32_t)) { return -errno; } written_size += result; qb_util_log(LOG_DEBUG, " writing total of: %zd\n", written_size); return written_size; } qb_ringbuffer_t * qb_rb_create_from_file(int32_t fd, uint32_t flags) { ssize_t n_read; size_t n_required; size_t total_read = 0; uint32_t read_pt; uint32_t write_pt; qb_ringbuffer_t *rb = calloc(1, sizeof(qb_ringbuffer_t)); if (rb == NULL || fd < 0) { return NULL; } rb->shared_hdr = calloc(1, sizeof(struct qb_ringbuffer_shared_s)); if (rb->shared_hdr == NULL) { goto cleanup_fail2; } rb->flags = flags; n_required = sizeof(uint32_t); n_read = read(fd, &rb->shared_hdr->size, n_required); if (n_read != n_required) { qb_util_perror(LOG_ERR, "Unable to read blackbox file header"); goto cleanup_fail; } total_read += n_read; n_required = (rb->shared_hdr->size * sizeof(uint32_t)); if ((rb->shared_data = malloc(n_required)) == NULL) { qb_util_perror(LOG_ERR, "exhausted virtual memory"); goto cleanup_fail; } n_read = read(fd, rb->shared_data, n_required); if (n_read < 0) { qb_util_perror(LOG_ERR, "Unable to read blackbox file data"); goto cleanup_fail; } total_read += n_read; if (n_read != n_required) { qb_util_log(LOG_WARNING, "read %zd bytes, but expected %zu", n_read, n_required); goto cleanup_fail; } n_read = read(fd, &write_pt, sizeof(uint32_t)); assert(n_read == sizeof(uint32_t)); rb->shared_hdr->write_pt = write_pt; total_read += n_read; n_read = read(fd, &read_pt, sizeof(uint32_t)); assert(n_read == sizeof(uint32_t)); rb->shared_hdr->read_pt = read_pt; total_read += n_read; qb_util_log(LOG_DEBUG, "read total of: %zd", total_read); print_header(rb); return rb; cleanup_fail: free(rb->shared_hdr); cleanup_fail2: free(rb); return NULL; } int32_t qb_rb_chown(qb_ringbuffer_t * rb, uid_t owner, gid_t group) { int32_t res; if (rb == NULL) { return -EINVAL; } res = chown(rb->shared_hdr->data_path, owner, group); if (res < 0) { return -errno; } res = chown(rb->shared_hdr->hdr_path, owner, group); if (res < 0) { return -errno; } return 0; }