/*************************************************************************
 * @file dy_shm.c
 * @brief Shared memory APIs
 *
 *
 *************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

#include "dy_shm.h"

#define log_error printf
#define dy_syslog(LOG_USER | LOG_INFO,  printf

/**
 * @brief create shared memory with specified key
 *
 * @param shmid pointer to where to store created shared memory id (dy_shn_t)
 * @param key unique key value to use to share this memory with other processes
 * @param size The size of the memory segment to create
 */
void *dy_shm_create(dy_shm_t *shmid, dy_shm_key_t key, size_t size, dy_sem_t *sem_id)
{
    int shm_id;
    char *shm_addr = NULL;
    int first_create = 0;

    /* try get shared memory ID with specified key */
    shm_id = shmget(key, size + sizeof(dy_shm_header_t), IPC_CREAT | IPC_EXCL | DY_SHM_MODE);
    /* Check whether shared memory created */
    if (shm_id == -1)
    {
        if (errno == EEXIST)
        {
            dy_syslog(LOG_USER | LOG_INFO, ("get an exist key:%x\n", key);
                      shm_id = shmget(key, size + sizeof(dy_shm_header_t), DY_SHM_MODE);
        }
        else
        {
            dy_syslog(LOG_ERR, "Error creating shared memory %x of size %lu (error %d)", key, size, errno);
        }
    }
    else
    {
        dy_syslog(LOG_USER | LOG_INFO, ("first create the key:%x\n", key);
                  first_create = 1;
    }
    /* attach shared memory to process */
    shm_addr = shmat(shm_id, NULL, 0);
    if (shm_addr == (char *) - 1L)
    {
        dy_syslog(LOG_ERR, "Error mapping shared memory %x of size %lu (error %d)", key, size, errno);
    }
    if (shmid != NULL)
    {
        *shmid = shm_id;
    }

    *sem_id = dy_sem_create((dy_sem_key_t)key + 1, 1);

    if (first_create == 1)
    {
        /*
         * NOTE: The first user create the share memory need make sure there is no
         * junk data in it. even maybe zero it when creating a shared memory segment.
         */
        memset(shm_addr, 0, size + sizeof(dy_shm_header_t));
    }

    dy_syslog(LOG_USER | LOG_INFO, ("Fetch shared memory %x of size %lu, address at %p", key, size, shm_addr);

              (void)dy_atomic_inc(((dy_shm_header_t *)shm_addr)->counter);

              return shm_addr + sizeof(dy_shm_header_t);
}

void dy_shm_destroy(dy_shm_t shmid, void *in_addr, dy_sem_t sem_id)
{
    char *addr = ((char *)in_addr) - sizeof(dy_shm_header_t);
    int  val = dy_atomic_dec(((dy_shm_header_t *)addr)->counter);
    // Yes - there's a small race condition here.  I (mjq) don't believe that it's a big
    // enough probability to actually try to deal with, given the way shared memory is used in
    // our system - it's not being dynamically created and destroyed to where someone might be
    // creating it just as we're destroying it.....
    // The atomic adds are so that two people creating at the same time don't mess up the counter.
    //
    dy_syslog(LOG_USER | LOG_INFO, ("shm desctroy shmid:%x addr:%p val:%d\n", shmid, addr, val);
              if (shmdt(addr) == 0)
{
    if (val == 0)
        {
            if (shmctl(shmid, IPC_RMID, NULL) < 0)
            {
                dy_syslog(LOG_ERR, "Error destroying shared memory at %p (error %d)", addr, errno);
            }
            dy_sem_destroy(sem_id);
        }
    }
    else
    {
        dy_syslog(LOG_ERR, "Error detaching shared memory at %p (error %d)", addr, errno);
    }
}


dy_sem_t dy_sem_create(dy_sem_key_t key, int count)
{
    dy_sem_t sem_id;
    struct sembuf sbuf;

retry:
    sem_id = semget(key, 0, 0);
    if (sem_id == -1)
    {
        if (errno == ENOENT)
        {
            sem_id = semget(key, 1, IPC_CREAT | DY_SEM_MODE | IPC_EXCL);
            if (sem_id == -1)
            {
                if (errno == EEXIST)
                {
                    goto retry;
                }
                dy_syslog(LOG_ERR, "Could not create semaphore for key %x", key);
            }
            dy_syslog(LOG_USER | LOG_INFO, ("Semaphore id %x created for key %x", sem_id, key);
                      sbuf.sem_num = 0;
                      sbuf.sem_op = count;
                      sbuf.sem_flg = 0;
                      if (semop(sem_id, &sbuf, 1) != 0)
        {
            dy_syslog(LOG_ERR, "semop failed for key %x with error %d", key, errno);
            }
        }
        else
        {
            dy_syslog(LOG_ERR, "Fetch of semaphore %x failed with error %d", key, errno);
        }
    }
    return sem_id;
}

/**
 * @brief Wait for a semaphore, but return if an EINTR occurs.
 * @param sem_id the semaphore id
 * @return true if we got the semaphore, false if we were interrupted.
 */
Boolean dy_sem_wait_interrupt(dy_sem_t sem_id)
{
    struct sembuf  sem_op;

    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sem_op, 1) < 0)
    {
        if (errno != EAGAIN && errno != EINTR)
        {
            dy_syslog(LOG_ERR, "Wait on semaphore %x failed with error %d", sem_id, errno);
        }
        return FALSE;
    }
    return TRUE;
}

/**
 * @brief Wait for a semaphore
 * @param sem_id the semaphore id
 */
void dy_sem_wait(dy_sem_t sem_id)
{
    struct sembuf  sem_op;

    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = SEM_UNDO;

    while (semop(sem_id, &sem_op, 1) < 0)
    {
        if (errno != EAGAIN && errno != EINTR)
        {
            dy_syslog(LOG_ERR, "Wait on semaphore %x failed with error %d", sem_id, errno);
        }
    }
}

/**
 * @brief Signal the semaphore (i.e. wake up waiters)
 * @param sem_id the semaphore id
 * @return 0 if signal execute, -1 if an error occurred.
 */
void dy_sem_signal(dy_sem_t sem_id)
{
    struct sembuf  sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = 1;
    sem_op.sem_flg = SEM_UNDO;

    while (semop(sem_id, &sem_op, 1) < 0)
    {
        if (errno != EAGAIN && errno != EINTR)
        {
            dy_syslog(LOG_ERR, "Semaphore %x signalling failed with error %d", sem_id, errno);
        }
    }
}

/**
 * @brief Destroy the semaphore.
 * @param sem_id the semaphore id to destroy
 */
void dy_sem_destroy(dy_sem_t sem_id)
{
    if (semctl(sem_id, IPC_RMID, 0) == -1)
    {
        dy_syslog(LOG_ERR, "Semaphore %x could not be destroyed - error %d", sem_id, errno);
    }
}
