#include <sys/types.h>
#include <errno.h>
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "sys/ipc.h"
#include "sys/sem.h"
#include "sys/shm.h"

static struct sem_set sem_sets[N_SEM_SETS+1];

#define PERMISSIONS ( S_IRUSR | S_IWUSR  | S_IRGRP | S_IWGRP  | S_IROTH | S_IWOTH)

static int create_semaphore_set( int last_free, key_t key, int nsems, int semflg );

/*
 *
 */

int
semctl( int semid, int semnum, int cmd, union semun arg )
{
    register struct semaphore * sem;
    register i;

    if( semid <= 0 || semid > N_SEM_SETS )
      {
        errno = EINVAL;
        return -1;
      }

    if( sem_sets[semid].sem_nsems == 0 )
      {
        errno = EIDRM;
        return -1;
      }

    if( semnum >= sem_sets[semid].sem_nsems || semnum < 0 )
      {
        errno = EFBIG;
        return -1;
      }

    sem = (sem_sets[semid].semaphores) + semnum;

    switch( cmd )
      {
        case GETVAL:
          return sem->semval;

        case SETVAL:
          sem->semval = arg.val;
          break;

        case GETPID:
          return sem->sempid;

        case GETNCNT:
          return sem->semncnt;

        case GETZCNT:
          return sem->semzcnt;

        case GETALL:
          for( i = 0; i < sem_sets[semid].sem_nsems; i++ )
            {
              arg.array[i] = (sem_sets[semid].semaphores)[i].semval;
            }
          return sem->semval;

        case SETALL:
          for( i = 0; i < sem_sets[semid].sem_nsems; i++ )
            {
              (sem_sets[semid].semaphores)[i].semval = arg.array[i];
            }
        break;

        case IPC_STAT:
          arg.buf->sem_perm  = sem_sets[semid].sem_perm;
          arg.buf->sem_nsems = sem_sets[semid].sem_nsems;
          arg.buf->sem_otime = sem_sets[semid].sem_otime;
          arg.buf->sem_ctime = sem_sets[semid].sem_ctime;
          break;

        case IPC_SET:
          sem_sets[semid].sem_perm.uid  = arg.buf->sem_perm.uid;
          sem_sets[semid].sem_perm.gid  = arg.buf->sem_perm.gid;
          sem_sets[semid].sem_perm.mode = arg.buf->sem_perm.mode & PERMISSIONS;
          break;

        case IPC_RMID:
          sem_sets[semid].sem_nsems = 0;
          free( (void *)sem_sets[semid].semaphores );
          sem_sets[semid].semaphores = (struct semaphore *)NULL;

        default:
          errno = EINVAL;
          return -1;
      }

    errno = 0;
    return 0;
}

/*
 *
 */

int
semget( key_t key, int nsems, int semflg )
{
    register int i;
    register int id;
    register int last_free;

    /*
    fprintf(stderr,
            "*** entering semget: key = %d, nsems = %d, semflg = %o\n",
            key, nsems, semflg );
    */

    if( nsems < 0 )
      {
        errno = EINVAL;
        return -1;
      }

    id = 0;
    last_free = 0;

    if( key != IPC_PRIVATE )
      {
        for( i = 1; i <= N_SEM_SETS; i++)
          {
            if( sem_sets[i].sem_nsems == 0 )
              last_free = i;

            if( sem_sets[i].sem_perm.key == key )
              {
                id = i;
                break;
              }
          }
      }

    if( id == 0 ) /* There is no semaphores set associated with key */
      {
        /*
        if( (semflg & IPC_CREAT) || key == IPC_PRIVATE )
        */
          return create_semaphore_set( last_free, key, nsems, semflg );
        /*
        errno = ENOENT;
        return -1;
        */
      }
    else /* Semaphores set associated with key already exists */
      {
        if( (semflg & IPC_CREAT) && (semflg & IPC_EXCL) )
          {
            errno = EEXIST;
            return -1;
          }
        if( sem_sets[id].sem_nsems < nsems )
          {
            errno = EINVAL;
            return -1;
          }

        if( (( ~sem_sets[id].sem_perm.mode ) & (semflg & PERMISSIONS)) != 0 )
          {
            errno = EACCES;
            return -1;
          }

        /*
        fprintf( stderr, "*** found semid = %d\n", id );
        */
        errno = 0;
        return id;
      }
}

static int
create_semaphore_set( int last_free, key_t key, int nsems, int semflg )
{
    register int i;
    register struct semaphore * sem_array;

    if( nsems <= 0 )
      {
        errno = EINVAL;
        return -1;
      }

    i = 0;
    if( last_free == 0 )
      {
        for( i = 1; i <= N_SEM_SETS; i++ )
          {
            if( sem_sets[i].sem_nsems == 0 )
            {
              last_free = i;
              break;
            }
          }

        if( last_free == 0  )
          {
            errno = ENOSPC;
            return -1;
          }

      }

    if( (sem_array = (struct semaphore *)calloc(nsems, sizeof(struct semaphore)))
        ==
         (struct semaphore *)NULL
      )
      {
        errno = ENOSPC;
        return -1;
      }

    sem_sets[last_free].semaphores = sem_array;
    sem_sets[last_free].sem_nsems  = nsems;
    sem_sets[last_free].sem_otime  = 0;
    sem_sets[last_free].sem_ctime  = time((time_t)NULL);
    sem_sets[last_free].sem_perm.key   = key;
    sem_sets[last_free].sem_perm.cuid  = geteuid();
    sem_sets[last_free].sem_perm.uid   = sem_sets[last_free].sem_perm.cuid;
    sem_sets[last_free].sem_perm.cgid  = getegid();
    sem_sets[last_free].sem_perm.gid   = sem_sets[last_free].sem_perm.cgid;
    sem_sets[last_free].sem_perm.mode  = semflg & PERMISSIONS;

    /*
    fprintf(stderr, "*** created semid = %d\n", last_free );
    */

    errno = 0;
    return last_free;
}

/*
 *
 */

int
semop( int semid, struct sembuf * sops, int nsops )
{
    struct semaphore * sem;

    /*
    fprintf(stderr,
            "\n*** entering nsemop: semid = %d, sops->sem_op = %d, sops->sem_num = %d, nsops = %d\n",
            semid, sops->sem_op, sops->sem_num, nsops );
    */

    if( nsops != 1 )
      {
        errno = E2BIG;
        return -1;
      }

    if( semid <= 0 || semid > N_SEM_SETS )
      {
        errno = EINVAL;
        return -1;
      }

    /*
    fprintf(stderr,
            "*** sem_sets[semid].sem_perm.key = %d, sem_sets[semid].sem_nsems = %d\n",
            sem_sets[semid].sem_perm.key, sem_sets[semid].sem_nsems );
    */

    if( sem_sets[semid].sem_nsems == 0 )
      {
        errno = EIDRM;
        return -1;
      }

    if( sops->sem_num >= sem_sets[semid].sem_nsems || sops->sem_num < 0 )
      {
        errno = EFBIG;
        return -1;
      }

    sem = (sem_sets[semid].semaphores) + (sops->sem_num);

    if( sops->sem_op < 0 )
      {
        if( sem->semval + sops->sem_op >= 0 )
          {
            sem->semval += sops->sem_op;
          }
        else if( sops->sem_flg & IPC_CREAT )
          {
            errno = EAGAIN;
            return -1;
          }
        else
          {
            /* process must go to sleep */
            /*
            fprintf( stderr, "\n*** process must sleep ***\n" );
            errno = 0;
            return -1;
            */
            sem->semval += 0;
          }
      }
    else if( sops->sem_op > 0 )
      {
        sem->semval += sops->sem_op;
      }
    else
      {
        if( sem->semval != 0 )
          {
            if( sops->sem_flg & IPC_CREAT )
              {
                errno = EAGAIN;
                return -1;
              }
            else
              {
                /* process must go to sleep */
                fprintf( stderr, "\n*** process must sleep waiting 0 ***\n" );
                errno = 0;
                return -1;
              }
          }
      }

    errno = 0;
    return 0;
}

/*
 *
 */

struct shm_segment
  {
    key_t     key;  /* key */
    char    * name; /* path to memory-mapped file */
    int       fd;   /* file descriptor of meory-mapped file */
    int       size; /* size of shared region */
    caddr_t   mem;  /* local address of shared region */
  };

struct shm_segment shm_segments[N_SHM_SEGMENTS+1];
char * memory_mapped_file_path[MAXNAMLEN+1];

/*
 *
 */

int
kill_memory_mapped_file( int shmid )
{
    /*
    fprintf(stderr, "*** kill_memory_mapped_file: shmid = %d\n", shmid );
    */
    if( shmid <= 0 || shmid > N_SHM_SEGMENTS )
      {
        errno = EINVAL;
        return -1;
      }

    if( shm_segments[shmid].size == 0 )
      {
        errno = EIDRM;
        return -1;
      }

    munmap( shm_segments[shmid].mem, shm_segments[shmid].size );
    close( shm_segments[shmid].fd );
    unlink( shm_segments[shmid].name );

    free( shm_segments[shmid].name );

    shm_segments[shmid].mem  = (caddr_t)NULL;
    shm_segments[shmid].size = 0;
    shm_segments[shmid].name = (char *)0;
    shm_segments[shmid].fd   = 0;
    shm_segments[shmid].key  = (key_t)0;
    return 0;
}

/*
 *
 */

int
create_memory_mapped_file( key_t key, int size )
{
    int     last_free;
    char    printable_key[20];
    char *  name_ptr;
    int     descriptor;
    caddr_t address;
    int     i;

    if( size < 0 )
      {
        errno = EINVAL;
        return -1;
      }

    last_free = 0;
    for( i = 1; i <= N_SHM_SEGMENTS; i++ )
      {
        if( shm_segments[i].size == 0 )
          last_free = i;

        if( shm_segments[i].key == key )
          {
            if( shm_segments[i].size < size )
              {
                errno = EINVAL;
                return -1;
              }
            return i;
          }
      }

    if( size == 0 )
      {
        errno = EINVAL;
        return -1;
      }

    if( last_free == 0 )
      {
        errno = ENOSPC;
        return -1;
      }

    if( memory_mapped_file_path[0] == '\0' )
      {
        char * path;

        if( (path = getenv( "PGSHMPATH" )) != (char *)NULL )
          {
            strcpy( memory_mapped_file_path, (const char *)path );
          }
        else
          {
            strcpy( memory_mapped_file_path, DEFAULT_MEMORY_MAPPED_FILE_PATH );
          }
      }

    sprintf( printable_key, "%d", (int)key );

    if( (name_ptr = ( char *)malloc( strlen(memory_mapped_file_path) +
                                     strlen(printable_key) + 1
                                   )
        )
        == 
        ( char *) NULL
      )
      {
        errno = ENOMEM;
        return -1;
      }
    strcpy( name_ptr, (const char *)memory_mapped_file_path );
    strcat( name_ptr, (const char *)printable_key );

    if( (descriptor = open( (const char *)name_ptr,
                            (O_RDWR | O_CREAT),
                            PERMISSIONS
                          )
        )
        ==
        -1
      )
      {
        free( name_ptr );
        return -1;
      }

    if( lseek( descriptor, (off_t)(size-1), SEEK_SET ) == (off_t)-1   ||
        write( descriptor, "\0", (size_t)1 )           == (ssize_t)-1 
      )
      {
        close( descriptor );
        unlink( (const char *)name_ptr );
        free( name_ptr );
        return -1;
      }

    if( (address = mmap( (caddr_t)NULL,
                         size,
                         (PROT_READ | PROT_WRITE),
                         (MAP_FILE | MAP_SHARED),
                         descriptor,
                         (off_t)0
                       )
        )
        ==
        (caddr_t)-1
      )
      {
        close( descriptor );
        unlink( (const char *)name_ptr );
        free( name_ptr );
        return -1;
      }

    for( i = 0; i < size/(sizeof(int)); i++  )
      *( ((int*)address) + i) = 0;

    shm_segments[last_free].key  = key;
    shm_segments[last_free].name = name_ptr;
    shm_segments[last_free].fd   = descriptor;
    shm_segments[last_free].size = size;
    shm_segments[last_free].mem  = address;
    return last_free;
}

/*
 *
 */

int
attach_memory_mapped_file( key_t key, int size )
{
    int           last_free;
    char          printable_key[20];
    char        * name_ptr;
    int           descriptor;
    caddr_t       address;
    int           i;
    struct stat   file_info;

    if( size < 0 )
      {
        errno = EINVAL;
        return -1;
      }

    last_free = 0;
    for( i = 1; i <= N_SHM_SEGMENTS; i++ )
      {
        if( shm_segments[i].size == 0 )
          last_free = i;

        if( shm_segments[i].key == key )
          {
            if( shm_segments[i].size < size )
              {
                errno = EINVAL;
                return -1;
              }
            return i;
          }
      }

    if( last_free == 0 )
      {
        errno = ENOSPC;
        return -1;
      }

    if( memory_mapped_file_path[0] == '\0' )
      {
        char * path;

        if( (path = getenv( "PGSHMPATH" )) != (char *)NULL )
          {
            strcpy( memory_mapped_file_path, (const char *)path );
          }
        else
          {
            strcpy( memory_mapped_file_path, DEFAULT_MEMORY_MAPPED_FILE_PATH );
          }
      }

    sprintf( printable_key, "%d", (int)key );

    if( (name_ptr = ( char *)malloc( strlen(memory_mapped_file_path) +
                                     strlen(printable_key) + 1
                                   )
        )
        == 
        ( char *) NULL
      )
      {
        errno = ENOMEM;
        return -1;
      }
    strcpy( name_ptr, (const char *)memory_mapped_file_path );
    strcat( name_ptr, (const char *)printable_key );

    if( (descriptor = open( (const char *)name_ptr,
                            O_RDWR,
                            PERMISSIONS
                          )
        )
        ==
        -1
      )
      {
        free( name_ptr );
        return -1;
      }

    if( fstat( descriptor, &file_info ) == -1 )
      {
        free( name_ptr );
        close( descriptor );
        return -1;
      }

    if( file_info.st_size < size )
      {
        free( name_ptr );
        close( descriptor );
        errno = EINVAL;
        return -1;
      }

    if( (address = mmap( (caddr_t)NULL,
                         file_info.st_size,
                         (PROT_READ | PROT_WRITE),
                         (MAP_FILE | MAP_SHARED),
                         descriptor,
                         (off_t)0
                       )
        )
        ==
        (caddr_t)-1
      )
      {
        close( descriptor );
        free( name_ptr );
        return -1;
      }

    shm_segments[last_free].key  = key;
    shm_segments[last_free].name = name_ptr;
    shm_segments[last_free].fd   = descriptor;
    shm_segments[last_free].size = file_info.st_size;
    shm_segments[last_free].mem  = address;
    return last_free;

}

/*
 *
 */

caddr_t
map_memory_mapped_file( int shmid )
{
    if(  shmid   <= 0 || shmid > N_SHM_SEGMENTS )
      {
        errno = EINVAL;
        return (caddr_t)-1;
      }

    if( shm_segments[shmid].size == 0 )
      {
        errno = EIDRM;
        return (caddr_t)-1;
      }

    return shm_segments[shmid].mem;
}

int
get_id_memory_mapped_file( key_t key )
{
    register int i;

    for( i = 1; i <= N_SHM_SEGMENTS; i++ )
      {
        if( shm_segments[i].key == key )
          {
            return i;
          }
      }
    return -1;
}
