/*
 * Copyright (C) 2010-2020 Red Hat, Inc.
 *
 * Author: Angus Salkeld <asalkeld@redhat.com>
 *
 * 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 <http://www.gnu.org/licenses/>.
 */
#ifndef QB_RB_H_DEFINED
#define QB_RB_H_DEFINED

/* *INDENT-OFF* */
#ifdef __cplusplus
extern "C" {
#endif
/* *INDENT-ON* */

#include <sys/types.h>
#include <stdint.h>

/**
 * @file qbrb.h
 * This implements a ring buffer that works in "chunks", not bytes.
 * So you write/read a complete chunk or not at all.
 * There are two types of ring buffer: normal and overwrite.
 * Overwrite will reclaim the oldest chunks inorder to make way for new ones,
 * the normal version will refuse to write a new chunk if the ring buffer
 * is full.
 *
 * This implementation is capable of working across processes, but one process
 * must only write and the other process read.
 *
 * The read process will do the following:
 * @code
 *	rb = qb_rb_open("test2", 2000, QB_RB_FLAG_SHARED_PROCESS|QB_RB_FLAG_CREATE);
 *	for (i = 0; i < 200; i++) {
 * try_read_again:
 *		l = qb_rb_chunk_read(rb, (void *)out, 32, 1000);
 *		if (l < 0) {
 *			goto try_read_again;
 *		}
 *	}
 *	...
 *	qb_rb_close(rb);
 * 
 * @endcode
 *
 * The write process will do the following:
 * @code
 *	rb = qb_rb_open("test2", 2000, QB_RB_FLAG_SHARED_PROCESS);
 *	for (i = 0; i < 200; i++) {
 * try_write_again:
 *		l = qb_rb_chunk_write(rb, &v, sizeof(v));
 *		if (l < sizeof(v)) {
 *			goto try_write_again;
 *		}
 *	}
 *	...
 *	qb_rb_close(rb);
 * @endcode
 *
 * @author Angus Salkeld <asalkeld@redhat.com>
 */

/**
 * Create a ring buffer (rather than open and existing one).
 * @see qb_rb_open()
 */
#define QB_RB_FLAG_CREATE		0x01
/**
 * New calls to qb_rb_chunk_write() will call qb_rb_chunk_reclaim()
 * if there is not enough space.
 * If this is not set then new writes will be refused.
 * @see qb_rb_open()
 */
#define QB_RB_FLAG_OVERWRITE		0x02
/**
 * The ringbuffer will be shared between pthreads not processes.
 * This effects the type of locks/semaphores that are used.
 * @see qb_rb_open()
 */
#define QB_RB_FLAG_SHARED_THREAD	0x04
/**
 * The ringbuffer will be shared between processes.
 * This effects the type of locks/semaphores that are used.
 * @see qb_rb_open()
 */
#define QB_RB_FLAG_SHARED_PROCESS	0x08

/**
 * Don't use semaphores, only atomic ops.
 * This mean that the timeout passed into qb_rb_chunk_read()
 * will be ignored.
 */
#define QB_RB_FLAG_NO_SEMAPHORE		0x10

struct qb_ringbuffer_s;
typedef struct qb_ringbuffer_s qb_ringbuffer_t;

/**
 * Create the ring buffer with the given type.
 *
 * This creates allocates a ring buffer in shared memory.
 *
 * @param name the unique name of this ringbuffer.
 * @param size the requested size.
 * @param flags or'ed flags
 * @param shared_user_data_size size for a shared data area.
 * @note the actual size will be rounded up to the next page size.
 * @return a new ring buffer or NULL if there was a problem.
 * @see QB_RB_FLAG_CREATE, QB_RB_FLAG_OVERWRITE, QB_RB_FLAG_SHARED_THREAD, QB_RB_FLAG_SHARED_PROCESS
 */
qb_ringbuffer_t *qb_rb_open(const char *name, size_t size, uint32_t flags,
			    size_t shared_user_data_size);

/**
 * Dereference the ringbuffer and, if we are the last user, destroy it.
 *
 * All files, mmaped memory, semaphores and locks will be destroyed.
 *
 * @param rb ringbuffer instance
 */
void qb_rb_close(qb_ringbuffer_t * rb);

/**
 * Get the name of the ringbuffer.
 * @param rb ringbuffer instance
 * @return name.
 */
char *qb_rb_name_get(qb_ringbuffer_t * rb);

/**
 * Get a point to user shared data area.
 *
 * @note this is of size "shared_user_data_size" passed into qb_rb_open()
 *
 * @param rb ringbuffer instance
 * @return pointer to shared data.
 */
void *qb_rb_shared_user_data_get(qb_ringbuffer_t * rb);

/**
 * Write a chunk to the ring buffer.
 *
 * This simply calls qb_rb_chunk_alloc() and then
 * qb_rb_chunk_commit().
 *
 * @param rb ringbuffer instance
 * @param data (in) the data to write
 * @param len (in) the size of the chunk.
 * @return the amount of bytes actually buffered (either len or -1).
 *
 * @see qb_rb_chunk_alloc()
 * @see qb_rb_chunk_commit()
 */
ssize_t qb_rb_chunk_write(qb_ringbuffer_t * rb, const void *data, size_t len);

/**
 * Allocate space for a chunk of the given size.
 *
 * If type == QB_RB_FLAG_OVERWRITE and NULL is returned, memory corruption of
 * the memory file has occurred. The ringbuffer should be destroyed.
 * If type == QB_RB_NORMAL then when there is not enough space it will
 * return NULL.
 *
 * @param rb ringbuffer instance
 * @param len (in) the size to allocate.
 * @return pointer to chunk to write to, or NULL (if no space).
 *
 * @see qb_rb_chunk_alloc()
 */
void *qb_rb_chunk_alloc(qb_ringbuffer_t * rb, size_t len);

/**
 * Finalize the chunk.
 * @param rb ringbuffer instance
 * @param len (in) the size of the chunk.
 */
int32_t qb_rb_chunk_commit(qb_ringbuffer_t * rb, size_t len);

/**
 * Read (without reclaiming) the last chunk.
 *
 * This function is a way of accessing the next chunk without a memcpy().
 * You can read the chunk data in place.
 *
 * @note This function will not "pop" the chunk, you will need to call
 * qb_rb_chunk_reclaim().
 * @param rb ringbuffer instance
 * @param data_out (out) a pointer to the next chunk to read (not copied).
 * @param ms_timeout (in) time to wait for new data.
 *
 * @return the size of the chunk (0 if buffer empty).
 */
ssize_t qb_rb_chunk_peek(qb_ringbuffer_t * rb, void **data_out,
			 int32_t ms_timeout);

/**
 * Reclaim the oldest chunk.
 * You will need to call this if using qb_rb_chunk_peek(). 
 * @param rb ringbuffer instance
 */
void qb_rb_chunk_reclaim(qb_ringbuffer_t * rb);

/**
 * Read the oldest chunk into data_out.
 *
 * This is the same as qb_rb_chunk_peek() memcpy() and qb_rb_chunk_reclaim().
 *
 * @param rb ringbuffer instance
 * @param data_out (in/out) the chunk will be memcpy'ed into this.
 * @param len (in) the size of data_out.
 * @param ms_timeout the amount od time to wait for new data.
 * @return the size of the chunk, or error.
 */
ssize_t qb_rb_chunk_read(qb_ringbuffer_t * rb, void *data_out, size_t len,
			 int32_t ms_timeout);

/**
 * Get the reference count.
 *
 * @param rb ringbuffer instance
 * @return the number of references
 */
int32_t qb_rb_refcount_get(qb_ringbuffer_t * rb);

/**
 * The amount of free space in the ring buffer.
 *
 * @note Some of this space will be consumed by the chunk headers.
 * @param rb ringbuffer instance
 */
ssize_t qb_rb_space_free(qb_ringbuffer_t * rb);

/**
 * The total amount of data in the buffer.
 *
 * @note This includes the chunk headers (8 bytes per chunk).
 * @param rb ringbuffer instance
 */
ssize_t qb_rb_space_used(qb_ringbuffer_t * rb);

/**
 * The total number of chunks in the buffer.
 *
 * @param rb ringbuffer instance
 */
ssize_t qb_rb_chunks_used(qb_ringbuffer_t * rb);

/**
 * Write the contents of the Ring Buffer to file.
 * @param fd open file to write the ringbuffer data to.
 * @param rb ringbuffer instance
 * @see qb_rb_create_from_file()
 */
ssize_t qb_rb_write_to_file(qb_ringbuffer_t * rb, int32_t fd);

/**
 * Load the saved ring buffer from file into temporary memory.
 * @param fd file with saved ringbuffer data.
 * @param flags same flags as passed into qb_rb_open()
 * @return new ringbuffer instance
 * @see qb_rb_write_to_file()
 */
qb_ringbuffer_t *qb_rb_create_from_file(int32_t fd, uint32_t flags);

/**
 * Like 'chown', it changes the owner and group of the ringbuffer's
 * resources.
 * @param owner uid of the owner to change to
 * @param group gid of the group to change to
 * @param rb ringbuffer instance
 * @return status (0 = ok, -errno for error)
 */
int32_t qb_rb_chown(qb_ringbuffer_t * rb, uid_t owner, gid_t group);

/**
 * Like 'chmod', it changes the mode of the ringbuffer's resources.
 * @param mode mode to change to
 * @param rb ringbuffer instance
 * @retval 0 == ok
 * @retval -errno for error
 */
int32_t qb_rb_chmod(qb_ringbuffer_t * rb, mode_t mode);

/* *INDENT-OFF* */
#ifdef __cplusplus
}
#endif /* __cplusplus */
/* *INDENT-ON* */

#endif /* QB_RB_H_DEFINED */
