/****************************************************************************
    
    dssi_shm.h - functions that can handle shared memory segments for
                 DSSI plugins and UIs
    
    Copyright (C) 2005  Lars Luthman <larsl@users.sourceforge.net>
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    
    This program 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 General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307  USA

****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>

#include "dssi_shm.h"


void* dssi_shm_allocate(size_t bytes, char** keystring, char** used_flag) {
  int fd, shm_id, i;
  key_t key;
  void* shared_buffer;
  
  /* get a random key for the memory segment */
  if ((fd = open("/dev/urandom", O_RDONLY)) == -1) {
    perror("Could not open /dev/urandom");
    return NULL;
  }
  read(fd, &key, sizeof(key_t));
  close(fd);
  
  /* create and attach the memory segment */
  shm_id = shmget(key, bytes + 9, IPC_CREAT | IPC_EXCL | S_IRWXU | S_IRWXG);
  if (shm_id == -1) {
    perror("Could not create shared memory segment");
    return NULL;
  }
  shared_buffer = shmat(shm_id, NULL, 0);
  if (!shared_buffer) {
    perror("Could not attach to shared memory segment");
    shmctl(shm_id, IPC_RMID, NULL);
    return NULL;
  }
  
  /* add the unique string */
  *keystring = calloc(100, sizeof(char));
  srand(time(NULL) + getpid() * 1000000);
  for (i = 0; i < 8; ++i)
    sprintf((char*)shared_buffer + bytes + i, "%X", rand() % 16);
  
  /* give the caller a key to send to the plugin */
  sprintf(*keystring, "%X:%s:%X", shm_id, (char*)shared_buffer + bytes, bytes);

  /* set the USED flag to 0 (it should be 0 already, but there's no harm in
     setting it explicitly) */
  ((char*)shared_buffer)[bytes + 8] = 0;
  *used_flag = ((char*)shared_buffer) + bytes + 8;
  
  return shared_buffer;
}


void* dssi_shm_attach(const char* key, void* old_ptr) {
  int shm_id, keystart, bytes;
  void* ptr;
  
  if (sscanf(key, "%X:%n%*X:%X", &shm_id, &keystart, &bytes) < 1) {
    fprintf(stderr, "Invalid keystring, can not attach "
	    "shared memory segment\n");
    return NULL;
  }

  /* first, check if this is the same segment as the one we have already */
  if (old_ptr) {
    if (!strncmp(key + keystart, (char*)old_ptr + bytes, 8)) {
      fprintf(stderr, "Trying to attach a memory segment that we already "
	      "have attached\n");
      return old_ptr;
    }
    /* set the USED flag to 0 and detach the segment 
       (will attach it again immediately) */
    ((char*)old_ptr)[bytes + 8] = 0;
    shmdt(old_ptr);
  }
	
  ptr = shmat(shm_id, NULL, 0);
  
  /* check the keystring */
  if (strncmp(key + keystart, (char*)ptr + bytes, 8)) {
    shmdt(ptr);
    fprintf(stderr, "The keystrings do not match, detaching the "
	    "shared memory segment\n");
    return NULL;
  }
  
  /* check that this segment isn't already in use 
     this is potentially dangerous since someone could come along
     and set the use flag to 1 in another thread right after our check */
  if (((char*)ptr)[bytes + 8] == 0)
    ((char*)ptr)[bytes + 8] = 1;
  else {
    shmdt(ptr);
    fprintf(stderr, "The shared memory segment is already in use!\n");
    return NULL;
  }
  
  
  return ptr;
}


int dssi_shm_free(const char* key) {
  int shm_id;
  unsigned int ptr_int;
  if (sscanf(key, "%X:%*X:%X", &shm_id, &ptr_int) < 1)
    shm_id = -1;
  shmdt((void*)ptr_int);
  return shmctl(shm_id, IPC_RMID, NULL);
}


int dssi_shm_detach(void* ptr) {
  return shmdt(ptr);
}
