/*
 * $Id: imshared.cxx,v 4.0 2003/04/28 14:44:41 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  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 02111, USA.
 * */

/*------------------------------------------------------------------------
 *
 * This is a re-implementation of the `put' command, to put up an
 * image on to ImView via socket I/O, this time using SYSV shared
 * memory mechanism.
 *
 * Obviously this is complementary to the pure socket I/O mechanism.
 * Shared memory can only work when both server and client are on the
 * same host. However, it is much faster.
 *
 * Messages are passed using Socket I/O, only the data transfer uses
 * shared memory. This mechanism is meant to be almost transparent to
 * the client. The only difference is the command (`putm' instead of `put')
 * and the response.
 *
 * Hugues Talbot	26 Mar 2000
 *      
 *-----------------------------------------------------------------------*/

#include "imnmspc.hxx"
#include "imview.hxx"

#ifdef HAVE_PTHREADS

#include "imageIO.hxx"
#include "pointfile.hxx"
#include "interpreter.hxx"
#include "imshared.hxx"

extern imview_server *is;
extern interpreter   *imview_interpreter;
extern pointfile     *PtFileMngr;
extern imageIO       *IOBlackBox;
extern volatile bool syncDisplay;
extern volatile bool im_draw_finished;

#ifdef HAVE_SYSV_IPC

char putm::ref_pathname[L_tmpnam+1]; // definition.

putm::putm()
{
    char            idsem;
    char           *retpath;

    FILE           *unique_fd;
    
    shm_setup_ok = false; // sh_mem not set up yet
    sem_setup_ok = false;  // pessimistically, we think things will go wrong
    // set up the semaphores, though
 
    idsem = 'A';
    if (ref_pathname[0] == '\0') {
	retpath = tmpnam(ref_pathname); 
	if (retpath == NULL) {
	    dbgprintf("putm::putm: could not secure unique temporary file name. This is not good\n");
	    strncpy(ref_pathname, FPATH1, L_tmpnam); // last ditch effort...
	}
    } // else the temporary file name is assumed to exist.
    // open the file and close it.
    if ((unique_fd = fopen(ref_pathname, "a")) != NULL) {
	fprintf(unique_fd, "%d\n", (int)(getpid())); // and why not?
	fclose(unique_fd); // creates the file.
    } else {
	dbgprintf("putm::putm: Unique file could not be created. Semaphore set creation will fail\n");
	return;
    }
   
    if ((sem_key = ftok(ref_pathname, idsem)) == -1) {
	dbgprintf("putm::putm: Could not get sem_key, %s\n", strerror(errno));
	return; 
    }
    /* create a semaphore set with 3 semaphores: */
    /* the first is for the clients access to the shared memory (between clients) */
    /* the second is for read access and the last for write access */

    if ((semid = semget(sem_key, 3, 0666 | IPC_CREAT)) == -1) {
	dbgprintf("putm::putm: Could not get sem ID, %s\n", strerror(errno));
	return; // could not get ID
    }

    
    if (!sem_reset()) // set semaphores in a predictable state.
	sem_setup_ok = true; // we can proceed

    data = 0;
}

putm::~putm()
{
    union semun     arg; // can't do with 'em, useless though.
    
    // remove the temporary file name
    unlink(ref_pathname); // who cares if it fails?

    arg.val = 0; // serves no purpose except shutting up some compiler warnings
    // get rid of semaphore and shared memory segment, if necessary
    /* remove semaphore anyway */
    if (semctl(semid, 0, IPC_RMID, arg) == -1) {
	dbgprintf("putm: could not get rid of semaphore: %s\n",
		  strerror(errno));
	// don't return right now, try to get rid of the shared memory.
    }

    if (shm_setup_ok) {
	// detach shared memory segment
	if (shmdt(data) == -1) {
	    dbgprintf("putm: could not detach shared memory: %s\n",
		      strerror(errno));
	    return;
	}
	// try to destroy the memory segment
	if (shmctl(shmid, IPC_RMID, 0) == -1 ) {
	    dbgprintf("putm: could not remove shared memory segment, %s\n",
		      strerror(errno));
	    return;
	}
    }
    return;
}

// in contrast to put::perform, this call has
// no callback associated with it. It will block until
// the client puts up the data, or until timeout (a few seconds).
int putm::perform(vector <string> &l, string &result)
{
    int             i, j, ret, expected_size;
    int             so_far = 0;
    char           *newlabel;
    size_t          exp_nb;
    ostringstream   sres;
    IMAGE_HEADER   *img, *previmg;

    dbgprintf("Data transfer using Unix SysV style shared memory\n");
    if (!sem_setup_ok) {
	result = "Internal semaphore handling failed. shared memory put not available\n";
	return 1;
    }
    
    if (l.size() > 3) {
	ret = 0;
	img = new IMAGE_HEADER;

	img->label = 0;     // this is important, as otherwise find_header will choke on an invalid pointer
	img->needswap = 0;  // coming from the same host, so we never need to swap the byte order of the data
	imh.push_back(img); // imh belong to put::, the parent class
	// fill the header
	newlabel = strdup(l[0].c_str());
	// find out if there is another header with the same
	// name in use already. If so it will have to be delete
	// if the data transmission goes fine.
	dbgprintf("Looking for previous header with same label: %s\n", newlabel);
	previmg = put::find_header(newlabel); // might be 0

	if (previmg != 0) {
	    dbgprintf("Unique ID associated with this previous header: %d\n",
		      previmg->unique_id);
	    img->previous_id = previmg->unique_id;
	} else {
	    dbgprintf("No previous unique id found\n");
	    img->previous_id = 0;
	}
	
	img->label = newlabel;
	img->unique_id = id_pool++; // each header will have its own unique ID.
	img->nbc = strtol(l[1].c_str(),0,0);
	img->rawdata = 0 ; // nothing received yet

	exp_nb = 9 * img->nbc + 2; // expected number of arguments in the list
	if ((img->nbc > 0) && (exp_nb == l.size())) {
	    // create the suitable number of components
	    img->comp = new IMAGECOMP_HEADER[img->nbc];
	    for (i = 0, j = 2 ; i < img->nbc ; i++, j+=9) {
		(img->comp[i]).nx = atoi(l[j].c_str());   
		(img->comp[i]).ny = atoi(l[j+1].c_str()); 
		(img->comp[i]).nz = atoi(l[j+2].c_str()); 
		(img->comp[i]).ox = atoi(l[j+3].c_str());
		(img->comp[i]).oy = atoi(l[j+4].c_str());
		(img->comp[i]).oz = atoi(l[j+5].c_str());
		(img->comp[i]).imgt = imview_interpreter->translate_img(l[j+6]);
		(img->comp[i]).pixt = imview_interpreter->translate_pix(l[j+7]);
		(img->comp[i]).spp = atoi(l[j+8].c_str());
		(img->comp[i]).buffp = 0; // to be filled when we actually get some data.

		// check for each component:
		if (((img->comp[i]).nx * (img->comp[i]).ny * (img->comp[i]).nz) == 0) {
		    result = "Put: no data\n";
		    ret = 2;
		    break;
		} else if ((img->comp[i]).imgt == IM_ERROR) {
		    result = "Put: incorrect image type\n";
		    ret = 3;
		    break;
		} else if ((img->comp[i]).pixt == IM_INVALID) {
		    result = "Put: incorrect pixel type\n";
		    ret = 4;
		    break;
		}
	    }
	    // check that what we got was sensible
	    if (ret == 0) {
		int j, ts = 0, spp = 0, smps = 0;
		int xfer_ok;

		// size of the raw data
		expected_size = HEADER_ID_SIZE ; // size of the header
		for (j = 0 ; j < img->nbc ; j++) {
		    // the multiplier
		    ts = IOBlackBox->typeSize((img->comp[j]).pixt);
		    spp = (img->comp[j]).spp;
		    smps = \
			(img->comp[j]).nx * \
			(img->comp[j]).ny * \
			(img->comp[j]).nz;
		    
		    expected_size += ts * spp * smps;
		}
		img->expected_size = expected_size;
		
		// this is where we differ from put	
		// port = is->open_data_channel(result);
		if (!shm_setup_ok && (setup_shm() != 0)) { // shared memory allocated only if needed
		    result = "Shared memory setup failed\n";
		    ret = 41;
		}

		if (!ret) {
		    // all is well
		    // allocate final data
		    img->rawdata = (char *)malloc(expected_size * sizeof(char));
		    if (img->rawdata == 0)
			ret = 42;
		    else {
			// fake a transmission by socket
			memcpy(img->rawdata, &(img->label), sizeof(int));
			memset((char *)(img->rawdata) + sizeof(int), 0, sizeof(int));
			so_far = 2*sizeof(int);

			// data may come in chunks
			do {
			    xfer_ok = get_data((char *)(img->rawdata) + so_far);
			    if (xfer_ok < 0) {
				ret = 43;
				break;
			    }
			    so_far += xfer_ok;
			} while (so_far < expected_size);

			if (so_far == expected_size) {
			    // build the image
			    char *curp;
			    int   k, l;

			    dbgprintf("putm: transfer was successful, building the image\n");
			    curp = (char *)img->rawdata + HEADER_ID_SIZE; // start of the real data 
			    for (l = 0 ; l < img->nbc ; l++) {
				// allocate buffp, this is tiny, I'm not expecting problems here
				(img->comp[l]).buffp = (void **)malloc(spp * sizeof(void*));
				for (k = 0 ; k < spp ; k++) {
				    (img->comp[l]).buffp[k] = curp;
				    curp += smps * ts; // size of a single spectrum of the given data
				}
			    }
			    // delete the previous image with the same name, if any
			    dbgprintf("putm: deleting previous image with same name");
			    put::delete_header(img->previous_id);
			    // reset the semaphores to known state
			    
			    // finally, post message to GUI to display the image
			    IOBlackBox->pleaseDisplay(img->label);
			}
			// what if data transfer is incomplete? the lines below take care of that.
		    }
		}

		// when we return, data has been transfered already.
		if (so_far == expected_size)
		    sres << "OK! Received: " << so_far << " bytes" << '\n';
		else
		    sres << "Failed! Received: " << so_far << " bytes, Expected: " << expected_size << '\n';
		// overwrite the result with the port number
		result = sres.str();
	    }
	} else {
	    sres << "Put syntax error, expected " << exp_nb << " arguments, \n"
		 << "Got: " << l.size() << "\n";
	    result = sres.str();
	    ret = 5;
	}

	if (ret > 0) {
	    imh.pop_back(); // forget about this header
	    free(img->label); // had been strduped
	    free(img->rawdata); // may be 0, still OK.
	    delete img; // not useful anymore
	}
    } else {
	result = "Put: incomplete argument list\n";
	ret = 10;
    }
    if (ret > 0)
	result += \
	    "\n"
	    "Syntax: put \"name\" nbcomp nx ny nz ox oy oz IMG_TYPE PIX_TYPE spp\n"
	    "        with as many times (nx ny ... spp) as there are nbcomp\n";

    else if (syncDisplay)
	im_draw_finished = false;
    
    return ret;
}

// Sets the shared memory segment up.
//
int putm::setup_shm(void)
{
    char            idshm;
    struct sembuf   sb = {0, -1, 0};

    // ----------- setup the shared memory ------
    idshm = 'B';  // the shared memory is unique to this imview
    if ((shm_key = ftok(ref_pathname, idshm)) == -1) { // We use a different path each time
	dbgprintf("putm::setup: Could not get shm_key, %s\n", strerror(errno));
	return(4); 
    }
    dbgprintf("RefPathname: %s, char: %c, shared memory ID: %p", ref_pathname, idshm, shm_key);
    /* connect to (and possibly create) the segment: */
    if ((shmid = shmget(shm_key, SHM_SIZE, 0644 | IPC_CREAT)) == -1) {
	dbgprintf("putm::setup: Could not get shm ID, %s\n", strerror(errno));
	return(5);
    }
    /* attach to the segment to get a pointer to it: */
    data = (char *)shmat(shmid, 0, 0);
    if (data == (char *)(-1)) {
	dbgprintf("putm::setup: Could not get pointer to data, %s\n", strerror(errno));
	return(6);
    }

    /* allow clients to connect to now existing shared memory segment */
    dbgprintf("setup_shm: server now allowing access to shared memory segment\n");
    sb.sem_num = SEM_ACS;
    sb.sem_op = 1; /* up */
    if (semop(semid, &sb, 1) == -1) {
	dbgprintf("setup_shm: semaphore operation failed\n");
	return(7);
    }
    
    // if we get here, all was set up correctly
    shm_setup_ok = true;
    
    return 0;
}


// to be called in case of errors. Will put the
// semaphores in a consistent state: client access
// allowed and r/w disabled.
int putm::sem_reset(void)
{
    union semun     arg;
    
    // initializes access semaphore to 1: first client must wait
    // until memory is setup.
    arg.val = 0;
    if (semctl(semid, SEM_ACS, SETVAL, arg) == -1) {
	dbgprintf("putm::reset_sem: Could not control SEM_ACS semaphore, %s\n", strerror(errno));
	return 1; // could not control semaphore
    }
    /* initializes read to 0: block */
    arg.val = 0;
    if (semctl(semid, SEM_RD, SETVAL, arg) == -1) {
	dbgprintf("putm::reset_sem: Could not control SEM_RD semaphore, %s\n", strerror(errno));
	return 1; // could not control semaphore
    }
    /* initializes write to 1: allowed to write */
    arg.val = 1;
    if (semctl(semid, SEM_WR, SETVAL, arg) == -1) {
	dbgprintf("putm::reset_sem: Could not control SEM_WR semaphore, %s\n", strerror(errno));
	return 1; // could not control semaphore
    }

    return 0;
}

/*------------------------------------------------------------------------
 *
 * It works this way:
 *
 *   Rv = Read OK, Rx = Read NOT OK.
 *   Wv = Write OK, Wx = Write NOT OK.
 *
 *     SERVER                     |   CLIENT
 *   -----------------------------+------------------------------
 *                                | w:
 *   r:                           | Writes (blocks)
 *   Set RxWv                     | Writes (unblocked)
 *   Reads (blocks)               | Writes
 *         (blocks)               | Writes
 *         (...)                  | Sets RvWx
 *         (unblocked)            | Writes (blocks)
 *   Reads                        | Writes (blocks)
 *   Reads                        | Writes (blocks)
 *   ...                          | ...
 *   go to r:                     | go to w:
 *   -----------------------------+------------------------------
 *
 *   This must be completely standard but it took me a couple of
 *   hours to get it right again.
 *
 *-----------------------------------------------------------------------*/

static jmp_buf sysv_alrm_env;

static void sysv_alarmHandler(int sig)
{
    // reset the signal handler
    signal(sig, sysv_alarmHandler);
    longjmp(sysv_alrm_env, 1);
}

// this reads what's in the shared memory buffer.
// the semaphores are left in a consistent state as long
// as there are no errors. If errors are detected, a reset() will
// be necessary.
int putm::get_data(char *dest)
{
    int           retval;
    struct sembuf sb = {0, -1, 0};

    signal(SIGALRM, sysv_alarmHandler); // timeout is likely if the client screws up

    // ----------- Blocking waiting for client to fill data buffer, and unlock it
    if (sigsetjmp(sysv_alrm_env, 1) == 0) {
	// trying to read. Should block as long as the client is writing.
	dbgprintf("putm: server now trying to read\n");
	sb.sem_num = SEM_RD;
	sb.sem_op = -1; /* down */
	if (semop(semid, &sb, 1) == -1) {
	    dbgprintf("putm: semaphore operation failed\n");
	    return(-1);
	}

	// actual read operation
	memcpy(&retval, data, sizeof(int));
	if ((retval > 0) && (retval <= SHM_SIZE))
	    memcpy(dest, data+sizeof(int), retval);
	
	// allow writing on to the buffer
	dbgprintf("putm: Server allows client to write to buffer\n");
	alarm(15); /* timeout is in 15 seconds */
	sb.sem_num = SEM_WR;
	sb.sem_op = 1; /* up */
	if (semop(semid, &sb, 1) == -1) {
	    dbgprintf("putm: semaphore operation failed\n");
	    return(-1);
	}
    } else {
	dbgprintf("putm: Timeout! this is bad...\n");
	return -7;
    }
	
    // if we are here, all is well, the client has filled the data!
    // it is the caller's role to do something with it. We
    // return the first 4 bytes interpreted as an int.
    
    alarm(0); // cancel the alarm

    // spot of checking
    if ((retval <= 0) || (retval > SHM_SIZE))
	retval = -9;
    
    return retval;
}

//-------------------------------------------------
// this only get into play if sysv ipc are not available
// on the system in question.
 
#else // no SYSV IPC

int putm::perform(vector <string> &l, string &result)
{
    result = "Unix SVR4 IPC Not supported\n";
    return 1;
}

#endif // HAVE_SYSV_IPC


/*------------------------------------------------------------------------
 *      
 * Same deal, for POSIX style IPC
 *
 * POSIX is nicer because the cruft laying around in case of a 
 * crash of either the server or the client is on the filesystem,
 * not in the kernel. Most people don't know about ipcs and ipcrm
 * to check out if they find out they are running out of resources.
 * 
 *      
 *-----------------------------------------------------------------------*/

#ifdef HAVE_POSIX_IPC

// define class variable
char        putp::ref_pathname[DFLTSTRLEN];
const char *putp::posix_tmp_path = "/tmp/px_imview_XXXXXX";

int ppath::perform(vector<string> &l, string &result)
{
    static string posix_path = string(putp::ref_pathname) + "\n";

    result=posix_path;

    return 0;
}

putp::putp()
{
    shm_setup_ok = false;
    sem_setup_ok = false; // nothing is set up yet
    putsem[SEM_ACCESS] = putsem[SEM_READ] = putsem[SEM_WRITE] = (sem_t *)0; // initialized state.
    px_templ_name = 0;

    // create a valid unique file path (don't open it)
    strcpy(ref_pathname, posix_tmp_path);
    int tempfd= mkstemp(ref_pathname);

    if (tempfd < 0) {
        // find error, will be print out if something goes wrong when putp is
        // asked.
        errformat("putp::putp : could not find unique temporary file name", errno);
        return;
    }

    // create semaphores
    // 1: first massage the temporary file name, get rid of /tmp in the name
    const char *bsname = strstr(ref_pathname+1, "/")+1; // skip both '/'
    if (bsname != NULL) {
        // Jeez it's hard to be standard
        px_templ_name = px_ipc_name(bsname); // will need to be freed 

        if (init_sem(STR_ACCESS, SEM_ACCESS, 0) == SEM_FAILED)
            return;

        if (init_sem(STR_READ, SEM_READ, 0) == SEM_FAILED)
            return;

        if (init_sem(STR_WRITE, SEM_WRITE, 1) == SEM_FAILED)
            return;
    }

    // if we get there, all is fine with semaphores
    dbgprintf("POSIX IPC constructed, temporary file = %s\n", ref_pathname);
    sem_setup_ok = true;

    return;
}

putp::~putp()
{
    dbgprintf("Putp destructor called\n");
    
    // close the semaphores
    for (int i = 0 ; i < 3 ; ++i) {
        if (putsem[i] != SEM_FAILED) 
            sem_close(putsem[i]);
    }
    
    // actually remove them from the filesystem
    remove_sem(STR_ACCESS);
    remove_sem(STR_READ);
    remove_sem(STR_WRITE);

    // close and unlink the shared memory file
    if (shmfd > 0) {
        close(shmfd); // can fail
        if (data != 0)
            munmap(data, SHM_SIZE); // unmap the data
        remove_shm(); // unlinks the file
    }

    if (tempfd >= 0) {
        // close(tempfd);  // the O/S will do that...
        dbgprintf("POSIX IPC destructed, removing temporary file = %s\n", ref_pathname);
        unlink(ref_pathname); // actually remove the file
    }
    free(px_templ_name); // It's OK to free a NULL pointer


    return;
}

sem_t *putp::init_sem(const char *postfix, int which_sem, int start_val)
{
    char   semaphore_name[DFLTSTRLEN];
    sem_t *res;

    dbgprintf("putp: initializing POSIX semaphore %s\n", postfix);

    strcpy(semaphore_name, px_templ_name);
    strcat(semaphore_name, postfix);
    dbgprintf("putp: semaphore name: %s\n", semaphore_name);
    res = putsem[which_sem] = sem_open(semaphore_name, O_CREAT|O_EXCL, IPC_FILE_MODE, start_val); // access allowed

    if (res == SEM_FAILED) {
        errformat("putp::putp : cannot initialize semaphore", errno);
    }

    return res;
}

void putp::remove_sem(const char *postfix)
{
    char  semaphore_name[DFLTSTRLEN];

    strcpy(semaphore_name, px_templ_name);
    strcat(semaphore_name, postfix);
    sem_unlink(semaphore_name);

    return;
}

void putp::remove_shm(void)
{
    char shm_name[DFLTSTRLEN];

    strcpy(shm_name,  px_templ_name);
    strcat(shm_name, STR_SHM);
    shm_unlink(shm_name);
}

// POSIX naming convention is actually non-standard. How
// stupid is that.
char *putp::px_ipc_name(const char *name)
{
    char	*dir, *dst;
    const char  *slash;
    
    if ( (dst = (char *)malloc(PATH_MAX * sizeof(char))) == NULL)
        return(NULL);

    /* can override default directory with environment variable */
    if ( (dir = getenv("PX_IPC_NAME")) == NULL) {
#ifdef	POSIX_IPC_PREFIX
        dir = POSIX_IPC_PREFIX;		/* from "config.h" */
#else
        dir = "/tmp/";				/* default */
#endif
    }
		/* dir must end in a slash */
    slash = (dir[strlen(dir) - 1] == '/') ? "" : "/";
    snprintf(dst, PATH_MAX, "%s%s%s", dir, slash, name);
    
    return(dst);			/* caller can free() this pointer */
}

// helper method
// saves the error into the error buffer.
// this error buffer is printed on the console if for
// some reason an error occured and the server is unable
// to load up image via POSIX shared memory.
void putp::errformat(const char *msg_prefix, int myerrno)
{
    //strerror_r(myerrno, errbuff, DFLTSTRLEN);
    //snprintf(perrbuff, DFLTSTRLEN, "*** %s, %s", msg_prefix, errbuff);
    dbgprintf("%s: %s\n", msg_prefix, strerror(myerrno));

    return;
}


// Sets the shared memory segment up.
//
int putp::setup_shm(void)
{
    char  shm_name[DFLTSTRLEN];
    // ----------- setup the shared memory ------

    strcpy(shm_name, px_templ_name);
    strcat(shm_name, STR_SHM);

    shmfd = shm_open(shm_name, O_CREAT|O_EXCL|O_RDWR, IPC_FILE_MODE);
    if (shmfd < 0) {
        errformat("putp::setup_shm : cannot initialize shared memory", errno);
        return 1;
    } else {
        dbgprintf("putp::setup_shm : shared memory file is %sw\n", shm_name);
    }
    
    // set the size of the segment
    if (ftruncate(shmfd, SHM_SIZE) < 0) {
        errformat("putp::setup_shm : cannot set size of shared memory segment", errno);
        return 2;
    }
    
    // attach the memory block
    if ((data = (char *) mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0)) == MAP_FAILED) {
        errformat("putp::setup_shm : cannot map shared memory segment", errno);
        return 3;
    }
    
    // now allow access to shared memory segment
    dbgprintf("setup_shm: server now allowing access to shared memory segment\n");
    if (sem_post(putsem[SEM_ACCESS]) < 0) {
        errformat("putp::setup_shm : Post semaphore operation failed", errno);
        return 4;
    }
    
    // if we get here, all was set up correctly
    shm_setup_ok = true;
    
    return 0;
}

static jmp_buf px_alrm_env;

static void px_alarmHandler(int sig)
{
    // reset the signal handler
    signal(sig, px_alarmHandler);
    longjmp(px_alrm_env, 1);
}

// this reads what's in the shared memory buffer.
// the semaphores are left in a consistent state as long
// as there are no errors. If errors are detected, a reset() will
// be necessary.
int putp::get_data(char *dest)
{
    int           retval;

    signal(SIGALRM, px_alarmHandler); // timeout is likely if the client screws up

    // ----------- Blocking waiting for client to fill data buffer, and unlock it
    if (sigsetjmp(px_alrm_env, 1) == 0) {
	// trying to read. Should block as long as the client is writing.
	dbgprintf("putp: server now trying to read\n");

        if (sem_wait(putsem[SEM_READ]) < 0) {
	    errformat("putp: semaphore operation failed\n", errno);
	    return(-1);
	}

	// actual read operation
        retval = ((int*)data)[0]; // first word is size of data
	if ((retval > 0) && (retval <= SHM_SIZE))
	    memcpy(dest, data+sizeof(int), retval);
	
	// allow writing on to the buffer
	dbgprintf("putp: Server allows client to write to buffer\n");
	alarm(15); /* timeout is in 15 seconds */
	if (sem_post(putsem[SEM_WRITE]) < 0) {
	    errformat("putp: semaphore operation failed\n", errno);
	    return(-1);
	}
    } else {
	dbgprintf("putp: Timeout! this is bad...\n");
	return -7;
    }
	
    // if we are here, all is well, the client has filled the data!
    // it is the caller's role to do something with it. We
    // return the first 4 bytes interpreted as an int.
    
    alarm(0); // cancel the alarm

    // spot of checking
    if ((retval <= 0) || (retval > SHM_SIZE))
	retval = -9;
    
    return retval;
}

// long scary function
// does nearly the same thing as putp::perform
int putp::perform(vector <string> &l, string &result)
{
    int             i, j, ret, expected_size;
    int             so_far = 0;
    char           *newlabel;
    size_t          exp_nb;
    ostringstream   sres;
    IMAGE_HEADER   *img, *previmg;

    dbgprintf("Data transfer using POSIX style shared memory\n");
    if (!sem_setup_ok) {
	result = "Internal semaphore handling failed. POSIX shared memory put not available\n" + \
            string(errbuff);
	return 1;
    }
    
    if (l.size() > 3) {
	ret = 0;
	img = new IMAGE_HEADER;

	img->label = 0;     // this is important, as otherwise find_header will choke on an invalid pointer
	img->needswap = 0;  // coming from the same host, so we never need to swap the byte order of the data
	imh.push_back(img); // imh belong to put::, the parent class
	// fill the header
	newlabel = strdup(l[0].c_str());
	// find out if there is another header with the same
	// name in use already. If so it will have to be delete
	// if the data transmission goes fine.
	dbgprintf("Looking for previous header with same label: %s\n", newlabel);
	previmg = put::find_header(newlabel); // might be 0

	if (previmg != 0) {
	    dbgprintf("Unique ID associated with this previous header: %d\n",
		      previmg->unique_id);
	    img->previous_id = previmg->unique_id;
	} else {
	    dbgprintf("No previous unique id found\n");
	    img->previous_id = 0;
	}
	
	img->label = newlabel;
	img->unique_id = id_pool++; // each header will have its own unique ID.
	img->nbc = strtol(l[1].c_str(),0,0);
	img->rawdata = 0 ; // nothing received yet

	exp_nb = 9 * img->nbc + 2; // expected number of arguments in the list
	if ((img->nbc > 0) && (exp_nb == l.size())) {
	    // create the suitable number of components
	    img->comp = new IMAGECOMP_HEADER[img->nbc];
	    for (i = 0, j = 2 ; i < img->nbc ; i++, j+=9) {
		(img->comp[i]).nx = atoi(l[j].c_str());   
		(img->comp[i]).ny = atoi(l[j+1].c_str()); 
		(img->comp[i]).nz = atoi(l[j+2].c_str()); 
		(img->comp[i]).ox = atoi(l[j+3].c_str());
		(img->comp[i]).oy = atoi(l[j+4].c_str());
		(img->comp[i]).oz = atoi(l[j+5].c_str());
		(img->comp[i]).imgt = imview_interpreter->translate_img(l[j+6]);
		(img->comp[i]).pixt = imview_interpreter->translate_pix(l[j+7]);
		(img->comp[i]).spp = atoi(l[j+8].c_str());
		(img->comp[i]).buffp = 0; // to be filled when we actually get some data.

		// check for each component:
		if (((img->comp[i]).nx * (img->comp[i]).ny * (img->comp[i]).nz) == 0) {
		    result = "Putp: no data\n";
		    ret = 2;
		    break;
		} else if ((img->comp[i]).imgt == IM_ERROR) {
		    result = "Putp: incorrect image type\n";
		    ret = 3;
		    break;
		} else if ((img->comp[i]).pixt == IM_INVALID) {
		    result = "Putp: incorrect pixel type\n";
		    ret = 4;
		    break;
		}
	    }
	    // check that what we got was sensible
	    if (ret == 0) {
		int j, ts = 0, spp = 0, smps = 0;
		int xfer_ok;

		// size of the raw data
		expected_size = HEADER_ID_SIZE ; // size of the header
		for (j = 0 ; j < img->nbc ; j++) {
		    // the multiplier
		    ts = IOBlackBox->typeSize((img->comp[j]).pixt);
		    spp = (img->comp[j]).spp;
		    smps = \
			(img->comp[j]).nx * \
			(img->comp[j]).ny * \
			(img->comp[j]).nz;
		    
		    expected_size += ts * spp * smps;
		}
		img->expected_size = expected_size;
		
		// this is where we differ from put	
		if (!shm_setup_ok && (setup_shm() != 0)) { // shared memory allocated only if needed
		    result = "Shared memory setup failed\n";
		    ret = 41;
		}

		if (!ret) {
		    // all is well
		    // allocate final data
		    img->rawdata = (char *)malloc(expected_size * sizeof(char));
		    if (img->rawdata == 0)
			ret = 42;
		    else {
			// fake a transmission by socket
			memcpy(img->rawdata, &(img->label), sizeof(int));
			memset((char *)(img->rawdata) + sizeof(int), 0, sizeof(int));
			so_far = 2*sizeof(int);

			// data may come in chunks
			do {
			    xfer_ok = get_data((char *)(img->rawdata) + so_far);
			    if (xfer_ok < 0) {
				ret = 43;
				break;
			    }
			    so_far += xfer_ok;
			} while (so_far < expected_size);

			if (so_far == expected_size) {
			    // build the image
			    char *curp;
			    int   k, l;

			    dbgprintf("putp: transfer was successful, building the image\n");
			    curp = (char *)img->rawdata + HEADER_ID_SIZE; // start of the real data 
			    for (l = 0 ; l < img->nbc ; l++) {
				// allocate buffp, this is tiny, I'm not expecting problems here
				(img->comp[l]).buffp = (void **)malloc(spp * sizeof(void*));
				for (k = 0 ; k < spp ; k++) {
				    (img->comp[l]).buffp[k] = curp;
				    curp += smps * ts; // size of a single spectrum of the given data
				}
			    }
			    // delete the previous image with the same name, if any
			    dbgprintf("putp: deleting previous image with same name");
			    put::delete_header(img->previous_id);
			    // reset the semaphores to known state
			    
			    // finally, post message to GUI to display the image
			    IOBlackBox->pleaseDisplay(img->label);
			}
			// what if data transfer is incomplete? the lines below take care of that.
		    }
		}

		// when we return, data has been transfered already.
		if (so_far == expected_size)
		    sres << "OK! Received: " << so_far << " bytes" << '\n';
		else
		    sres << "Failed! Received: " << so_far << " bytes, Expected: " << expected_size << '\n';
		// overwrite the result with the port number
		result = sres.str();
	    }
	} else {
	    sres << "Putp syntax error, expected " << exp_nb << " arguments, \n"
		 << "Got: " << l.size() << "\n";
	    result = sres.str();
	    ret = 5;
	}

	if (ret > 0) {
	    imh.pop_back(); // forget about this header
	    free(img->label); // had been strduped
	    free(img->rawdata); // may be 0, still OK.
	    delete img; // not useful anymore
	}
    } else {
	result = "Putp: incomplete argument list\n";
	ret = 10;
    }
    if (ret > 0)
	result += \
	    "\n"
	    "Syntax: put \"name\" nbcomp nx ny nz ox oy oz IMG_TYPE PIX_TYPE spp\n"
	    "        with as many times (nx ny ... spp) as there are nbcomp\n";

    else if (syncDisplay)
	im_draw_finished = false;
    
    return ret;
}

#else // HAVE_POSIX_IPC

int ppath::perform(vector<string> &l, string &result)
{
    result = "531 POSIX IPC not supported\n";
    return 1;
}

int putp::perform(vector <string> &l, string &result)
{
    result = "531 POSIX IPC Not supported\n";
    return 1;
}

#endif // HAVE_POSIX_IPC

#endif // HAVE_PTHREADS
