source: pymolproxy/branches/1.0/pymolproxy.c

Last change on this file was 6643, checked in by ldelgass, 7 years ago

merge r6642 from pymolproxy trunk (dataset stats)

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