source: pymolproxy/trunk/pymolproxy.c @ 6629

Last change on this file since 6629 was 6629, checked in by ldelgass, 8 years ago

Add idle timeout option to pymolproxy

File size: 69.5 KB
Line 
1/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * ----------------------------------------------------------------------
4 * proxypymol.c
5 *
6 * This module creates the Tcl interface to the pymol server.  It acts as
7 * a go-between establishing communication between a molvisviewer widget
8 * and the pymol server. The communication protocol from the molvisviewer
9 * widget is the Tcl language.  Commands are then relayed to the pymol
10 * server.  Responses from the pymol server are translated into Tcl
11 * commands and send to the molvisviewer widget. For example, resulting
12 * image rendered offscreen is returned as ppm-formatted image data.
13 *
14 *  Copyright (c) 2004-2016  HUBzero Foundation, LLC
15 *
16 *  See the file "license.terms" for information on usage and
17 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
18 * ======================================================================
19 *
20 * Authors:
21 *   Nicholas Kisseberth <nkissebe@purdue.edu>
22 *   George A. Howlett <gah@purdue.edu>
23 *   Leif Delgass <ldelgass@purdue.edu>
24 */
25
26/*
27 * Notes:
28 *
29 * The proxy should not maintain any state information from the
30 * client, other that what it needs for event (rotate, pan, zoom,
31 * atom scale, bond thickness, etc.) compression.  This is because
32 * the connection is periodically broken (timeout, error, etc.).
33 * It's the responsibility of the client (molvisviewer) to restore
34 * the settings of the previous view.  The proxy is just a relay
35 * between the client and the pymol server.
36 */
37/*
38 *   +--------------+          +------------+          +----------+
39 *   |              | (stdin)  |            |  (stdin) |          |
40 *   |              |--------->|            |--------->|          |
41 *   | molvisviewer |          | pymolproxy |          | pymol    |
42 *   |  (client)    |          |            |          | (server) |(stderr)
43 *   |              | (stdout) |            | (stdout) |          |-------> file
44 *   |              |<---------|            |<---------|          |
45 *   +--------------+          +------------+          +----------+
46 *
47 * We're using a simple 2 thread setup: one for read client requests and
48 * relaying them to the pymol server, another reading pymol server output,
49 * and sending images by to the client.  The reason is because the client
50 * blocks on writes.  While it is sending requests or data, the proxy
51 * must be available to accept it.  Likewise, the proxy buffers images in a
52 * linked list when the client isn't ready to receive them.
53 *
54 * Reader thread:
55 * The communication between the pymol server and proxy is asynchronous.
56 * The proxy translates commands from the client and sends them to the
57 * server without waiting for a response.  It watches the server's
58 * stdout in a separate thread snooping for image data.
59 *
60 * Writer thread:
61 * The communication between the client and the proxy is also asynchronous.
62 * The client commands are read when they become available on the socket.
63 * The proxy writes images to the client when it can, otherwise it stores
64 * them in a list in memory.  This should prevent deadlocks from occuring:
65 * the client sends a request, while the proxy is writing an image.
66 */
67
68#include <assert.h>
69#include <ctype.h>
70#include <errno.h>
71#include <fcntl.h>
72#include <getopt.h>
73#include <poll.h>
74#include <stdio.h>
75#include <stdlib.h>
76#include <string.h>
77#include <sys/stat.h>
78#include <sys/time.h>
79#include <sys/times.h>
80#include <sys/types.h>
81#include <sys/wait.h>
82#include <time.h>
83#include <syslog.h>
84#include <unistd.h>
85#include <tcl.h>
86#include <pthread.h>
87#include <md5.h>
88
89#define PYMOLPROXY_VERSION "1.1.0"
90
91#undef INLINE
92#ifdef __GNUC__
93#  define INLINE __inline__
94#else
95#  define INLINE
96#endif
97
98#define FALSE 0
99#define TRUE  1
100
101static FILE *frecord = NULL;
102static FILE *flog = NULL;
103static int recording = FALSE;
104static int pymolIsAlive = TRUE;
105static int statsFile = -1;
106static long idleTimeout = -1L;
107
108#define READ_DEBUG  1
109#define WRITE_DEBUG 1
110#define EXEC_DEBUG  1
111#define LOG_PYMOL_STDERR 0
112
113#define FORCE_UPDATE            (1<<0)
114#define CAN_UPDATE              (1<<1)
115#define INVALIDATE_CACHE        (1<<3)
116#define ATOM_SCALE_PENDING      (1<<4)
117#define STICK_RADIUS_PENDING    (1<<5)
118#define ROTATE_PENDING          (1<<6)
119#define PAN_PENDING             (1<<7)
120#define ZOOM_PENDING            (1<<8)
121#define UPDATE_PENDING          (1<<9)
122#define VIEWPORT_PENDING        (1<<10)
123
124#define IO_TIMEOUT (30000)
125#define CLIENT_READ  STDIN_FILENO
126#define CLIENT_WRITE STDOUT_FILENO
127
128#ifndef LOGDIR
129#define LOGDIR "/tmp"
130#endif /* LOGDIR */
131
132#ifndef STATSDIR
133#define STATSDIR "/var/tmp/visservers"
134#endif  /*STATSDIR*/
135
136#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
137
138typedef struct {
139    pid_t pid;                          /* Child process. */
140    size_t numFrames;                   /* # of frames sent to client. */
141    size_t numBytes;                    /* # of bytes for all frames. */
142    size_t numCommands;                 /* # of commands executed */
143    double cmdTime;                     /* Elapsed time spend executing
144                                         * commands. */
145    struct timeval start;               /* Start of elapsed time. */
146} Stats;
147
148static Stats stats;
149
150typedef struct Image {
151    struct Image *nextPtr;              /* Next image in chain of images. The
152                                         * list is ordered by the most
153                                         * recently received image from the
154                                         * pymol server to the least. */
155    struct Image *prevPtr;              /* Previous image in chain of
156                                         * images. The list is ordered by the
157                                         * most recently received image from
158                                         * the pymol server to the least. */
159    int id;
160    ssize_t numWritten;                 /* Number of bytes of image data
161                                         * already delivered.*/
162    size_t bytesLeft;                   /* Number of bytes of image data left
163                                         * to delivered to the client. */
164    unsigned char data[1];              /* Start of image data. We allocate
165                                         * the size of the Image structure
166                                         * plus the size of the image data. */
167} Image;
168
169#define BUFFER_SIZE 4096
170
171typedef struct {
172    Image *headPtr, *tailPtr;           /* List of images to be delivered to
173                                         * the client.  The most recent images
174                                         * are in the front of the list. */
175} ImageList;
176
177typedef struct {
178    const char *ident;
179    unsigned char *bytes;
180    size_t fill;
181    size_t mark;
182    size_t newline;
183    size_t bufferSize;
184    int lastStatus;
185    int fd;
186} ReadBuffer;
187
188#define BUFFER_OK        0
189#define BUFFER_ERROR    -1
190#define BUFFER_CONTINUE -2
191#define BUFFER_EOF      -3
192
193typedef struct {
194    Tcl_Interp *interp;
195    unsigned int flags;                 /* Various flags. */
196    int sin, sout;                      /* Server file descriptors. */
197    int cin, cout;                      /* Client file descriptors. */
198    ReadBuffer client;                  /* Read buffer for client input. */
199    ReadBuffer server;                  /* Read buffer for server output. */
200    int frame;
201    int rockOffset;
202    int cacheId;
203    int status;
204    int width, height;                  /* Size of viewport. */
205    float xAngle, yAngle, zAngle;       /* Euler angles of pending
206                                         * rotation.  */
207    float sphereScale;                  /* Atom scale of pending re-scale. */
208    float stickRadius;                  /* Bond thickness of pending
209                                         * re-scale. */
210    float zoom;
211    float xPan, yPan;
212    pid_t pid;
213} PymolProxy;
214
215#ifdef WANT_DEBUG
216#define DEBUG(...) PrintToLog(__VA_ARGS__)
217#else
218#define DEBUG(...)
219#endif
220
221#define ERROR(...) SysLog(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
222#define TRACE(...) SysLog(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
223#define WARN(...)  SysLog(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
224#define INFO(...)  SysLog(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
225
226#if 0
227static const char *syslogLevels[] = {
228    "emergency", /* System is unusable */
229    "alert",     /* Action must be taken immediately */
230    "critical",  /* Critical conditions */
231    "error",     /* Error conditions */
232    "warning",   /* Warning conditions */
233    "notice",    /* Normal but significant condition */
234    "info",      /* Informational */
235    "debug",     /* Debug-level messages */
236};
237#endif
238
239#ifdef WANT_DEBUG
240static void
241PrintToLog(const char *format, ...)
242{
243    va_list args;
244
245    if (flog == NULL) return;
246
247    va_start(args, format);
248    fprintf(flog, "pymolproxy: ");
249    vfprintf(flog, format, args);
250    va_end(args);
251    fprintf(flog, "\n");
252    fflush(flog);
253}
254#endif
255
256void
257SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
258{
259#define MSG_LEN (2047)
260    char message[MSG_LEN+1];
261    const char *s;
262    int length;
263    va_list lst;
264
265    va_start(lst, fmt);
266    s = strrchr(path, '/');
267    if (s == NULL) {
268        s = path;
269    } else {
270        s++;
271    }
272    length = snprintf(message, MSG_LEN, "%s:%d: ", s, lineNum);
273    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
274    message[MSG_LEN] = '\0';
275    va_end(lst);
276
277    if (priority == LOG_DEBUG) {
278        DEBUG("%s", message);
279    } else {
280        syslog(priority, message, length);
281    }
282}
283
284static void
285Record(const char *format, ...)
286{
287    va_list args;
288
289    if (frecord == NULL) return;
290
291    va_start(args, format);
292    vfprintf(frecord, format, args);
293    va_end(args);
294    fflush(frecord);
295}
296
297static int
298SendToPymol(PymolProxy *proxyPtr, const char *format, ...)
299{
300    va_list ap;
301    char buffer[BUFSIZ];
302    int result;
303    ssize_t numWritten;
304    size_t length;
305
306    va_start(ap, format);
307    result = vsnprintf(buffer, BUFSIZ, format, ap);
308    va_end(ap);
309    if (result >= BUFSIZ) {
310        ERROR("Buffer too small for command (len=%d)", result);
311        proxyPtr->status = TCL_ERROR;
312        return proxyPtr->status;
313    }
314
315#if EXEC_DEBUG
316    DEBUG("to-pymol>(%s) len=%d", buffer, result);
317#endif
318    if (recording) {
319        Record("%s\n", buffer);
320    }
321
322    /* Write the command out to the server. */
323    length = strlen(buffer);
324    numWritten = write(proxyPtr->sin, buffer, length);
325    if (numWritten != length) {
326        ERROR("short write to pymol (wrote=%d, should have been %d): %s",
327              numWritten, length, strerror(errno));
328        proxyPtr->status = TCL_ERROR;
329    }
330    return proxyPtr->status;
331}
332
333static void
334FlushReadBuffer(ReadBuffer *bp)
335{
336    bp->fill = bp->mark = 0;
337    bp->newline = 0;
338}
339
340/**
341 * \param[in] fd File descriptor to read
342 * \param[in] bufferSize Block size to use in internal buffer
343 */
344
345static void
346InitReadBuffer(ReadBuffer *bp, const char *id, int fd, size_t bufferSize)
347{
348    bp->ident = id;
349    bp->bufferSize = bufferSize;
350    bp->fd = fd;
351    bp->lastStatus = BUFFER_OK;
352    bp->bytes = malloc(bufferSize);
353    FlushReadBuffer(bp);
354}
355
356/**
357 * \brief Checks if a new line is currently in the buffer.
358 *
359 * \return the index of the character past the new line.
360 */
361static size_t
362NextLine(ReadBuffer *bp)
363{
364    /* Check for a newline in the current buffer. */
365
366    if (bp->newline > 0) {
367        return bp->newline;
368    } else {
369        unsigned char *p;
370        size_t newline;
371
372        p = (unsigned char *)memchr(bp->bytes + bp->mark, '\n',
373                                    bp->fill - bp->mark);
374        if (p == NULL) {
375            newline = 0;
376        } else {
377            newline = (p - bp->bytes + 1);
378        }
379        bp->newline = newline;
380        return newline;
381    }
382}
383
384/**
385 * \brief Fills the buffer with available data.
386 *
387 * Any existing data in the buffer is moved to the front of the buffer,
388 * then the channel is read to fill the rest of the buffer.
389 *
390 * \return If an error occur when reading the channel, then ERROR is
391 * returned. ENDFILE is returned on EOF.  If the buffer can't be filled,
392 * then CONTINUE is returned.
393 */
394static int
395FillReadBuffer(ReadBuffer *bp)
396{
397    ssize_t numRead;
398    size_t bytesLeft;
399
400#if READ_DEBUG
401    DEBUG("Enter FillReadBuffer for %s: mark=%lu fill=%lu",
402          bp->ident, bp->mark, bp->fill);
403#endif
404    if (bp->mark >= bp->fill) {
405        FlushReadBuffer(bp); /* Fully consumed buffer */
406    }
407    if (bp->mark > 0) {
408        /* Some data has been consumed. Move the unconsumed data to the front
409         * of the buffer. */
410#if READ_DEBUG
411        DEBUG("memmove %lu bytes", bp->fill - bp->mark);
412#endif
413        memmove(bp->bytes, bp->bytes + bp->mark,
414                bp->fill - bp->mark);
415        bp->fill -= bp->mark;
416        bp->mark = 0;
417    }
418
419    bytesLeft = bp->bufferSize - bp->fill;
420#if READ_DEBUG
421    DEBUG("going to read %lu bytes", bytesLeft);
422#endif
423    numRead = read(bp->fd, bp->bytes + bp->fill, bytesLeft);
424    if (numRead == 0) {
425        /* EOF */
426#if READ_DEBUG
427        DEBUG("EOF found reading %s buffer (fill=%d): ",
428              bp->ident, bp->fill);
429#endif
430        return BUFFER_EOF;
431    }
432    if (numRead < 0) {
433        if (errno != EAGAIN) {
434            ERROR("error reading %s buffer: %s", bp->ident, strerror(errno));
435            return BUFFER_ERROR;
436        }
437#if READ_DEBUG
438        DEBUG("Short read for buffer");
439#endif
440        return BUFFER_CONTINUE;
441    }
442    bp->fill += numRead;
443#if READ_DEBUG
444    DEBUG("Read %lu bytes", numRead);
445#endif
446    return ((size_t)numRead == bytesLeft) ? BUFFER_OK : BUFFER_CONTINUE;
447}
448
449/**
450 * \brief Read the requested number of bytes from the buffer.
451
452 * Fails if the requested number of bytes are not immediately
453 * available. Never should be short.
454 */
455static int
456ReadFollowingData(ReadBuffer *bp, unsigned char *out, size_t numBytes)
457{
458#if READ_DEBUG
459    DEBUG("Enter ReadFollowingData %s", bp->ident);
460#endif
461    while (numBytes > 0) {
462        size_t bytesLeft;
463
464        bytesLeft = bp->fill - bp->mark;
465        if (bytesLeft > 0) {
466            int size;
467
468            /* Pull bytes out of the buffer, updating the mark. */
469            size = (bytesLeft >  numBytes) ? numBytes : bytesLeft;
470            memcpy(out, bp->bytes + bp->mark, size);
471            bp->mark += size;
472            numBytes -= size;
473            out += size;
474        }
475        if (numBytes == 0) {
476            /* Received requested # bytes. */
477            return BUFFER_OK;
478        }
479        /* Didn't get enough bytes, need to read some more. */
480        bp->lastStatus = FillReadBuffer(bp);
481        if ((bp->lastStatus == BUFFER_ERROR) ||
482            (bp->lastStatus == BUFFER_EOF)) {
483            return bp->lastStatus;
484        }
485    }
486    return BUFFER_OK;
487}
488
489/**
490 * \brief Returns the next available line (terminated by a newline)
491 *
492 * If insufficient data is in the buffer, then the channel is
493 * read for more data.  If reading the channel results in a
494 * short read, CONTINUE is returned and *numBytesPtr is set to 0.
495 */
496static int
497GetLine(ReadBuffer *bp, size_t *numBytesPtr, const char **bytesPtr)
498{
499#if READ_DEBUG
500    DEBUG("Enter GetLine");
501#endif
502    *numBytesPtr = 0;
503    *bytesPtr = NULL;
504
505    bp->lastStatus = BUFFER_OK;
506    for (;;) {
507        size_t newline;
508
509        newline = NextLine(bp);
510        if (newline > 0) {
511            /* Start of the line. */
512            *bytesPtr = (const char *)(bp->bytes + bp->mark);
513            /* Number of bytes in the line. */
514            *numBytesPtr = newline - bp->mark;
515            bp->mark = newline;
516            bp->newline = 0;
517            return BUFFER_OK;
518        }
519        /* Couldn't find a newline, so it may be that we need to read some
520         * more. Check first that last read wasn't a short read. */
521        if (bp->lastStatus == BUFFER_CONTINUE) {
522            /* No complete line just yet. */
523            return BUFFER_CONTINUE;
524        }
525        /* Try to add more data to the buffer. */
526        bp->lastStatus = FillReadBuffer(bp);
527        if (bp->lastStatus == BUFFER_ERROR ||
528            bp->lastStatus == BUFFER_EOF) {
529            return bp->lastStatus;
530        }
531        /* OK or CONTINUE */
532    }
533    return BUFFER_CONTINUE;
534}
535
536static int
537IsLineAvailable(ReadBuffer *bp)
538{
539    return (NextLine(bp) > 0);
540}
541
542static int
543WaitForNextLine(ReadBuffer *bp, struct timeval *tvPtr)
544{
545    fd_set readFds;
546    int n;
547
548    if (IsLineAvailable(bp)) {
549        return 1;
550    }
551    FD_ZERO(&readFds);
552    FD_SET(bp->fd, &readFds);
553    n = select(bp->fd+1, &readFds, NULL, NULL, tvPtr);
554    return (n > 0);
555}
556
557INLINE static void
558clear_error(PymolProxy *proxyPtr)
559{
560    proxyPtr->status = TCL_OK;
561}
562
563static int
564GetStatsFile(const char *string)
565{
566    Tcl_DString ds;
567    int i;
568    char fileName[33];
569    const char *path;
570    int length;
571    char pidstr[200];
572    md5_state_t state;
573    md5_byte_t digest[16];
574
575    if ((string == NULL) || (statsFile >= 0)) {
576        return statsFile;
577    }
578    /* By itself the client's key/value pairs aren't unique.  Add in the
579     * process id of this render server. */
580    Tcl_DStringInit(&ds);
581    Tcl_DStringAppend(&ds, string, -1);
582    Tcl_DStringAppendElement(&ds, "pid");
583    sprintf(pidstr, "%d", getpid());
584    Tcl_DStringAppendElement(&ds, pidstr);
585
586    /* Create a md5 hash of the key/value pairs and use it as the file name. */
587    string = Tcl_DStringValue(&ds);
588    length = strlen(string);
589    md5_init(&state);
590    md5_append(&state, (const md5_byte_t *)string, length);
591    md5_finish(&state, digest);
592    for (i = 0; i < 16; i++) {
593        sprintf(fileName + i * 2, "%02x", digest[i]);
594    }
595    Tcl_DStringSetLength(&ds, 0);
596    Tcl_DStringAppend(&ds, STATSDIR, -1);
597    Tcl_DStringAppend(&ds, "/", 1);
598    Tcl_DStringAppend(&ds, fileName, 32);
599    path = Tcl_DStringValue(&ds);
600
601    statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
602    Tcl_DStringFree(&ds);
603    if (statsFile < 0) {
604        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
605        return -1;
606    }
607    return statsFile;
608}
609
610static int
611WriteToStatsFile(int f, const char *s, size_t length)
612{
613
614    if (f >= 0) {
615        ssize_t numWritten;
616
617        numWritten = write(f, s, length);
618        if (numWritten == (ssize_t)length) {
619            close(dup(f));
620        }
621    }
622    return 0;
623}
624
625static int
626ServerStats(int code)
627{
628    double session_time;
629    char buf[BUFSIZ];
630    Tcl_DString ds;
631    int result;
632    int f;
633    struct timeval now;
634
635    /* Get ending time.  */
636    gettimeofday(&now, NULL);
637    session_time = CVT2SECS(now) - CVT2SECS(stats.start);
638
639    /*
640     * Session information:
641     *   - Name of render server
642     *   - Process ID
643     *   - Hostname where server is running
644     *   - Stop date of session
645     *   - Stop date of session in seconds
646     *   - Number of frames returned
647     *   - Number of bytes total returned (in frames)
648     *   - Number of commands received
649     *   - Total elapsed time of all commands
650     *   - Total elapsed time of session
651     *   - Exit code of vizserver
652     *   - User time
653     *   - System time
654     *   - User time of children
655     *   - System time of children
656     */
657
658    Tcl_DStringInit(&ds);
659
660    Tcl_DStringAppendElement(&ds, "render_stop");
661    /* renderer */
662    Tcl_DStringAppendElement(&ds, "renderer");
663    Tcl_DStringAppendElement(&ds, "pymol");
664    /* pid */
665    Tcl_DStringAppendElement(&ds, "pid");
666    sprintf(buf, "%d", getpid());
667    Tcl_DStringAppendElement(&ds, buf);
668    /* host */
669    Tcl_DStringAppendElement(&ds, "host");
670    gethostname(buf, BUFSIZ-1);
671    buf[BUFSIZ-1] = '\0';
672    Tcl_DStringAppendElement(&ds, buf);
673    /* date */
674    Tcl_DStringAppendElement(&ds, "date");
675    strcpy(buf, ctime(&now.tv_sec));
676    buf[strlen(buf) - 1] = '\0';
677    Tcl_DStringAppendElement(&ds, buf);
678    /* date_secs */
679    Tcl_DStringAppendElement(&ds, "date_secs");
680    sprintf(buf, "%ld", now.tv_sec);
681    Tcl_DStringAppendElement(&ds, buf);
682    /* num_frames */
683    Tcl_DStringAppendElement(&ds, "num_frames");
684    sprintf(buf, "%lu", (unsigned long int)stats.numFrames);
685    Tcl_DStringAppendElement(&ds, buf);
686    /* frame_bytes */
687    Tcl_DStringAppendElement(&ds, "frame_bytes");
688    sprintf(buf, "%lu", (unsigned long int)stats.numBytes);
689    Tcl_DStringAppendElement(&ds, buf);
690    /* num_commands */
691    Tcl_DStringAppendElement(&ds, "num_commands");
692    sprintf(buf, "%lu", (unsigned long int)stats.numCommands);
693    Tcl_DStringAppendElement(&ds, buf);
694    /* cmd_time */
695    Tcl_DStringAppendElement(&ds, "cmd_time");
696    sprintf(buf, "%g", stats.cmdTime);
697    Tcl_DStringAppendElement(&ds, buf);
698    /* session_time */
699    Tcl_DStringAppendElement(&ds, "session_time");
700    sprintf(buf, "%g", session_time);
701    Tcl_DStringAppendElement(&ds, buf);
702    /* status */
703    Tcl_DStringAppendElement(&ds, "status");
704    sprintf(buf, "%d", code);
705    Tcl_DStringAppendElement(&ds, buf);
706    {
707        long clocksPerSec = sysconf(_SC_CLK_TCK);
708        double clockRes = 1.0 / clocksPerSec;
709        struct tms tms;
710
711        memset(&tms, 0, sizeof(tms));
712        times(&tms);
713        /* utime */
714        Tcl_DStringAppendElement(&ds, "utime");
715        sprintf(buf, "%g", tms.tms_utime * clockRes);
716        Tcl_DStringAppendElement(&ds, buf);
717        /* stime */
718        Tcl_DStringAppendElement(&ds, "stime");
719        sprintf(buf, "%g", tms.tms_stime * clockRes);
720        Tcl_DStringAppendElement(&ds, buf);
721        /* cutime */
722        Tcl_DStringAppendElement(&ds, "cutime");
723        sprintf(buf, "%g", tms.tms_cutime * clockRes);
724        Tcl_DStringAppendElement(&ds, buf);
725        /* cstime */
726        Tcl_DStringAppendElement(&ds, "cstime");
727        sprintf(buf, "%g", tms.tms_cstime * clockRes);
728        Tcl_DStringAppendElement(&ds, buf);
729    }
730    Tcl_DStringAppend(&ds, "\n", -1);
731    f = GetStatsFile(NULL);
732    result = WriteToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
733    Tcl_DStringFree(&ds);
734    close(f);
735    return result;
736}
737
738static int
739CartoonCmd(ClientData clientData, Tcl_Interp *interp, int argc,
740           const char *argv[])
741{
742    PymolProxy *p = clientData;
743    int bool, defer, push, i;
744    const char *model;
745
746    clear_error(p);
747    defer = push = FALSE;
748    model = "all";
749    bool = FALSE;
750    for(i = 1; i < argc; i++) {
751        if (strcmp(argv[i],"-defer") == 0) {
752            defer = TRUE;
753        } else if (strcmp(argv[i],"-push") == 0) {
754            push = TRUE;
755        } else if (strcmp(argv[i],"-model") == 0) {
756            if (++i < argc) {
757                model = argv[i];
758            }
759        } else {
760            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
761                return TCL_ERROR;
762            }
763        }
764    }
765    p->flags |= INVALIDATE_CACHE; /* cartoon */
766    if (!defer || push) {
767        p->flags |= UPDATE_PENDING;
768    }
769    if (push) {
770        p->flags |= FORCE_UPDATE;
771    }
772    if (bool) {
773        SendToPymol(p, "show cartoon,%s\n", model);
774    } else {
775        SendToPymol(p, "hide cartoon,%s\n", model);
776    }
777    return p->status;
778}
779
780static int
781CartoonTraceCmd(ClientData clientData, Tcl_Interp *interp, int argc,
782                const char *argv[])
783{
784    PymolProxy *p = clientData;
785    int bool, defer, push, i;
786    const char *model;
787
788    clear_error(p);
789    defer = push = bool = FALSE;
790    model = "all";
791    for(i = 1; i < argc; i++) {
792        if (strcmp(argv[i],"-defer") == 0) {
793            defer = TRUE;
794        } else if (strcmp(argv[i],"-push") == 0) {
795            push = TRUE;
796        } else if (strcmp(argv[i],"-model") == 0) {
797            if (++i < argc) {
798                model = argv[i];
799            }
800        } else {
801            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
802                return TCL_ERROR;
803            }
804        }
805    }
806    p->flags |= INVALIDATE_CACHE; /* cartoon_trace  */
807    if (!defer || push) {
808        p->flags |= UPDATE_PENDING;
809    }
810    if (push) {
811        p->flags |= FORCE_UPDATE;
812    }
813    SendToPymol(p, "set cartoon_trace,%d,%s\n", bool, model);
814    return p->status;
815}
816
817static int
818DisableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
819           const char *argv[])
820{
821    PymolProxy *p = clientData;
822    const char *model = "all";
823    int i, defer, push;
824
825    clear_error(p);
826    defer = push = FALSE;
827    for(i = 1; i < argc; i++) {
828        if (strcmp(argv[i], "-defer") == 0 )
829            defer = 1;
830        else if (strcmp(argv[i], "-push") == 0 )
831            push = 1;
832        else
833            model = argv[i];
834    }
835
836    p->flags |= INVALIDATE_CACHE; /* disable */
837    if (!defer || push) {
838        p->flags |= UPDATE_PENDING;
839    }
840    if (push) {
841        p->flags |= FORCE_UPDATE;
842    }
843    SendToPymol(p, "disable %s\n", model);
844    return p->status;
845}
846
847static int
848EnableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
849          const char *argv[])
850{
851    PymolProxy *p = clientData;
852    const char *model;
853    int i, defer, push;
854
855    clear_error(p);
856    push = defer = FALSE;
857    model = "all";
858    for(i = 1; i < argc; i++) {
859        if (strcmp(argv[i],"-defer") == 0) {
860            defer = TRUE;
861        } else if (strcmp(argv[i], "-push") == 0) {
862            push = TRUE;
863        } else {
864            model = argv[i];
865        }
866    }
867    p->flags |= INVALIDATE_CACHE; /* enable */
868    if (!defer || push) {
869        p->flags |= UPDATE_PENDING;
870    }
871    if (push) {
872        p->flags |= FORCE_UPDATE;
873    }
874    SendToPymol(p, "enable %s\n", model);
875    return p->status;
876}
877
878static int
879FrameCmd(ClientData clientData, Tcl_Interp *interp, int argc,
880         const char *argv[])
881{
882    PymolProxy *p = clientData;
883    int i, push, defer, frame;
884
885    clear_error(p);
886    frame = 0;
887    push = defer = FALSE;
888    for(i = 1; i < argc; i++) {
889        if (strcmp(argv[i],"-defer") == 0) {
890            defer = TRUE;
891        } else if (strcmp(argv[i],"-push") == 0) {
892            push = TRUE;
893        } else {
894            frame = atoi(argv[i]);
895        }
896    }
897    if (!defer || push) {
898        p->flags |= UPDATE_PENDING;
899    }
900    if (push) {
901        p->flags |= FORCE_UPDATE;
902    }
903    p->frame = frame;
904
905    /* Does not invalidate cache? */
906
907    SendToPymol(p,"frame %d\n", frame);
908    return p->status;
909}
910
911/*
912 * ClientInfoCmd --
913 *
914 * clientinfo path list
915 */
916static int
917ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int argc,
918              const char *argv[])
919{
920    Tcl_DString ds;
921    int result;
922    int i, numElems;
923    const char **elems;
924    char buf[BUFSIZ];
925    static int first = 1;
926    int fd;
927
928    if (argc != 2) {
929        Tcl_AppendResult(interp, "wrong # of arguments: should be \"", argv[0],
930                " list\"", (char *)NULL);
931        return TCL_ERROR;
932    }
933    /* Use the initial client key value pairs as the parts for a generating
934     * a unique file name. */
935    fd = GetStatsFile(argv[1]);
936    if (fd < 0) {
937        Tcl_AppendResult(interp, "can't open stats file: ",
938                         Tcl_PosixError(interp), (char *)NULL);
939        return TCL_ERROR;
940    }
941    Tcl_DStringInit(&ds);
942    if (first) {
943        first = 0;
944        Tcl_DStringAppendElement(&ds, "render_start");
945        /* renderer */
946        Tcl_DStringAppendElement(&ds, "renderer");
947        Tcl_DStringAppendElement(&ds, "pymol");
948        /* pid */
949        Tcl_DStringAppendElement(&ds, "pid");
950        sprintf(buf, "%d", getpid());
951        Tcl_DStringAppendElement(&ds, buf);
952        /* host */
953        Tcl_DStringAppendElement(&ds, "host");
954        gethostname(buf, BUFSIZ-1);
955        buf[BUFSIZ-1] = '\0';
956        Tcl_DStringAppendElement(&ds, buf);
957        /* date */
958        Tcl_DStringAppendElement(&ds, "date");
959        strcpy(buf, ctime(&stats.start.tv_sec));
960        buf[strlen(buf) - 1] = '\0';
961        Tcl_DStringAppendElement(&ds, buf);
962        /* date_secs */
963        Tcl_DStringAppendElement(&ds, "date_secs");
964        sprintf(buf, "%ld", stats.start.tv_sec);
965        Tcl_DStringAppendElement(&ds, buf);
966    } else {
967        struct timeval now;
968        Tcl_DStringAppendElement(&ds, "render_info");
969        gettimeofday(&now, NULL);
970        /* date */
971        Tcl_DStringAppendElement(&ds, "date");
972        strcpy(buf, ctime(&now.tv_sec));
973        buf[strlen(buf) - 1] = '\0';
974        Tcl_DStringAppendElement(&ds, buf);
975        /* date_secs */
976        Tcl_DStringAppendElement(&ds, "date_secs");
977        sprintf(buf, "%ld", now.tv_sec);
978        Tcl_DStringAppendElement(&ds, buf);
979    }
980    /* Client arguments. */
981    if (Tcl_SplitList(interp, argv[1], &numElems, &elems) != TCL_OK) {
982        return TCL_ERROR;
983    }
984    for (i = 0; i < numElems; i++) {
985        Tcl_DStringAppendElement(&ds, elems[i]);
986    }
987    free(elems);
988    Tcl_DStringAppend(&ds, "\n", 1);
989    result = WriteToStatsFile(fd, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
990    Tcl_DStringFree(&ds);
991    return result;
992}
993
994static int
995LabelCmd(ClientData clientData, Tcl_Interp *interp, int argc,
996         const char *argv[])
997{
998    PymolProxy *p = clientData;
999    int i, push, defer, bool, size;
1000    const char *model;
1001
1002    clear_error(p);
1003    model = "all";
1004    size = 14;
1005    bool = TRUE;
1006    push = defer = FALSE;
1007    for(i = 1; i < argc; i++) {
1008        if (strcmp(argv[i],"-defer") == 0) {
1009            defer = TRUE;
1010        } else if (strcmp(argv[i],"-push") == 0) {
1011            push = TRUE;
1012        } else if (strcmp(argv[i],"-model") == 0) {
1013            if (++i < argc) {
1014                model = argv[i];
1015            }
1016        } else if (strcmp(argv[i],"-size") == 0) {
1017            if (++i < argc) {
1018                size = atoi(argv[i]);
1019            }
1020        } else if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
1021            return TCL_ERROR;
1022        }
1023    }
1024    p->flags |= INVALIDATE_CACHE; /* label */
1025    if (!defer || push) {
1026        p->flags |= UPDATE_PENDING;
1027    }
1028    if (push) {
1029        p->flags |= FORCE_UPDATE;
1030    }
1031    SendToPymol(p, "set label_color,white,%s\nset label_size,%d,%s\n",
1032            model, size, model);
1033    if (bool) {
1034        SendToPymol(p, "label %s,\"%%s%%s\" %% (ID,name)\n", model);
1035    } else {
1036        SendToPymol(p, "label %s\n", model);
1037    }
1038    return p->status;
1039}
1040
1041/*
1042 * LoadPDBCmd --
1043 *
1044 * Load a PDB into pymol.  We write the pdb data into a temporary file
1045 * and then let pymol read it and delete it.  There is no good way to
1046 * load PDB data into pymol without using a file.  The specially created
1047 * routine "loadandremovepdbfile" in pymol will remove the file after
1048 * loading it.
1049 */
1050static int
1051LoadPDBCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1052           const char *argv[])
1053{
1054    PymolProxy *p = clientData;
1055    const char *string;
1056    const char *name;
1057    unsigned char *allocated;
1058    int state, defer, push;
1059    size_t numBytes;
1060    int i, j;
1061    int status;
1062
1063    if (p == NULL){
1064        return TCL_ERROR;
1065    }
1066    clear_error(p);
1067    defer = push = FALSE;
1068    for(i = j = 1; i < argc; i++) {
1069        if (strcmp(argv[i],"-defer") == 0) {
1070            defer = TRUE;
1071        } else if (strcmp(argv[i],"-push") == 0) {
1072            push = TRUE;
1073        } else {
1074            if (j < i) {
1075                argv[j] = argv[i];
1076            }
1077            j++;
1078        }
1079    }
1080    argc = j;
1081    if (argc < 4) {
1082        Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1083                         " <data>|follows <model> <state> ?<numBytes>?\"",
1084                         (char *)NULL);
1085        return TCL_ERROR;
1086    }
1087    string = argv[1];
1088    name   = argv[2];
1089    if (Tcl_GetInt(interp, argv[3], &state) != TCL_OK) {
1090        return TCL_ERROR;
1091    }
1092    numBytes = 0;
1093    status = BUFFER_ERROR;
1094    if (strcmp(string, "follows") == 0) {
1095        int n;
1096
1097        if (argc != 5) {
1098            Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1099                         " follows <model> <state> <numBytes>\"", (char *)NULL);
1100            return TCL_ERROR;
1101        }
1102        if (Tcl_GetInt(interp, argv[4], &n) != TCL_OK) {
1103            return TCL_ERROR;
1104        }
1105        if (n < 0) {
1106            Tcl_AppendResult(interp, "bad value for # bytes \"", argv[4],
1107                         "\"", (char *)NULL);
1108            return TCL_ERROR;
1109        }
1110        numBytes = (size_t)n;
1111    }
1112    if (!defer || push) {
1113        p->flags |= UPDATE_PENDING;
1114    }
1115    if (push) {
1116        p->flags |= FORCE_UPDATE;
1117    }
1118    p->cacheId = state;
1119
1120    /* Does not invalidate cache? */
1121
1122    allocated = NULL;
1123    allocated = malloc(sizeof(char) * numBytes);
1124    if (allocated == NULL) {
1125        Tcl_AppendResult(interp, "can't allocate buffer for pdbdata.",
1126                         (char *)NULL);
1127        return TCL_ERROR;
1128    }
1129    status = ReadFollowingData(&p->client, allocated, numBytes);
1130    if (status != BUFFER_OK) {
1131        Tcl_AppendResult(interp, "can't read pdbdata from client.",
1132                         (char *)NULL);
1133        free(allocated);
1134        return TCL_ERROR;
1135    }
1136    string = (const char *)allocated;
1137    {
1138        int f;
1139        ssize_t numWritten;
1140        char fileName[200];
1141
1142        strcpy(fileName, "/tmp/pdb.XXXXXX");
1143        p->status = TCL_ERROR;
1144        f = mkstemp(fileName);
1145        if (f < 0) {
1146            Tcl_AppendResult(interp, "can't create temporary file \"",
1147                fileName, "\":", Tcl_PosixError(interp), (char *)NULL);
1148            goto error;
1149        }
1150        numWritten = write(f, string, numBytes);
1151        if (numBytes != numWritten) {
1152            Tcl_AppendResult(interp, "can't write PDB data to \"",
1153                fileName, "\": ", Tcl_PosixError(interp), (char *)NULL);
1154            close(f);
1155            goto error;
1156        }
1157        close(f);
1158        SendToPymol(p, "loadandremovepdbfile %s,%s,%d\n", fileName, name,
1159                    state);
1160        p->status = TCL_OK;
1161    }
1162 error:
1163    if (allocated != NULL) {
1164        free(allocated);
1165    }
1166    return p->status;
1167}
1168
1169static int
1170OrthoscopicCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1171              const char *argv[])
1172{
1173    PymolProxy *p = clientData;
1174    int bool, defer, push, i;
1175
1176    clear_error(p);
1177    defer = push = FALSE;
1178    bool = FALSE;
1179    for(i = 1; i < argc; i++) {
1180        if (strcmp(argv[i],"-defer") == 0) {
1181            defer = TRUE;
1182        } else if (strcmp(argv[i],"-push") == 0) {
1183            push = TRUE;
1184        } else {
1185            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
1186                return TCL_ERROR;
1187            }
1188        }
1189    }
1190    p->flags |= INVALIDATE_CACHE; /* orthoscopic */
1191    if (!defer || push) {
1192        p->flags |= UPDATE_PENDING;
1193    }
1194    if (push) {
1195        p->flags |= FORCE_UPDATE;
1196    }
1197    SendToPymol(p, "set orthoscopic=%d\n", bool);
1198    return p->status;
1199}
1200
1201/*
1202 * PanCmd --
1203 *
1204 * Issue "move" commands for changes in the x and y coordinates of the
1205 * camera.  The problem here is that there is no event compression.
1206 * Consecutive "pan" commands are not compressed into a single
1207 * directive.  The means that the pymol server will render scenes that
1208 * are not used by the client.
1209 *
1210 * Need to 1) defer the "move" commands until we find the next command
1211 * isn't a "pan". 2) Track the x and y coordinates as they are
1212 * compressed.
1213 */
1214static int
1215PanCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1216{
1217    PymolProxy *p = clientData;
1218    double x, y;
1219    int i;
1220    int defer, push;
1221
1222    clear_error(p);
1223    defer = push = FALSE;
1224    for (i = 1; i < argc; i++) {
1225        if (strcmp(argv[i],"-defer") == 0) {
1226            defer = 1;
1227        } else if (strcmp(argv[i],"-push") == 0) {
1228            push = 1;
1229        } else {
1230            break;
1231        }
1232    }
1233    if ((Tcl_GetDouble(interp, argv[i], &x) != TCL_OK) ||
1234        (Tcl_GetDouble(interp, argv[i+1], &y) != TCL_OK)) {
1235        return TCL_ERROR;
1236    }
1237    p->flags |= INVALIDATE_CACHE; /* pan */
1238    if (!defer || push) {
1239        p->flags |= UPDATE_PENDING;
1240    }
1241    if (push) {
1242        p->flags |= FORCE_UPDATE;
1243    }
1244    if ((x != 0.0f) || (y != 0.0f)) {
1245        p->xPan = x * 0.05;
1246        p->yPan = -y * 0.05;
1247        p->flags |= PAN_PENDING;
1248    }
1249    return p->status;
1250}
1251
1252static int
1253PngCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1254{
1255    PymolProxy *p = clientData;
1256
1257    clear_error(p);
1258
1259    /* Force pymol to update the current scene. */
1260    SendToPymol(p, "refresh\n");
1261    /* This is a hack. We're encoding the filename to pass extra information
1262     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1263     * parameters of a new "molvispng" command that would be passed all the
1264     * way through to MyPNGWrite.
1265     *
1266     * The extra information is contained in the token we get from the
1267     * molvisviewer client, the frame number, and rock offset. */
1268    SendToPymol(p, "png -:%d:%d:%d\n", p->cacheId, p->frame, p->rockOffset);
1269    return p->status;
1270}
1271
1272static int
1273PpmCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1274{
1275    PymolProxy *p = clientData;
1276
1277    clear_error(p);
1278
1279    /* Force pymol to update the current scene. */
1280    SendToPymol(p, "refresh\n");
1281    /* This is a hack. We're encoding the filename to pass extra information
1282     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1283     * parameters of a new "molvispng" command that would be passed all the
1284     * way through to MyPNGWrite.
1285     *
1286     * The extra information is contained in the token we get from the
1287     * molvisviewer client, the frame number, and rock offset. */
1288    SendToPymol(p, "png -:%d:%d:%d,format=1\n", p->cacheId, p->frame,
1289            p->rockOffset);
1290    p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
1291    return p->status;
1292}
1293
1294static int
1295PrintCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1296         const char *argv[])
1297{
1298    PymolProxy *p = clientData;
1299    int width, height;
1300    const char *token, *bgcolor;
1301
1302    clear_error(p);
1303
1304    if (argc != 5) {
1305        Tcl_AppendResult(interp, "wrong # arguments: should be \"",
1306                         argv[0], " token width height color\"", (char *)NULL);
1307        return TCL_ERROR;
1308    }
1309    token = argv[1];
1310    if (Tcl_GetInt(interp, argv[2], &width) != TCL_OK) {
1311        return TCL_ERROR;
1312    }
1313    if (Tcl_GetInt(interp, argv[3], &height) != TCL_OK) {
1314        return TCL_ERROR;
1315    }
1316    bgcolor = argv[4];
1317    /* Force pymol to update the current scene. */
1318    if (strcmp(bgcolor, "none") == 0) {
1319        SendToPymol(p, "set ray_opaque_background,off\n");
1320        SendToPymol(p, "refresh\n", bgcolor);
1321    } else {
1322        SendToPymol(p, "set ray_opaque_background,on\n");
1323        SendToPymol(p, "bg_color %s\nrefresh\n", bgcolor);
1324    }
1325    /* This is a hack. We're encoding the filename to pass extra information
1326     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1327     * parameters of a new "molvispng" command that would be passed all the
1328     * way through to MyPNGWrite.
1329     *
1330     * The extra information is contained in the token we get from the
1331     * molvisviewer client, the frame number, and rock offset.
1332     */
1333    SendToPymol(p, "png -:%s:0:0,width=%d,height=%d,ray=1,dpi=300\n",
1334            token, width, height);
1335    SendToPymol(p, "bg_color black\n");
1336    return p->status;
1337}
1338
1339static int
1340RawCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1341{
1342    PymolProxy *p = clientData;
1343    int arg, defer = 0, push = 0;
1344    const char *cmd;
1345    clear_error(p);
1346
1347    cmd = NULL;
1348    defer = push = FALSE;
1349    for(arg = 1; arg < argc; arg++) {
1350        if (strcmp(argv[arg], "-defer") == 0)
1351            defer = 1;
1352        else if (strcmp(argv[arg], "-push") == 0)
1353            push = 1;
1354        else {
1355            cmd = argv[arg];
1356        }
1357    }
1358
1359    p->flags |= INVALIDATE_CACHE; /* raw */
1360    if (!defer || push) {
1361        p->flags |= UPDATE_PENDING;
1362    }
1363    if (push) {
1364        p->flags |= FORCE_UPDATE;
1365    }
1366    SendToPymol(p,"%s\n", cmd);
1367    return p->status;
1368}
1369
1370static int
1371ResetCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1372         const char *argv[])
1373{
1374    PymolProxy *p = clientData;
1375    int arg, push = 0, defer = 0;
1376
1377    clear_error(p);
1378    defer = push = 0;
1379    for(arg = 1; arg < argc; arg++) {
1380        if ( strcmp(argv[arg],"-defer") == 0 )
1381            defer = 1;
1382        else if (strcmp(argv[arg],"-push") == 0 )
1383            push = 1;
1384    }
1385
1386    p->flags |= INVALIDATE_CACHE; /* reset */
1387    if (!defer || push) {
1388        p->flags |= UPDATE_PENDING;
1389    }
1390    if (push) {
1391        p->flags |= FORCE_UPDATE;
1392    }
1393    SendToPymol(p, "reset\nzoom complete=1\n");
1394    return p->status;
1395}
1396
1397static int
1398RockCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1399        const char *argv[])
1400{
1401    PymolProxy *p = clientData;
1402    float y = 0.0;
1403    int arg, push, defer;
1404
1405    clear_error(p);
1406
1407    defer = push = FALSE;
1408    for(arg = 1; arg < argc; arg++) {
1409        if ( strcmp(argv[arg],"-defer") == 0 )
1410            defer = 1;
1411        else if (strcmp(argv[arg],"-push") == 0 )
1412            push = 1;
1413        else
1414            y = atof( argv[arg] );
1415    }
1416
1417    /* Does not invalidate cache. */
1418
1419    if (!defer || push) {
1420        p->flags |= UPDATE_PENDING;
1421    }
1422    if (push) {
1423        p->flags |= FORCE_UPDATE;
1424    }
1425    SendToPymol(p,"turn y, %f\n", y - p->rockOffset);
1426    p->rockOffset = y;
1427    return p->status;
1428}
1429
1430static int
1431RepresentationCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1432                  const char *argv[])
1433{
1434    PymolProxy *p = clientData;
1435    const char *model;
1436    const char *rep;
1437    int defer, push, i;
1438
1439    clear_error(p);
1440    defer = push = FALSE;
1441    model = "all";
1442    rep = NULL;
1443    for (i = 1; i < argc; i++) {
1444        if (strcmp(argv[i],"-defer") == 0 ) {
1445            defer = TRUE;
1446        } else if (strcmp(argv[i],"-push") == 0) {
1447            push = TRUE;
1448        } else if (strcmp(argv[i],"-model") == 0) {
1449            if (++i < argc) {
1450                model = argv[i];
1451            }
1452        } else {
1453            rep = argv[i];
1454        }
1455    }
1456    if (rep == NULL) {
1457        Tcl_AppendResult(interp, "missing representation argument",
1458                         (char *)NULL);
1459        return TCL_ERROR;
1460    }
1461
1462    p->flags |= INVALIDATE_CACHE; /* representation */
1463    if (!defer || push) {
1464        p->flags |= UPDATE_PENDING;
1465    }
1466    if (push) {
1467        p->flags |= FORCE_UPDATE;
1468    }
1469    if (strcmp(rep, "ballnstick") == 0) { /* Ball 'n Stick */
1470        SendToPymol(p,
1471              "set stick_color,white,%s\n"
1472              "show sticks,%s\n"
1473              "show spheres,%s\n"
1474              "hide lines,%s\n"
1475              "hide cartoon,%s\n",
1476              model, model, model, model, model);
1477    } else if (strcmp(rep, "spheres") == 0) { /* spheres */
1478        SendToPymol(p,
1479              "hide sticks,%s\n"
1480              "show spheres,%s\n"
1481              "hide lines,%s\n"
1482              "hide cartoon,%s\n"
1483              "set sphere_quality,2,%s\n"
1484              "set ambient,.2,%s\n",
1485              model, model, model, model, model, model);
1486    } else if (strcmp(rep, "none") == 0) { /* nothing */
1487        SendToPymol(p,
1488              "hide sticks,%s\n",
1489              "hide spheres,%s\n"
1490              "hide lines,%s\n"
1491              "hide cartoon,%s\n",
1492              model, model, model, model);
1493    } else if (strcmp(rep, "sticks") == 0) { /* sticks */
1494        SendToPymol(p,
1495              "set stick_color,white,%s\n"
1496              "show sticks,%s\n"
1497              "hide spheres,%s\n"
1498              "hide lines,%s\n"
1499              "hide cartoon,%s\n",
1500              model, model, model, model, model);
1501    } else if (strcmp(rep, "lines") == 0) { /* lines */
1502        SendToPymol(p,
1503              "hide sticks,%s\n"
1504              "hide spheres,%s\n"
1505              "show lines,%s\n"
1506              "hide cartoon,%s\n",
1507              model, model, model, model);
1508    } else if (strcmp(rep, "cartoon") == 0) { /* cartoon */
1509        SendToPymol(p,
1510              "hide sticks,%s\n"
1511              "hide spheres,%s\n"
1512              "hide lines,%s\n"
1513              "show cartoon,%s\n",
1514              model, model, model, model);
1515    }
1516    return p->status;
1517}
1518
1519/*
1520 * RotateCmd --
1521 *
1522 * Issue "turn" commands for changes in the angle of the camera.  The
1523 * problem here is that there is no event compression.  Consecutive
1524 * "rotate" commands are not compressed into a single directive.  The
1525 * means that the pymol server will render many scene that are not used
1526 * by the client.
1527 *
1528 * Need to 1) defer the "turn" commands until we find the next command
1529 * isn't a "rotate". 2) Track the rotation angles as they are compressed.
1530 */
1531static int
1532RotateCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1533          const char *argv[])
1534{
1535    PymolProxy *p = clientData;
1536    float xAngle, yAngle, zAngle;
1537    int defer, push, arg, varg = 1;
1538
1539    clear_error(p);
1540    defer = push = 0;
1541    xAngle = yAngle = zAngle = 0.0f;
1542    for(arg = 1; arg < argc; arg++) {
1543        if (strcmp(argv[arg],"-defer") == 0) {
1544            defer = 1;
1545        } else if (strcmp(argv[arg],"-push") == 0) {
1546            push = 1;
1547        } else  if (varg == 1) {
1548            double value;
1549            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1550                return TCL_ERROR;
1551            }
1552            xAngle = (float)value;
1553            varg++;
1554        } else if (varg == 2) {
1555            double value;
1556            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1557                return TCL_ERROR;
1558            }
1559            yAngle = (float)value;
1560            varg++;
1561        } else if (varg == 3) {
1562            double value;
1563            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1564                return TCL_ERROR;
1565            }
1566            zAngle = (float)value;
1567            varg++;
1568        }
1569    }
1570    p->flags |= INVALIDATE_CACHE; /* rotate */
1571    if (!defer || push) {
1572        p->flags |= UPDATE_PENDING;
1573    }
1574    if (push) {
1575        p->flags |= FORCE_UPDATE;
1576    }
1577    if ((xAngle != 0.0f) || (yAngle != 0.0f) || (zAngle != 0.0f)) {
1578        p->xAngle += xAngle;
1579        p->yAngle += yAngle;
1580        p->zAngle += zAngle;
1581        p->flags |= ROTATE_PENDING;
1582    }
1583    return p->status;
1584}
1585
1586static int
1587ScreenCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1588          const char *argv[])
1589{
1590    PymolProxy *p = clientData;
1591    int width = -1, height = -1;
1592    int defer, push, i, varg;
1593
1594    clear_error(p);
1595    defer = push = FALSE;
1596    varg = 1;
1597    for(i = 1; i < argc; i++) {
1598        if ( strcmp(argv[i],"-defer") == 0 )
1599            defer = 1;
1600        else if ( strcmp(argv[i], "-push") == 0 )
1601            push = 1;
1602        else if (varg == 1) {
1603            width = atoi(argv[i]);
1604            height = width;
1605            varg++;
1606        }
1607        else if (varg == 2) {
1608            height = atoi(argv[i]);
1609            varg++;
1610        }
1611    }
1612    if ((width < 0) || (height < 0)) {
1613        return TCL_ERROR;
1614    }
1615    p->flags |= INVALIDATE_CACHE; /* viewport */
1616    if (!defer || push) {
1617        p->flags |= UPDATE_PENDING;
1618    }
1619    if (push) {
1620        p->flags |= FORCE_UPDATE;
1621    }
1622    p->width = width;
1623    p->height = height;
1624    p->flags |= VIEWPORT_PENDING;
1625    return p->status;
1626}
1627
1628static int
1629SphereScaleCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1630           const char *argv[])
1631{
1632    int defer = 0, push = 0, i;
1633    double scale;
1634    const char *model = "all";
1635    PymolProxy *p = clientData;
1636
1637    clear_error(p);
1638    scale = 0.25f;
1639    for(i = 1; i < argc; i++) {
1640        if ( strcmp(argv[i],"-defer") == 0 ) {
1641            defer = 1;
1642        } else if (strcmp(argv[i],"-push") == 0) {
1643            push = 1;
1644        } else if (strcmp(argv[i],"-model") == 0) {
1645            if (++i < argc) {
1646                model = argv[i];
1647            }
1648        } else {
1649            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1650                return TCL_ERROR;
1651            }
1652        }
1653    }
1654    p->flags |= INVALIDATE_CACHE;  /* sphere_scale */
1655    if (!defer || push) {
1656        p->flags |= UPDATE_PENDING;
1657    }
1658    if (push) {
1659        p->flags |= FORCE_UPDATE;
1660    }
1661    if (strcmp(model, "all") == 0) {
1662        p->flags |= ATOM_SCALE_PENDING;
1663        p->sphereScale = scale;
1664    } else {
1665        SendToPymol(p, "set sphere_scale,%f,%s\n", scale, model);
1666    }
1667    return p->status;
1668}
1669
1670static int
1671StickRadiusCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1672               const char *argv[])
1673{
1674    PymolProxy *p = clientData;
1675    int defer = 0, push = 0, i;
1676    double scale;
1677    const char *model = "all";
1678
1679    clear_error(p);
1680    scale = 0.25f;
1681    for(i = 1; i < argc; i++) {
1682        if (strcmp(argv[i],"-defer") == 0 ) {
1683            defer = 1;
1684        } else if (strcmp(argv[i],"-push") == 0) {
1685            push = 1;
1686        } else if (strcmp(argv[i],"-model") == 0) {
1687            if (++i < argc)
1688                model = argv[i];
1689        } else {
1690            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1691                return TCL_ERROR;
1692            }
1693        }
1694    }
1695    p->flags |= INVALIDATE_CACHE;  /* stick_radius */
1696    if (!defer || push) {
1697        p->flags |= UPDATE_PENDING;
1698    }
1699    if (push) {
1700        p->flags |= FORCE_UPDATE;
1701    }
1702
1703    if (strcmp(model, "all") == 0) {
1704        p->flags |= STICK_RADIUS_PENDING;
1705        p->stickRadius = scale;
1706    } else {
1707        SendToPymol(p, "set stick_radius,%f,%s\n", scale, model);
1708    }
1709    return p->status;
1710}
1711
1712static int
1713TransparencyCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1714                const char *argv[])
1715{
1716    PymolProxy *p = clientData;
1717    const char *model;
1718    float transparency;
1719    int defer, push;
1720    int i;
1721
1722    clear_error(p);
1723    model = "all";
1724    defer = push = FALSE;
1725    transparency = 0.0f;
1726    for(i = 1; i < argc; i++) {
1727        if ( strcmp(argv[i],"-defer") == 0 ) {
1728            defer = 1;
1729        } else if (strcmp(argv[i],"-push") == 0) {
1730            push = 1;
1731        } else if (strcmp(argv[i],"-model") == 0) {
1732            if (++i < argc) {
1733                model = argv[i];
1734            }
1735        } else {
1736            transparency = atof(argv[i]);
1737        }
1738    }
1739    p->flags |= INVALIDATE_CACHE; /* transparency */
1740    if (!defer || push) {
1741        p->flags |= UPDATE_PENDING;
1742    }
1743    if (push) {
1744        p->flags |= FORCE_UPDATE;
1745    }
1746    SendToPymol(p,
1747          "set sphere_transparency,%g,%s\n"
1748          "set stick_transparency,%g,%s\n"
1749          "set cartoon_transparency,%g,%s\n",
1750          transparency, model, transparency, model,
1751          transparency, model);
1752    return p->status;
1753}
1754
1755static int
1756VMouseCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1757          const char *argv[])
1758{
1759    PymolProxy *p = clientData;
1760    int i, defer = 0, push = 0, varg = 1;
1761    int arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0, arg5 = 0;
1762
1763    clear_error(p);
1764
1765    for(i = 1; i < argc; i++) {
1766        if (strcmp(argv[i], "-defer") == 0)
1767            defer = 1;
1768        else if (strcmp(argv[i], "-push") == 0)
1769            push = 1;
1770        else if (varg == 1) {
1771            arg1 = atoi(argv[i]);
1772            varg++;
1773        } else if (varg == 2) {
1774            arg2 = atoi(argv[i]);
1775            varg++;
1776        } else if (varg == 3) {
1777            arg3 = atoi(argv[i]);
1778            varg++;
1779        } else if (varg == 4) {
1780            arg4 = atoi(argv[i]);
1781            varg++;
1782        } else if (varg == 5) {
1783            arg5 = atoi(argv[i]);
1784            varg++;
1785        }
1786    }
1787
1788    p->flags |= INVALIDATE_CACHE; /* vmouse */
1789    if (!defer || push) {
1790        p->flags |= UPDATE_PENDING;
1791    }
1792    if (push) {
1793        p->flags |= FORCE_UPDATE;
1794    }
1795    SendToPymol(p, "vmouse %d,%d,%d,%d,%d\n", arg1, arg2, arg3, arg4, arg5);
1796    return p->status;
1797}
1798
1799/*
1800 * ZoomCmd --
1801 *
1802 * Issue "move" commands for changes in the z-coordinate of the camera.
1803 * The problem here is that there is no event compression.  Consecutive
1804 * "zoom" commands are not compressed into a single directive.  The means
1805 * that the pymol server will render scenes that are not used by the
1806 * client.
1807 *
1808 * Need to 1) defer the "move" commands until we find the next command
1809 * isn't a "zoom". 2) Track the z-coordinate as they are compressed.
1810 */
1811static int
1812ZoomCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1813{
1814    PymolProxy *p = clientData;
1815    double factor = 0.0;
1816    int defer = 0, push = 0, i, varg = 1;
1817
1818    clear_error(p);
1819
1820    for(i = 1; i < argc; i++) {
1821        if (strcmp(argv[i],"-defer") == 0)
1822            defer = 1;
1823        else if (strcmp(argv[i],"-push") == 0)
1824            push = 1;
1825        else if (varg == 1) {
1826            double value;
1827            if (Tcl_GetDouble(interp, argv[i], &value) != TCL_OK) {
1828                return TCL_ERROR;
1829            }
1830            factor = (float)value;
1831            varg++;
1832        }
1833    }
1834    p->flags |= INVALIDATE_CACHE; /* Zoom */
1835    if (!defer || push) {
1836        p->flags |= UPDATE_PENDING;
1837    }
1838    if (push) {
1839        p->flags |= FORCE_UPDATE;
1840    }
1841    if (factor != 0.0) {
1842        p->zoom = factor;
1843        p->flags |= ZOOM_PENDING;
1844    }
1845    return p->status;
1846}
1847
1848static int
1849ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
1850{
1851    struct timeval tv;
1852    double start, finish;
1853    int result;
1854
1855    gettimeofday(&tv, NULL);
1856    start = CVT2SECS(tv);
1857
1858#if EXEC_DEBUG
1859    DEBUG("command from client is (%s)", Tcl_DStringValue(dsPtr));
1860#endif
1861    result = Tcl_Eval(interp, Tcl_DStringValue(dsPtr));
1862    if (result != TCL_OK) {
1863#if EXEC_DEBUG
1864        DEBUG("result was %s", Tcl_GetString(Tcl_GetObjResult(interp)));
1865#endif
1866    }
1867    gettimeofday(&tv, NULL);
1868    finish = CVT2SECS(tv);
1869
1870    stats.cmdTime += finish - start;
1871    stats.numCommands++;
1872    Tcl_DStringSetLength(dsPtr, 0);
1873    return result;
1874}
1875
1876static void
1877SetViewport(PymolProxy *p)
1878{
1879    if (p->flags & VIEWPORT_PENDING) {
1880        SendToPymol(p, "viewport %d,%d\n", p->width, p->height);
1881        SendToPymol(p, "refresh\n");
1882        p->flags &= ~VIEWPORT_PENDING;
1883    }
1884}
1885
1886static void
1887SetZoom(PymolProxy *p)
1888{
1889    if (p->flags & ZOOM_PENDING) {
1890        SendToPymol(p, "move z,%f\n", p->zoom);
1891        p->flags &= ~ZOOM_PENDING;
1892    }
1893}
1894
1895static void
1896SetPan(PymolProxy *p)
1897{
1898    if (p->flags & PAN_PENDING) {
1899        SendToPymol(p, "move x,%f\nmove y,%f\n", p->xPan, p->yPan);
1900        p->flags &= ~PAN_PENDING;
1901    }
1902}
1903
1904static void
1905SetRotation(PymolProxy *p)
1906{
1907    if (p->flags & ROTATE_PENDING) {
1908        /* Every pymol command line generates a new rendering. Execute all
1909         * three turns as a single command line. */
1910        SendToPymol(p,"turn x,%f\nturn y,%f\nturn z,%f\n", p->xAngle, p->yAngle,
1911                p->zAngle);
1912        p->xAngle = p->yAngle = p->zAngle = 0.0f;
1913        p->flags &= ~ROTATE_PENDING;
1914    }
1915}
1916
1917static void
1918SetSphereScale(PymolProxy *p)
1919{
1920    if (p->flags & ATOM_SCALE_PENDING) {
1921        SendToPymol(p, "set sphere_scale,%f,all\n", p->sphereScale);
1922        p->flags &= ~ATOM_SCALE_PENDING;
1923    }
1924}
1925
1926static void
1927SetStickRadius(PymolProxy *p)
1928{
1929    if (p->flags & STICK_RADIUS_PENDING) {
1930        SendToPymol(p, "set stick_radius,%f,all\n", p->stickRadius);
1931        p->flags &= ~STICK_RADIUS_PENDING;
1932    }
1933}
1934
1935static void
1936UpdateSettings(PymolProxy *p)
1937{
1938    /* Handle all the pending setting changes now. */
1939    if (p->flags & VIEWPORT_PENDING) {
1940        SetViewport(p);
1941    }
1942    if (p->flags & ROTATE_PENDING) {
1943        SetRotation(p);
1944    }
1945    if (p->flags & PAN_PENDING) {
1946        SetPan(p);
1947    }
1948    if (p->flags & ZOOM_PENDING) {
1949        SetZoom(p);
1950    }
1951    if (p->flags & ATOM_SCALE_PENDING) {
1952        SetSphereScale(p);
1953    }
1954    if (p->flags & STICK_RADIUS_PENDING) {
1955        SetStickRadius(p);
1956    }
1957}
1958
1959static Image *
1960NewImage(ImageList *listPtr, size_t dataLength)
1961{
1962    Image *imgPtr;
1963    static int id = 0;
1964
1965    imgPtr = malloc(sizeof(Image) + dataLength);
1966    if (imgPtr == NULL) {
1967        ERROR("can't allocate image of %lu bytes",
1968              (unsigned long)(sizeof(Image) + dataLength));
1969        abort();
1970    }
1971    imgPtr->prevPtr = imgPtr->nextPtr = NULL;
1972    imgPtr->bytesLeft = dataLength;
1973    imgPtr->id = id++;
1974#if WRITE_DEBUG
1975    DEBUG("NewImage: allocating image %d of %d bytes", imgPtr->id, dataLength);
1976#endif
1977    if (listPtr->headPtr != NULL) {
1978        listPtr->headPtr->prevPtr = imgPtr;
1979    }
1980    imgPtr->nextPtr = listPtr->headPtr;
1981    if (listPtr->tailPtr == NULL) {
1982        listPtr->tailPtr = imgPtr;
1983    }
1984    listPtr->headPtr = imgPtr;
1985    imgPtr->numWritten = 0;
1986    return imgPtr;
1987}
1988
1989INLINE static void
1990FreeImage(Image *imgPtr)
1991{
1992    assert(imgPtr != NULL);
1993    free(imgPtr);
1994}
1995
1996static void
1997WriteImages(ImageList *listPtr, int fd)
1998{
1999    Image *imgPtr, *prevPtr;
2000
2001    if (listPtr->tailPtr == NULL) {
2002        ERROR("Should not be here: no image available to write");
2003        return;
2004    }
2005#if WRITE_DEBUG
2006        DEBUG("Entering WriteImages");
2007#endif
2008    for (imgPtr = listPtr->tailPtr; imgPtr != NULL; imgPtr = prevPtr) {
2009        ssize_t bytesLeft;
2010
2011        assert(imgPtr->nextPtr == NULL);
2012        prevPtr = imgPtr->prevPtr;
2013#if WRITE_DEBUG
2014        DEBUG("WriteImages: image %d of %d bytes.", imgPtr->id,
2015              imgPtr->bytesLeft);
2016#endif
2017        for (bytesLeft = imgPtr->bytesLeft; bytesLeft > 0; /*empty*/) {
2018            ssize_t numWritten;
2019#if WRITE_DEBUG
2020            DEBUG("image %d: bytesLeft=%d", imgPtr->id, bytesLeft);
2021#endif
2022            numWritten = write(fd, imgPtr->data + imgPtr->numWritten, bytesLeft);
2023#if WRITE_DEBUG
2024            DEBUG("image %d: wrote %d bytes.", imgPtr->id, numWritten);
2025#endif
2026            if (numWritten < 0) {
2027                ERROR("Error writing fd=%d, %s", fd, strerror(errno));
2028#if WRITE_DEBUG
2029                DEBUG("Abnormal exit WriteImages");
2030#endif
2031                return;
2032            }
2033            bytesLeft -= numWritten;
2034            if (bytesLeft > 0) {
2035#if WRITE_DEBUG
2036                DEBUG("image %d, wrote a short buffer, %d bytes left.",
2037                      imgPtr->id, bytesLeft);
2038#endif
2039                /* Wrote a short buffer, means we would block. */
2040                imgPtr->numWritten += numWritten;
2041                imgPtr->bytesLeft = bytesLeft;
2042#if WRITE_DEBUG
2043                DEBUG("Abnormal exit WriteImages");
2044#endif
2045                return;
2046            }
2047            imgPtr->numWritten += numWritten;
2048        }
2049        /* Check if image is on the head.  */
2050        listPtr->tailPtr = prevPtr;
2051        if (prevPtr != NULL) {
2052            prevPtr->nextPtr = NULL;
2053        }
2054        FreeImage(imgPtr);
2055    }
2056    listPtr->headPtr = NULL;
2057#if WRITE_DEBUG
2058    DEBUG("Exit WriteImages");
2059#endif
2060}
2061
2062static void
2063ChildHandler(int sigNum)
2064{
2065    ERROR("pymol (%d) died unexpectedly", stats.pid);
2066    pymolIsAlive = FALSE;
2067    /*DoExit(1);*/
2068}
2069
2070typedef struct {
2071    const char *name;
2072    Tcl_CmdProc *proc;
2073} CmdProc;
2074
2075static CmdProc cmdProcs[] = {
2076    { "cartoon",        CartoonCmd        },
2077    { "cartoontrace",   CartoonTraceCmd   },
2078    { "clientinfo",     ClientInfoCmd     },
2079    { "disable",        DisableCmd        },
2080    { "enable",         EnableCmd         },
2081    { "frame",          FrameCmd          },
2082    { "label",          LabelCmd          },
2083    { "loadpdb",        LoadPDBCmd        },
2084    { "orthoscopic",    OrthoscopicCmd    },
2085    { "pan",            PanCmd            },
2086    { "png",            PngCmd            },
2087    { "ppm",            PpmCmd            },
2088    { "print",          PrintCmd          },
2089    { "raw",            RawCmd            },
2090    { "representation", RepresentationCmd },
2091    { "reset",          ResetCmd          },
2092    { "rock",           RockCmd           },
2093    { "rotate",         RotateCmd         },
2094    { "screen",         ScreenCmd         },
2095    { "spherescale",    SphereScaleCmd    },
2096    { "stickradius",    StickRadiusCmd    },
2097    { "transparency",   TransparencyCmd   },
2098    { "viewport",       ScreenCmd         },
2099    { "vmouse",         VMouseCmd         },
2100    { "zoom",           ZoomCmd           },
2101    { NULL,             NULL              }
2102};
2103
2104static int
2105InitProxy(PymolProxy *p, char *const *argv)
2106{
2107    int sin[2], sout[2]; /* Pipes to connect to server. */
2108    pid_t child;
2109    struct timeval end;
2110
2111    DEBUG("Entering InitProxy");
2112
2113    /* Create two pipes for communication with the external application. One
2114     * each for the applications's: stdin and stdout.  */
2115
2116    if (pipe(sin) == -1) {
2117        return -1;
2118    }
2119    if (pipe(sout) == -1) {
2120        close(sin[0]);
2121        close(sin[1]);
2122        return -1;
2123    }
2124
2125    /* Fork the new process.  Connect I/O to the new socket.  */
2126    child = fork();
2127    if (child < 0) {
2128        ERROR("can't fork process: %s", strerror(errno));
2129        return -3;
2130    }
2131
2132    if (child == 0) {  /* Child process */
2133        int f;
2134        char tmpname[BUFSIZ];
2135
2136        /*
2137         * Create a new process group, so we can later kill this process and
2138         * all its children without affecting the process that created this
2139         * one.
2140         */
2141        setpgid(child, 0);
2142
2143        /* Redirect stdin, stdout, and stderr to pipes before execing */
2144
2145        dup2(sin[0],  0); /* Server standard input */
2146        dup2(sout[1], 1); /* Server standard output */
2147
2148#if LOG_PYMOL_STDERR
2149        /* Redirect child's stderr to a log file. */
2150        sprintf(tmpname, "%s/pymol-%d.stderr", LOGDIR, getpid());
2151        f = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
2152#else
2153        sprintf(tmpname, "/dev/null");
2154        /* Discard stderr */
2155        f = open(tmpname, O_WRONLY, 0600);
2156#endif
2157        if (f < 0) {
2158            ERROR("can't open \"%s\" to capture pymol stderr: %s", tmpname,
2159                  strerror(errno));
2160            exit(1);
2161        }
2162        dup2(f, 2);  /* Redirect stderr to a log file */
2163
2164        /* Close all other descriptors  */
2165        for (f = 3; f < FD_SETSIZE; f++) {
2166            close(f);
2167        }
2168        INFO("Executing \"%s\"", argv[0]);
2169        execvp(argv[0], argv);
2170        ERROR("can't exec `%s': %s", argv[0], strerror(errno));
2171        exit(-1);
2172    } else {
2173        pymolIsAlive = TRUE;
2174        signal(SIGCHLD, ChildHandler);
2175    }
2176    stats.pid = child;
2177
2178    DEBUG("Started %s DISPLAY=%s", argv[0], getenv("DISPLAY"));
2179
2180    /* close opposite end of pipe, these now belong to the child process  */
2181    close(sin[0]);
2182    close(sout[1]);
2183
2184    memset(p, 0, sizeof(PymolProxy));
2185    p->sin        = sin[1];
2186    p->sout       = sout[0];
2187    p->cin        = fileno(stdout);
2188    p->cout       = fileno(stdin);
2189    p->flags      = CAN_UPDATE;
2190    p->frame      = 1;
2191    p->pid      = child;
2192    InitReadBuffer(&p->client, "client", CLIENT_READ, 1<<16);
2193    InitReadBuffer(&p->server, "server", p->sout, 1<<18);
2194
2195    /* Create safe interpreter and add pymol-specific commands to it. */
2196    {
2197        Tcl_Interp *interp;
2198        CmdProc *cp;
2199
2200        interp = Tcl_CreateInterp();
2201        Tcl_MakeSafe(interp);
2202
2203        for (cp = cmdProcs; cp->name != NULL; cp++) {
2204            DEBUG("Adding command %s", cp->name);
2205            Tcl_CreateCommand(interp, cp->name, cp->proc, p, NULL);
2206        }
2207        p->interp = interp;
2208    }
2209    gettimeofday(&end, NULL);
2210    stats.start = end;
2211    return 1;
2212}
2213
2214static int
2215FreeProxy(PymolProxy *p)
2216{
2217    int result, status;
2218
2219    DEBUG("Enter FreeProxy");
2220
2221    close(p->cout);
2222    close(p->sout);
2223    close(p->cin);
2224    close(p->sin);
2225
2226    Tcl_DeleteInterp(p->interp);
2227    ServerStats(0);
2228
2229    DEBUG("Waiting for pymol server to exit");
2230    alarm(5);
2231    if (waitpid(p->pid, &result, 0) < 0) {
2232        ERROR("error waiting on pymol server to exit: %s", strerror(errno));
2233    }
2234
2235    DEBUG("attempting to signal (SIGTERM) pymol server.");
2236    kill(-p->pid, SIGTERM); /* Kill process group */
2237    alarm(5);
2238
2239    DEBUG("Waiting for pymol server to exit after SIGTERM");
2240    if (waitpid(p->pid, &result, 0) < 0) {
2241        if (errno != ECHILD) {
2242            ERROR("error waiting on pymol server to exit after SIGTERM: %s",
2243                  strerror(errno));
2244        }
2245    }
2246    status = -1;
2247    while ((status == -1) && (errno == EINTR)) {
2248        DEBUG("Attempting to signal (SIGKILL) pymol server.");
2249        kill(-p->pid, SIGKILL); /* Kill process group */
2250        alarm(10);
2251
2252        DEBUG("Waiting for pymol server to exit after SIGKILL");
2253        status = waitpid(p->pid, &result, 0);
2254        alarm(0);
2255    }
2256    INFO("pymol server process ended (result=%d)", result);
2257
2258    if (WIFEXITED(result)) {
2259        result = WEXITSTATUS(result);
2260    }
2261    return result;
2262}
2263
2264static void *
2265ClientToServer(void *clientData)
2266{
2267    PymolProxy *p = clientData;
2268    Tcl_DString command;
2269    fd_set readFds;
2270    int ret = 1;
2271    struct timeval tv, *tvPtr;
2272    int polling = 0;
2273
2274#if READ_DEBUG
2275    DEBUG("Reader thread started");
2276#endif
2277    Tcl_DStringInit(&command);
2278    while (pymolIsAlive) {
2279        FD_ZERO(&readFds);
2280        FD_SET(p->client.fd, &readFds);
2281
2282        if (idleTimeout >= 0L) {
2283            tv.tv_sec = idleTimeout;
2284            tv.tv_usec = 0L;
2285            polling = (tv.tv_sec == 0 && tv.tv_usec == 0) ? 1 : 0;
2286            tvPtr = &tv;
2287        } else {
2288            tvPtr = NULL;
2289        }
2290#if READ_DEBUG
2291        DEBUG("Start I/O set");
2292#endif
2293        while (pymolIsAlive &&
2294               (IsLineAvailable(&p->client) ||
2295                (ret = select(p->client.fd+1, &readFds, NULL, NULL, tvPtr)) > 0)) {
2296            size_t numBytes;
2297            const char *line;
2298            int status;
2299
2300            status = GetLine(&p->client, &numBytes, &line);
2301            if (status != BUFFER_OK) {
2302                if (status == BUFFER_EOF) {
2303                    DEBUG("Exiting server on EOF from client");
2304                } else {
2305                    ERROR("Exiting server, failed to read from client (numBytes=%d, status=%d): %s",
2306                          numBytes, status, strerror(errno));
2307                }
2308                goto done;
2309            }
2310            Tcl_DStringAppend(&command, line, numBytes);
2311            if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2312                int result;
2313
2314                /* May execute more than one command. */
2315                result = ExecuteCommand(p->interp, &command);
2316                if (result == TCL_BREAK) {
2317#if READ_DEBUG
2318                    DEBUG("TCL_BREAK found");
2319#endif
2320                    ret = 2;
2321                    break;              /* This was caused by a "imgflush"
2322                                         * command. Break out of the read
2323                                         * loop and allow a new image to be
2324                                         * rendered. */
2325                } else if (result != TCL_OK) {
2326                    ret = 0;
2327                } else {
2328                    ret = 3;
2329                }
2330                if (p->flags & FORCE_UPDATE) {
2331#if READ_DEBUG
2332                    DEBUG("FORCE_UPDATE set");
2333#endif
2334                    break;
2335                }
2336            }
2337            polling = 1;
2338            tv.tv_sec = 0L;
2339            tv.tv_usec = 0L;            /* On successive reads, we break
2340                                         * out if no data is available. */
2341            FD_SET(p->client.fd, &readFds);
2342            tvPtr = &tv;
2343        }
2344#if READ_DEBUG
2345        DEBUG("Finish I/O set");
2346#endif
2347        if (ret < 0) {
2348            DEBUG("Error in select(): %s", strerror(errno));
2349            goto done;
2350        }
2351        if (!polling && ret == 0 && idleTimeout > 0L) {
2352            // If idle timeout expired, disconnect
2353            DEBUG("Exiting server after timeout waiting for client command");
2354            goto done;
2355        }
2356        /* Handle all the pending setting changes now. */
2357        UpdateSettings(p);
2358
2359        /* We might want to refresh the image if we're not currently
2360         * transmitting an image back to the client. The image will be
2361         * refreshed after the image has been completely transmitted. */
2362        if (p->flags & UPDATE_PENDING) {
2363#if READ_DEBUG
2364            DEBUG("calling ppm because of update");
2365#endif
2366            Tcl_Eval(p->interp, "ppm");
2367            p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
2368        }
2369    }
2370 done:
2371#if READ_DEBUG
2372    DEBUG("Leaving Reader thread");
2373#endif
2374    return NULL;
2375}
2376
2377static void *
2378ServerToClient(void *clientData)
2379{
2380    ReadBuffer *bp = clientData;
2381    ImageList list;
2382
2383#if WRITE_DEBUG
2384    DEBUG("Writer thread started");
2385#endif
2386    list.headPtr = list.tailPtr = NULL;
2387    while (pymolIsAlive) {
2388        while (WaitForNextLine(bp, NULL)) {
2389            Image *imgPtr;
2390            const char *line;
2391            char header[200];
2392            size_t len;
2393            int numBytes;
2394            size_t hdrLength;
2395            int frameNum, rockOffset;
2396            char cacheId[200];
2397
2398            /* Keep reading lines untils we find a "image follows:" line */
2399            if (GetLine(bp, &len, &line) != BUFFER_OK) {
2400#if WRITE_DEBUG
2401                DEBUG("Leaving Writer thread");
2402#endif
2403                return NULL;
2404            }
2405#if WRITE_DEBUG
2406            DEBUG("Writer: line found is \"%.*s\"", (int)len, line);
2407#endif
2408            if (strncmp(line, "image follows: ", 15) != 0) {
2409                continue;
2410            }
2411            if (sscanf(line, "image follows: %d %s %d %d\n", &numBytes, cacheId,
2412                       &frameNum, &rockOffset) != 4) {
2413                ERROR("can't get # bytes from \"%.*s\"", (int)len, line);
2414                DEBUG("Leaving Writer thread");
2415                return NULL;
2416            }
2417#if WRITE_DEBUG
2418            DEBUG("found image line (%d bytes)", numBytes);
2419#endif
2420            sprintf(header, "nv>image %d %s %d %d\n", numBytes, cacheId,
2421                    frameNum, rockOffset);
2422            hdrLength = strlen(header);
2423#if WRITE_DEBUG
2424            DEBUG("Queueing image numBytes=%d cacheId=%s, frameNum=%d, rockOffset=%d size=%d",
2425                  numBytes, cacheId, frameNum, rockOffset, numBytes + hdrLength);
2426#endif
2427            imgPtr = NewImage(&list, numBytes + hdrLength);
2428            memcpy(imgPtr->data, header, hdrLength);
2429            if (ReadFollowingData(bp, imgPtr->data + hdrLength,
2430                        (size_t)numBytes) != BUFFER_OK) {
2431                ERROR("can't read %d bytes for \"image follows\" buffer: %s",
2432                      numBytes, strerror(errno));
2433#if WRITE_DEBUG
2434                DEBUG("Leaving Writer thread");
2435#endif
2436                return NULL;
2437            }
2438            stats.numFrames++;
2439            stats.numBytes += numBytes;
2440            {
2441                struct timeval tv;
2442                fd_set writeFds;
2443
2444                tv.tv_sec = tv.tv_usec = 0L;
2445                FD_ZERO(&writeFds);
2446                FD_SET(CLIENT_WRITE, &writeFds);
2447                if (select(CLIENT_WRITE+1, NULL, &writeFds, NULL, &tv) > 0) {
2448                    WriteImages(&list, CLIENT_WRITE);
2449                }
2450            }
2451        }
2452    }
2453#if WRITE_DEBUG
2454    DEBUG("Leaving Writer thread");
2455#endif
2456    return NULL;
2457}
2458
2459int
2460main(int argc, char **argv)
2461{
2462    PymolProxy proxy;
2463    pthread_t thread1, thread2;
2464    char version[200];
2465    ssize_t numWritten;
2466    size_t numBytes;
2467
2468    while (1) {
2469        int c = getopt(argc, argv, "t:");
2470        if (c == -1) {
2471            break;
2472        }
2473        switch (c) {
2474        case 't':
2475            idleTimeout = atol(optarg);
2476            break;
2477        case '?':
2478            break;
2479        default:
2480            return 1;
2481        }
2482    }
2483
2484    frecord = NULL;
2485    if (recording) {
2486        char fileName[BUFSIZ];
2487
2488        sprintf(fileName, "/tmp/pymolproxy%d.py", getpid());
2489        frecord = fopen(fileName, "w");
2490    }
2491    sprintf(version, "PymolProxy %s (build %s)\n", PYMOLPROXY_VERSION, SVN_VERSION);
2492    numBytes = strlen(version);
2493    numWritten = write(CLIENT_WRITE, version, numBytes);
2494    if (numWritten < numBytes) {
2495        ERROR("Short write on version string", strerror(errno));
2496    }
2497
2498    flog = stderr;
2499    openlog("pymolproxy", LOG_CONS | LOG_PERROR | LOG_PID, LOG_USER);
2500    DEBUG("Starting pymolproxy");
2501
2502    InitProxy(&proxy, argv + optind);
2503    if (pthread_create(&thread1, NULL, &ClientToServer, &proxy) < 0) {
2504        ERROR("Can't create reader thread: %s", strerror(errno));
2505    }
2506    if (pthread_create(&thread2, NULL, &ServerToClient, &proxy.server) < 0) {
2507        ERROR("Can't create writer thread: %s", strerror(errno));
2508    }
2509    if (pthread_join(thread1, NULL) < 0) {
2510        ERROR("Can't join reader thread: %s", strerror(errno));
2511    }
2512    return FreeProxy(&proxy);
2513}
Note: See TracBrowser for help on using the repository browser.