/*
 * COPYRIGHT (c) International Business Machines Corp. 2001-2017
 *
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <grp.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>

#include "slotmgr.h"
#include "log.h"
#include "pkcsslotd.h"

#define MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
#define MAPFILENAME CONFIG_PATH "/.apimap"

pthread_mutexattr_t mtxattr;	// Mutex attribute for the shared memory Mutex

/***********************************************************************
 *  CreateSharedMemory -
 *
 *      Creates and initializes a shared memory file.  This function will fail if the
 *      memory is already allocated since we're the owner of it.
 *
 ***********************************************************************/

int CreateSharedMemory ( void ) {

  struct stat statbuf;
  char *Path = NULL;
  struct group *grp;
  struct shmid_ds shm_info;

#if !MMAP
  if ( ((Path = getenv("PKCS11_SHMEM_FILE")) == NULL ) || ( Path[0] == '\0' ) ) {
    Path = TOK_PATH;
  }
  InfoLog( "Shared memory file is %s", Path);

   // Get shared memory key token all users of the shared memory
   // need to get the same token

   if ( stat(Path, &statbuf) < 0 ) {
      ErrLog("Shared Memory Key Token creation file does not exist");
      return FALSE;
   }
   // SAB  Get the group information for the PKCS#11 group... fail if
   // it does not exist
   grp = getgrnam("pkcs11");
   if ( !grp ) {
     ErrLog("Group PKCS#11 does not exist ");
     return FALSE;  // Group does not exist... setup is wrong..
   }


   tok = ftok(Path ,'b');
   // Allocate the shared memory... Fail if the memory is already
   // allocated since the slot mgr is the owner of it.

   // Is this some attempt at exclusivity, or is that just a side effect? - SCM 9/1

   shmid = shmget( tok, sizeof( Slot_Mgr_Shr_t ),
                   IPC_CREAT | IPC_EXCL |  S_IRUSR |
	           S_IRGRP  | S_IWUSR | S_IWGRP   );

   // Explanation of options to shmget():

   /*
    *  IPC_CREAT  Creates the data structure if it does not already exist.
    *  IPC_EXCL   Causes the shmget subroutine to be unsuccessful if the
    *             IPC_CREAT flag is also set, and the data structure already exists.
    *  S_IRUSR    Permits the process that owns the data structure to read it.
    *  S_IWUSR    Permits the process that owns the data structure to modify it.
    *  S_IRGRP    Permits the group associated with the data structure to read it.
    *  S_IWGRP    Permits the group associated with the data structure to modify it.
    *
    *
    *  WE DON"T WANT OTHERS
    *  S_IROTH    Permits others to read the data structure.
    *  S_IWOTH    Permits others to modify the data structure.
    */


   if ( shmid < 0 ) {
      ErrLog("Shared memory creation failed (0x%X)\n", errno);
      ErrLog("Reclaiming 0x%X\n", tok);
      shmid = shmget( tok, sizeof( Slot_Mgr_Shr_t ), 0 );
      DestroySharedMemory();
      shmid = shmget( tok, sizeof( Slot_Mgr_Shr_t ),
                      IPC_CREAT | IPC_EXCL |  S_IRUSR |
	              S_IRGRP  | S_IWUSR | S_IWGRP   );
      if ( shmid < 0 ) {
          ErrLog("Shared memory reclamation failed (0x%X)\n", errno);
          ErrLog("perform ipcrm -M 0x%X\n", tok);
          return FALSE;
      }
   }

   // SAB Set the group ownership of the shared mem segment..
   // we already have the group structure..

   if ( shmctl(shmid, IPC_STAT,&shm_info) == 0 ) {

        shm_info.shm_perm.gid = grp->gr_gid;

        if (shmctl(shmid,IPC_SET,&shm_info) == -1) {
             ErrLog("Failed to set group ownership for shm \n");
             shmctl(shmid, IPC_RMID,NULL);
        }

   } else {
        ErrLog("Can't get status of shared memory %d\n",errno);
        // we know it was created... we need to destroy it...
        shmctl(shmid, IPC_RMID,NULL);

   }



   return TRUE;
#else
{
#warning "EXPERIMENTAL"
   int fd;
   int i;
   char *buffer;

   grp = getgrnam("pkcs11");
   if ( !grp ) {
     ErrLog("Group \"pkcs11\" does not exist! Please run %s/pkcs11_startup.",
		     SBIN_PATH);
     return FALSE;  // Group does not exist... setup is wrong..
   }

   fd = open(MAPFILENAME,O_RDWR,MODE);
   if (fd < 0 ) {
     // File does not exist... this is cool, we creat it here
     fd = open(MAPFILENAME,O_RDWR|O_CREAT,MODE); // Create the file
     if (fd < 0 ){ // We are really hosed here, since we should be able
       // to create the file now
       ErrLog("%s: open(%s): %s", __FUNCTION__, MAPFILENAME, strerror(errno));
       return FALSE;
     } else {
       if (fchmod(fd, MODE) == -1) {
	 ErrLog("%s: fchmod(%s): %s", __FUNCTION__, MAPFILENAME, strerror(errno));
	 close(fd);
	 return FALSE;
       }
       if (fchown(fd, 0, grp->gr_gid) == -1) {
	 ErrLog("%s: fchown(%s, root, pkcs11): %s", __FUNCTION__, MAPFILENAME,
		strerror(errno));
	 close(fd);
	 return FALSE;
       }
       // Create a buffer and make the file the right length
       i = sizeof(Slot_Mgr_Shr_t);
       buffer = malloc(sizeof(Slot_Mgr_Shr_t));
       memset(buffer,'\0',i);
       write(fd,buffer,i);
       free(buffer);
       close(fd);
     }
   } else {
     ErrLog("%s: [%s] exists; you may already have a pkcsslot daemon running. If this "
	    "is not the case, then the prior daemon was not shut down cleanly. "
	    "Please delete this file and try again\n", __FUNCTION__, MAPFILENAME);
     close(fd);
     return FALSE;
   }
   return TRUE;
}

#endif

}




/***********************************************************************
 *
 * AttachToSharedMemory -
 *
 *     Called after creating the shared memory file
 *     Basically allows us to have access to the memory we've just created
 *
 ***********************************************************************/

int AttachToSharedMemory ( void ) {

#if !MMAP
   shmp = NULL;
   shmp = (Slot_Mgr_Shr_t *) shmat( shmid, NULL, 0 );

   if ( !shmp ) {
      ErrLog("Shared memory attach failed (0x%X)\n", errno);
      return FALSE;
   }

   /* Initizalize the memory to 0  */
   memset ( shmp, '\0', sizeof(*shmp) );

   return TRUE;
#else
{
#warning "EXPERIMENTAL"
   int fd;
   int i;
   char *buffer;

    fd = open(MAPFILENAME,O_RDWR,MODE);
    if (fd < 0 ){
   	return FALSE;  //Failed
    }
   shmp = (Slot_Mgr_Shr_t *)mmap(NULL,sizeof(Slot_Mgr_Shr_t),PROT_READ|PROT_WRITE,
				    MAP_SHARED,fd,0);
   close(fd);
   if ( ! shmp ) {
      return FALSE;
   }
   return TRUE;
}
#endif

}






/***********************************************************************
 *
 * DetachFromSharedMemory -
 *
 *     Un-does AttachToSharedMemory() :)
 *
 ***********************************************************************/

void DetachFromSharedMemory ( void ) {

#if !MMAP
  if ( shmp == NULL ) return;

  if ( shmdt(shmp) != 0 ) {
    ErrLog("Attempted to detach from an invalid shared memory pointer");
  }

  shmp = NULL;
  return;
#else
  if ( shmp == NULL ) return;

  munmap((void *)shmp,sizeof(*shmp));

  unlink(MAPFILENAME);
#endif

}




/***********************************************************************
 *
 * DestroySharedMemory -
 *
 *     Closes (destroys) the shared memory file we created with CreateSharedMemory()
 *
 *     We should make sure that everyone else has detached before we do this
 *     if we manage to exit before this gets called, you have to call ipcrm
 *     to clean things up...
 *
 ***********************************************************************/

void DestroySharedMemory ( void ) {

  if (shmctl (shmid, IPC_RMID, 0) != 0) {
    perror ("error in closing shared memory segment");
  }

  return;

}





/***********************************************************************
 *
 * InitSharedMemory -
 *
 *      Set up our newly allocated shared memory segment
 *
 *
 ***********************************************************************/


int InitSharedMemory ( Slot_Mgr_Shr_t *sp ) {

   uint16 procindex;

   memset(sp->slot_global_sessions, 0, NUMBER_SLOTS_MANAGED * sizeof(uint32));

   /* Initialize the process side of things. */
   /* for now don't worry about the condition variables */
   for ( procindex=0; procindex < NUMBER_PROCESSES_ALLOWED; procindex++ ) {
     /* Initialize the mutex variables. */
      sp->proc_table[procindex].inuse = FALSE;
   }

   return TRUE;
}
