/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998, 1999  Andreas Mueller <mueller@daneb.ping.de>
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 * $Log: dao.cc,v $
 * Revision 1.7  2001/03/25 07:36:14  andreasm
 * Updated SCSI lib to version from cdrtools-1.10a17.
 * Added patches from compilation under UnixWare.
 *
 * Revision 1.6  2000/12/17 10:51:23  andreasm
 * Default verbose level is now 2. Adaopted message levels to have finer
 * grained control about the amount of messages printed by cdrdao.
 * Added CD-TEXT writing support to the GenericMMCraw driver.
 * Fixed CD-TEXT cue sheet creating for the GenericMMC driver.
 *
 * Revision 1.5  2000/11/12 16:50:44  andreasm
 * Fixes for compilation under Win32.
 *
 * Revision 1.4  2000/10/08 16:39:41  andreasm
 * Remote progress message now always contain the track relative and total
 * progress and the total number of processed tracks.
 *
 * Revision 1.3  2000/06/06 22:26:13  andreasm
 * Updated list of supported drives.
 * Added saving of some command line settings to $HOME/.cdrdao.
 * Added test for multi session support in raw writing mode to GenericMMC.cc.
 * Updated manual page.
 *
 * Revision 1.2  2000/05/01 18:13:18  andreasm
 * Fixed too small mode page buffer.
 *
 * Revision 1.1.1.1  2000/02/05 01:38:06  llanero
 * Uploaded cdrdao 1.1.3 with pre10 patch applied.
 *
 * Revision 1.12  1999/05/11 20:04:09  mueller
 * SYSV message queues are not used anymore. The communication between
 * reader and writer is based on a polling mechanism, now.
 * Added posix thread support.
 *
 * Revision 1.11  1999/03/27 20:57:27  mueller
 * Improved buffer code. Does not use semaphores anymore.
 * The reader is now the forked process.
 *
 * Revision 1.10  1999/01/24 16:03:57  mueller
 * Applied Radek Doulik's ring buffer patch. Added some cleanups and
 * improved behavior in error case.
 *
 * Revision 1.9  1998/10/03 14:33:46  mueller
 * Applied patch from Bjoern Fischer <bfischer@Techfak.Uni-Bielefeld.DE>:
 * Cosmetic changes and correction of real time scheduling selection.
 *
 * Revision 1.8  1998/09/22 19:14:29  mueller
 * Added locking of memory pages.
 *
 * Revision 1.7  1998/09/21 19:35:51  mueller
 * Added real time scheduling. Submitted by Bjoern Fischer <bfischer@Techfak.Uni-Bielefeld.DE>.
 *
 * Revision 1.6  1998/09/06 13:34:22  mueller
 * Use 'message()' for printing messages.
 *
 * Revision 1.5  1998/08/30 19:21:15  mueller
 * Rearranged the code.
 *
 * Revision 1.4  1998/08/15 20:47:16  mueller
 * Added support for GenericMMC driver.
 *
 */

static char rcsid[] = "$Id: dao.cc,v 1.7 2001/03/25 07:36:14 andreasm Exp $";

#include <config.h>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>

#ifdef linux
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/sysctl.h>
#include <sys/sysctl.h>
#endif

#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif

#ifdef USE_POSIX_THREADS
#include <pthread.h>
#else
#include <sys/ipc.h>
#include <sys/shm.h>
#endif

#include "dao.h"

#include "port.h"
#include "util.h"


#define DEBUG_WRITE 0



#if defined(__FreeBSD__)
#define IPC_ARG_T void
#else
#define IPC_ARG_T msgbuf
#endif


struct ShmSegment {
  int id;
  char *buffer;
};

struct Buffer {
  long bufLen;  // number of blocks in buffer that should be written
  TrackData::Mode mode; // data mode for writing
  TrackData::Mode trackMode; // mode of track may differ from 'mode' if data
                             // blocks must be encoded in audio blocks,
                             // only used for message printing
  int trackNr; // if != 0 a new track with given number has started
  int trackProgress; // reading progress of current track 0..1000
  char *buffer; // address of buffer that should be written
};
  
struct BufferHeader {
  long buffersRead;    // number of blocks that are read and put to the buffer
  long buffersWritten; // number of blocks that were taken from the buffer
  int buffersFilled;   // set to 1 by reader process when buffer is filled the
                       // first time
  int readerFinished;  
  int readerTerminated;
  int terminateReader;

  long nofBuffers;     // number of available buffers
  Buffer *buffers;
};

// buffer size in blocks
int BUFFER_SIZE = 75;

static int TERMINATE = 0;




static int getSharedMemory(long nofBuffers, BufferHeader **header,
			   long *nofSegments, ShmSegment **shmSegments);
static void releaseSharedMemory(long nofSegments, ShmSegment *shmSegments);



static RETSIGTYPE terminationRequest(int sig)
{
  if (sig == SIGQUIT || sig == SIGTERM) 
    TERMINATE = 1;

#if 0
  if (sig == SIGCHLD) {
    message(0, "SIGCHLD received.");
  }
#endif
}


#ifndef USE_POSIX_THREADS
// Waits or polls for termination of a child process.
// noHang: 0: wait until child terminates, 1: just poll if child terminated
// status: filled with status information, only valid if 0 is returned
// return: 0: child exited
//         1: no child exited, can only happen if 'noHang' is 1
//         2: wait failed, 'errno' contains cause
static int waitForChild(int noHang, int *status)
{
  int ret;

  do {
    if (noHang)
      ret = wait3(status, WNOHANG, NULL);
    else
      ret = wait(status);

    if (ret > 0)
      return 0;

    if (ret < 0 && errno != EINTR 
#ifdef ERESTARTSYS
	&& errno != ERESTARTSYS
#endif
	) {
      return 2;
    }
  } while (ret < 0);

  return 1;
}

#endif

// Blocks all signals that are handled by this module.
static void blockSignals()
{
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigaddset(&set, SIGQUIT);
  sigaddset(&set, SIGTERM);

#ifdef USE_POSIX_THREADS

#ifdef HAVE_PTHREAD_SIGMASK
  pthread_sigmask(SIG_BLOCK, &set, NULL);
#endif

#else
  sigprocmask(SIG_BLOCK, &set, NULL);
#endif
}

// Blocks all signals that are handled by this module.
static void unblockSignals()
{
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigaddset(&set, SIGQUIT);
  sigaddset(&set, SIGTERM);

#ifdef USE_POSIX_THREADS

#ifdef HAVE_PTHREAD_SIGMASK
  pthread_sigmask(SIG_UNBLOCK, &set, NULL);
#endif

#else
  sigprocmask(SIG_UNBLOCK, &set, NULL);
#endif
}

// return: 0: OK 
//         1: child process terminated and has been collected with 'wait()'
//         2: error -> child process must be terminated
static int writer(const Toc *toc, CdrDriver *cdr, BufferHeader *header,
		  long lba)
{
  long total = toc->length().lba() * AUDIO_BLOCK_LEN;
  long totalTracks = toc->nofTracks();
  long cnt = 0;
  long blkCount = 0;
  long len = 0;
  long cntMb;
  long lastMb = 0;
  long buffered;
  int buffFill;
  int minFill = 100;
  int maxFill = 0;
  int actTrackNr = 0;
  long actProgress;
  TrackData::Mode dataMode;
#ifndef USE_POSIX_THREADS
  int status;
#endif

  message (3, "Waiting for reader process");

  while (header->buffersFilled == 0) {
    sleep(1);

    if (header->readerTerminated) {
      message(-2, "Reader process terminated abnormally.");
      return 1;
    }

#ifndef USE_POSIX_THREADS
  // Check if child has terminated
    switch (waitForChild(1, &status)) {
    case 0: // Child exited
      message(-2, "Reader process terminated abnormally.");
      return 1;
    case 2:
      message(-2, "wait failed: %s", strerror(errno));
      return 2;
    }
#endif
  }

#if DEBUG_WRITE
  FILE *fp = fopen("test.out", "w");
#endif

  message (3, "Awaken, will start writing");

  if (cdr != NULL) {
    cdr->sendWriteCdProgressMsg(CdrDriver::WCD_LEADIN,
				totalTracks, 0, 0, 0, 100);

    blockSignals();
    if (cdr->startDao() != 0) {
      unblockSignals();
      return 2;
    }
    unblockSignals();
  }

  do {
    //message(4, "Slave: waiting for master.");

    while (header->buffersWritten == header->buffersRead) {
      if (header->readerTerminated) {
	message(-2, "Reader process terminated abnormally.");
	return 1;
      }

#ifndef USE_POSIX_THREADS
      // Check if child has terminated
      switch (waitForChild(1, &status)) {
      case 0: // Child exited
	message(-2, "Reader process terminated abnormally.");
	return 1;
      case 2:
	message(-2, "wait failed: %s", strerror(errno));
	return 2;
      }
#endif

      mSleep(20);
    }

    Buffer &buf = header->buffers[header->buffersWritten % header->nofBuffers];
    len = buf.bufLen;
    dataMode = buf.mode;

    if (header->readerFinished) {
      buffFill = 100;
      if (maxFill == 0)
	maxFill = 100;
    }
    else {
      buffered = header->buffersRead - header->buffersWritten;

      if (buffered == header->nofBuffers ||
	  buffered == header->nofBuffers - 1) {
	buffFill = 100;
      }
      else {
	buffFill = 100 * buffered;
	buffFill /= header->nofBuffers;
      }

      if (buffFill > maxFill)
	maxFill = buffFill;
    }

    if (buffFill < minFill)
      minFill = buffFill;

    if (len == 0) {
      // all data is written
      message(1, "");
      if (cdr == NULL)
	message(1, "Read %ld blocks.", blkCount);
      else
	message(1, "Wrote %ld blocks. Buffer fill min %d%%/max %d%%.",
		blkCount, minFill, maxFill);

#if DEBUG_WRITE
      if (fp != NULL)
	fclose(fp);
#endif

      if (cdr != NULL) {
	cdr->sendWriteCdProgressMsg(CdrDriver::WCD_LEADOUT,
				    totalTracks, 0xaa, 1000, 1000, 100);
	
	blockSignals();
	if (cdr->finishDao() != 0) {
	  unblockSignals();
	  return 2;
	}
	unblockSignals();
      }

      return 0;
    }

    cnt += len * AUDIO_BLOCK_LEN;
    blkCount += len;

    if (buf.trackNr > 0) {
      message(1, "Writing track %02d (mode %s/%s)...", buf.trackNr,
	      TrackData::mode2String(buf.trackMode),
	      TrackData::mode2String(dataMode));

      actTrackNr = buf.trackNr;
    }

    //message(4, "Slave: writing buffer %p (%ld).", buf, len);

#if DEBUG_WRITE
    if (fp != NULL) {
      if (cdr != NULL)
	fwrite(buf.buffer, cdr->blockSize(dataMode), len, fp);
      else
	fwrite(buf.buffer, 2352, len, fp);
    }	
#endif

    if (cdr != NULL) {
      blockSignals();
      if (cdr->writeData(dataMode, lba, buf.buffer, len) != 0) {
	message(-2, "Writing failed - buffer under run?");
	unblockSignals();
	return 2;
      }
      else {
	cntMb = cnt >> 20;
	if (cntMb > lastMb) {
	  message(1, "Wrote %ld of %ld MB (Buffer %3d%%).\r", cnt >> 20,
		  total >> 20, buffFill);
	  lastMb = cntMb;
	}
      }
      unblockSignals();

      actProgress = cnt;
      actProgress /= total / 1000;

      cdr->sendWriteCdProgressMsg(CdrDriver::WCD_DATA, totalTracks, actTrackNr,
				  buf.trackProgress, actProgress, buffFill);
    }
    else {
      message(1, "Read %ld of %ld MB.\r", cnt >> 20, total >> 20);
    }


    header->buffersWritten += 1;

  } while (!TERMINATE);

  message(-1, "Writing/simulation/read-test aborted on user request.");

  return 2;
}

struct ReaderArgs {
  const Toc *toc;
  CdrDriver *cdr;
  int swap;
  BufferHeader *header;
  long startLba;
};

static void *reader(void *args)
{
  const Toc *toc = ((ReaderArgs*)args)->toc;
  CdrDriver *cdr = ((ReaderArgs*)args)->cdr;
  int swap = ((ReaderArgs*)args)->swap;
  BufferHeader *header = ((ReaderArgs*)args)->header;
  long lba = ((ReaderArgs*)args)->startLba + 150; // used to encode the sector
                                                  // header (MSF)

  long length = toc->length().lba();
  long n, rn;
  int first = header->nofBuffers;
  const Track *track;
  int trackNr = 1;
  Msf tstart, tend;
  TrackData::Mode dataMode;
  int encodingMode = 1;
  int newTrack;
  long tact; // number of blocks already read from current track
  long tprogress;

  setRealTimeScheduling(4);

  if (cdr != NULL) {
    if (cdr->bigEndianSamples() == 0) {
      // swap samples for little endian recorders
      swap = !swap;
    }
    encodingMode = cdr->encodingMode();
  }
  message(4, "Swap: %d", swap);

  TrackIterator itr(toc);
  TrackReader reader;

  track = itr.first(tstart, tend);
  reader.init(track);

  if (reader.openData() != 0) {
    message(-2, "Opening of track data failed.");
    goto fail;
  }

  newTrack = 1;
  tact = 0;

  dataMode = (encodingMode == 0) ? TrackData::AUDIO : track->type();

  do {
    n = (length > BUFFER_SIZE ? BUFFER_SIZE : length);

    Buffer &buf = header->buffers[header->buffersRead % header->nofBuffers];

    do {
      rn = reader.readData(encodingMode, lba, buf.buffer, n);
    
      if (rn < 0) {
	message(-2, "Reading of track data failed.");
	goto fail;
      }
      
      if (rn == 0) {
	track = itr.next(tstart, tend);
	reader.init(track);

	if (reader.openData() != 0) {
	  message(-2, "Opening of track data failed.");
	  goto fail;
	}
	trackNr++;
	if (encodingMode != 0)
	  dataMode = track->type();

	newTrack = 1;
	tact = 0;
      }
    } while (rn == 0);

    lba += rn;
    tact += rn;

    if (track->type() == TrackData::AUDIO) {
      if (swap) {
	// swap audio data
	swapSamples((Sample *)(buf.buffer), rn * SAMPLES_PER_BLOCK);
      }
    }
    else {
      if (encodingMode == 0 && cdr != NULL && cdr->bigEndianSamples() == 0) {
	// swap encoded data blocks
	swapSamples((Sample *)(buf.buffer), rn * SAMPLES_PER_BLOCK);
      }
    }

    buf.bufLen = rn;
    buf.mode = dataMode;
    buf.trackMode = track->type();

    tprogress = tact * 1000;
    tprogress /= track->length().lba();

    buf.trackProgress = tprogress;

    if (newTrack) {
      // inform write process that it should print message about new track
      buf.trackNr = trackNr;
    }
    else {
      buf.trackNr = 0;
    }

    header->buffersRead += 1;

    length -= rn;

    if (first > 0) {
      first--;
      if (first == 0 || length == 0) {
	message (3, "Buffer filled");

	header->buffersFilled = 1;
      }
    }
    
    // wait for writing process to finish writing of previous buffer
    //message(4, "Reader: waiting for Writer.");
    while (header->buffersRead - header->buffersWritten 
	   == header->nofBuffers &&
	   header->terminateReader == 0) {
      mSleep(20);
    }


    newTrack = 0;
  } while (length > 0 && header->terminateReader == 0);

  header->readerFinished = 1;

  if (header->terminateReader == 0) {
    Buffer &buf1 = header->buffers[header->buffersRead % header->nofBuffers];
    buf1.bufLen = 0;
    buf1.trackNr = 0;
    header->buffersRead += 1;
  }

#ifndef USE_POSIX_THREADS
  // wait until we get killed
  while (1)
    sleep(1000);

  exit(0);
#endif

  return NULL;

fail:
  header->readerTerminated = 1;

#ifndef USE_POSIX_THREADS
  exit(1);
#endif

  return NULL;
}


int writeDiskAtOnce(const Toc *toc, CdrDriver *cdr, int nofBuffers, int swap,
		    int testMode)
{
  int err = 0;
  BufferHeader *header = NULL;
  long nofShmSegments = 0;
  ShmSegment *shmSegments = NULL;
  long startLba = 0;

#ifdef USE_POSIX_THREADS
  pthread_t readerThread;
  pthread_attr_t readerThreadAttr;
  int threadStarted = 0;
#else
  int pid = 0;
  int status;
#endif

#if 1
  if (nofBuffers < 10) {
    nofBuffers = 10;
    message(-1, "Adjusted number of FIFO buffers to 10.");
  }
#endif

  if (getSharedMemory(nofBuffers, &header, &nofShmSegments,
		      &shmSegments)  != 0) {
    releaseSharedMemory(nofShmSegments, shmSegments);
    return 1;
  }

  header->buffersRead = 0;
  header->buffersWritten = 0;
  header->buffersFilled = 0;
  header->readerFinished = 0;
  header->readerTerminated = 0;
  header->terminateReader = 0;

  TERMINATE = 0;

  installSignalHandler(SIGINT, SIG_IGN);
  installSignalHandler(SIGPIPE, SIG_IGN);
  installSignalHandler(SIGALRM, SIG_IGN);
  installSignalHandler(SIGCHLD, terminationRequest);
  installSignalHandler(SIGQUIT, terminationRequest);
  installSignalHandler(SIGTERM, terminationRequest);

  if (!testMode) {
    const DiskInfo *di;

    if (cdr->initDao(toc) != 0) {
      err = 1; goto fail;
    }

    if ((di = cdr->diskInfo()) != NULL) {
      startLba = di->thisSessionLba;
    }
  }

  // start reader process
#ifdef USE_POSIX_THREADS

  if (pthread_attr_init(&readerThreadAttr) != 0) {
    message(-2, "pthread_attr_init failed: %s", strerror(errno));
    err = 1; goto fail;
  }
  
  ReaderArgs rargs;

  rargs.toc = toc;
  rargs.cdr = cdr;
  rargs.swap = swap;
  rargs.header = header;
  rargs.startLba = startLba;

  if (pthread_create(&readerThread, &readerThreadAttr, reader, &rargs) != 0) {
    message(-2, "Cannot create thread: %s", strerror(errno));
    pthread_attr_destroy(&readerThreadAttr);
    err = 1; goto fail;
  }
  else {
    threadStarted = 1;
  }

#else /* USE_POSIX_THREADS */

  if ((pid = fork()) == 0) {
    // we are the new process

    setsid(); // detach from controlling terminal

#ifdef HAVE_MLOCKALL
    if (geteuid() == 0) {
      if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) {
	message(-1, "Cannot lock memory pages: %s", strerror(errno));
      }
      message(4, "Reader process memory locked");
    }
#endif

    ReaderArgs rargs;

    rargs.toc = toc;
    rargs.cdr = cdr;
    rargs.swap = swap;
    rargs.header = header;
    rargs.startLba = startLba;

    reader(&rargs);
  }
  else if (pid < 0) {
    message(-2, "fork failed: %s", strerror(errno));
    err = 1; goto fail;
  }
#endif /* USE_POSIX_THREADS */

  switch (setRealTimeScheduling(5)) {
  case 1:
    message(-1, "No super user permission to setup real time scheduling.");
    break;
  case 2:
    message(2, "Real time scheduling not available.");
    break;
  }

#ifdef HAVE_MLOCKALL
  if (geteuid() == 0) {
    if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) {
      message(-1, "Cannot lock memory pages: %s", strerror(errno));
    }
    message(4, "Memory locked");
  }
#endif

  switch (writer(toc, cdr, header, startLba)) {
  case 1: // error, reader process terminated abnormally
#ifndef USE_POSIX_THREADS
    pid = 0;
#endif
    err = 1;
    break;
  case 2: // error, reader process must be terminated
    err = 1;
    break;
  }

  if (err != 0 && cdr != NULL)
    cdr->abortDao(); // abort writing process

 fail:
#ifdef HAVE_MUNLOCKALL
  munlockall();
#endif

#ifdef USE_POSIX_THREADS
  if (threadStarted) {
    header->terminateReader = 1;

    if (pthread_join(readerThread, NULL) != 0) {
      message(-2, "pthread_join failed: %s", strerror(errno));
      err = 1;
    }

    pthread_attr_destroy(&readerThreadAttr);
  }

#else
  if (pid != 0) {
    if (kill(pid, SIGKILL) == 0) {
      waitForChild(0, &status);
    }
  }
#endif

  releaseSharedMemory(nofShmSegments, shmSegments);

  installSignalHandler(SIGINT, SIG_DFL);
  installSignalHandler(SIGPIPE, SIG_DFL);
  installSignalHandler(SIGALRM, SIG_DFL);
  installSignalHandler(SIGCHLD, SIG_DFL);
  installSignalHandler(SIGQUIT, SIG_DFL);
  installSignalHandler(SIGTERM, SIG_DFL);

  return err;
}


#ifdef USE_POSIX_THREADS
static int getSharedMemory(long nofBuffers,
			   BufferHeader **header, long *nofSegments,
			   ShmSegment **shmSegment)
{
  long b;
  long bufferSize = BUFFER_SIZE * AUDIO_BLOCK_LEN;

  *header = NULL;
  *nofSegments = 0;
  *shmSegment = NULL;

  if (nofBuffers <= 0) {
    return 1;
  }

  *shmSegment = new ShmSegment;
  *nofSegments = 1;

  (*shmSegment)->id = -1;

  (*shmSegment)->buffer = new char[sizeof(BufferHeader) +
				  nofBuffers * sizeof(Buffer) +
				  nofBuffers * bufferSize];

  if ( (*shmSegment)->buffer == NULL) {
    message(-2, "Cannot allocated memory for ring buffer.");
    return 1;
  }

  *header = (BufferHeader*)((*shmSegment)->buffer);
  (*header)->nofBuffers = nofBuffers;
  (*header)->buffers =
	(Buffer*)((*shmSegment)->buffer + sizeof(BufferHeader));

  char *bufferBase = (*shmSegment)->buffer + sizeof(BufferHeader) +
                      nofBuffers * sizeof(Buffer);

  for (b = 0; b < nofBuffers; b++)
    (*header)->buffers[b].buffer = bufferBase + b * bufferSize;

  return 0;
}

static void releaseSharedMemory(long nofSegments, ShmSegment *shmSegment)
{
  if (shmSegment == NULL || nofSegments == 0)
    return;

  if (shmSegment->buffer != NULL) {
    delete[] shmSegment->buffer;
    shmSegment->buffer = NULL;
  }

  delete shmSegment;
}

#else /* USE_POSIX_THREADS */

static int getSharedMemory(long nofBuffers,
			   BufferHeader **header, long *nofSegments,
			   ShmSegment **shmSegments)
{
  long i, b;
  long bufferSize = BUFFER_SIZE * AUDIO_BLOCK_LEN;
  long maxSegmentSize = 0;
  long bcnt = 0;

  *header = NULL;
  *nofSegments = 0;
  *shmSegments = NULL;

  if (nofBuffers <= 0) {
    return 1;
  }

#if defined(linux) && defined(IPC_INFO)
  struct shminfo info;

  if (shmctl(0, IPC_INFO, (struct shmid_ds*)&info) < 0) {
    message(-1, "Cannot get IPC info: %s", strerror(errno));
    maxSegmentSize = 4 * 1024 * 1024;
    message(-1, "Assuming %ld MB shared memory segment size.",
	    maxSegmentSize >> 20);
  }
  else {
    maxSegmentSize = info.shmmax;
  }

#elif defined(__FreeBSD__)
  maxSegmentSize = 4 * 1024 * 1024; // 4 MB
#else
  maxSegmentSize = 1 * 1024 * 1024; // 1 MB
#endif

  message(4, "Shm max segement size: %ld (%ld MB)", maxSegmentSize,
	  maxSegmentSize >> 20);

  if (maxSegmentSize < sizeof(BufferHeader) + nofBuffers * sizeof(Buffer)) {
    message(-2, "Shared memory segment cannot hold a single buffer.");
    return 1;
  }

  maxSegmentSize -= sizeof(BufferHeader) + nofBuffers * sizeof(Buffer);

  long buffersPerSegment = maxSegmentSize / bufferSize;

  if (buffersPerSegment == 0) {
    message(-2, "Shared memory segment cannot hold a single buffer.");
    return 1;
  }

  *nofSegments = nofBuffers / buffersPerSegment;

  if (nofBuffers % buffersPerSegment != 0)
    *nofSegments += 1;

  *shmSegments = new ShmSegment[*nofSegments];

  message(4, "Using %ld shared memory segments.", *nofSegments);

  for (i = 0; i < *nofSegments; i++) {
    (*shmSegments)[i].id = -1;
    (*shmSegments)[i].buffer = NULL;
  }

  long bufCnt = nofBuffers;
  long n;
  long segmentLength;
  char *bufferBase;

  for (i = 0; i < *nofSegments; i++) {
    n = (bufCnt > buffersPerSegment ? buffersPerSegment : bufCnt);

    segmentLength = n * bufferSize;
    if (*header == NULL) {
      // first segment contains the buffer header
      segmentLength += sizeof(BufferHeader) + nofBuffers * sizeof(Buffer);
    }

    (*shmSegments)[i].id = shmget(IPC_PRIVATE, segmentLength, 0600|IPC_CREAT);
    if ((*shmSegments)[i].id < 0) {
      message(-2, "Cannot create shared memory segment: %s",
	      strerror(errno));
      message(-2, "Try to reduce the buffer count (option --buffers).");
      return 1;
    }

    (*shmSegments)[i].buffer = (char *)shmat((*shmSegments)[i].id, 0, 0);
    if (((*shmSegments)[i].buffer) == NULL ||
	((*shmSegments)[i].buffer) == (char *)-1) {
      (*shmSegments)[i].buffer = NULL;
      message(-2, "Cannot get shared memory: %s", strerror(errno));
      message(-2, "Try to reduce the buffer count (option --buffers).");
      return 1;
    }

    
    if (*header == NULL) {
      bufferBase = (*shmSegments)[i].buffer + sizeof(BufferHeader) +
	           nofBuffers * sizeof(Buffer);
      *header = (BufferHeader*)(*shmSegments)[i].buffer;
      (*header)->nofBuffers = nofBuffers;
      (*header)->buffers =
	(Buffer*)((*shmSegments)[i].buffer + sizeof(BufferHeader));
    }
    else {
      bufferBase = (*shmSegments)[i].buffer;
    }

    for (b = 0; b < n; b++)
      (*header)->buffers[bcnt++].buffer = bufferBase + b * bufferSize;

    bufCnt -= n;
  }

  assert(bcnt == nofBuffers);

  return 0;
}

static void releaseSharedMemory(long nofSegments, ShmSegment *shmSegments)
{
  long i;

  if (shmSegments == NULL || nofSegments == 0)
    return;

  for (i = 0; i < nofSegments; i++) {
    if (shmSegments[i].id >= 0) {
      if (shmSegments[i].buffer != NULL) {
	if (shmdt(shmSegments[i].buffer) != 0) {
	  message(-2, "shmdt: %s", strerror(errno));
	}
      }
      if (shmctl(shmSegments[i].id, IPC_RMID, NULL) != 0) {
	message(-2, "Cannot remove shared memory: %s", strerror(errno));
      }
    }
  }

  delete[] shmSegments;
}
#endif /* USE_POSIX_THREADS */
