/* * Copyright (c) 1998 University of Southern California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, advertising * materials, and other materials related to such distribution and use * acknowledge that the software was developed by the University of * Southern California, Information Sciences Institute. The name of the * University may not be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * */ #include #include #include #include #include // for atof #include // for O_NONBLOCK #include // for lstat() #include #include "trace.h" #include "nam_stream.h" /**********************************************************************/ class NamStreamClass : public TclClass { public: NamStreamClass() : TclClass("NamStream") {} TclObject* create(int argc, const char*const* argv) { if (argc < 5) return 0; return NamStream::open(argv[4]); } } NamStream_class; int NamStream::command(int argc, const char *const *argv) { Tcl& tcl = Tcl::instance(); if (0) { } else if (argc == 2 && strcmp(argv[1], "gets") == 0) { if (eof()) return TCL_ERROR; // return a line char *buf = tcl.buffer(); assert(4096 > TRACE_LINE_MAXLEN+1); // can the buffer handle us? gets(buf, TRACE_LINE_MAXLEN); buf[TRACE_LINE_MAXLEN] = 0; // paranoia tcl.result(buf); return TCL_OK; } else if (argc == 2 && strcmp(argv[1], "close") == 0) { close(); return TCL_OK; } else if (argc == 2 && strcmp(argv[1], "eof") == 0) { tcl.resultf("%d", eof()); return TCL_OK; }; return (TclObject::command(argc, argv)); } NamStream * NamStream::open(const char *fn) { struct stat state; if (strcmp(fn, "-") == 0 || strcmp(fn, "-.nam") == 0) return NamStreamPipe::open_pipe("-"); #ifndef WIN32 // Windows doesn't have the lstat and S_ISFIFO functions if (lstat(fn, &state) < 0) return NULL; if (S_ISFIFO(state.st_mode)) return NamStreamPipe::open_pipe(fn); #endif if (strcmp(fn + strlen(fn) - 3, ".gz") == 0 || strcmp(fn + strlen(fn) - 2, ".Z") == 0) { #ifdef HAVE_ZLIB_H return new NamStreamCompressedFile(fn); #else /* ! HAVE_ZLIB */ fprintf(stderr, "nam not built with zlib; cannot read compressed files.\n"); return NULL; #endif /* HAVE_ZLIB */ }; /* hope we've got it now */ return new NamStreamFile(fn); } /* * rgets (gets in reverse order) */ char * NamStream::rgets(char *buf, int len) { int ch; /* * prior-line \n current-line \n next-line * * Initially the cursor is on the n of next-line. * read in current-line (which is behind us) * return it * leave the cursor on c of current line. */ /* first make sure we back over the prior newline, if any */ if (seek(-1, SEEK_CUR) < 0) return NULL; ch = get_char(); if (seek(ch == '\n' ? -2 : -1, SEEK_CUR) < 0) return NULL; /* now loop backwards until we get to the newline separating * prior and current. */ off_t pos = tell(); for(;;) { ch = get_char(); if (ch == '\n') break; if (pos == 0) { // beginning of file if (seek(-1, SEEK_CUR) < 0) return NULL; break; } // back up a char & try again pos--; if (seek(-2, SEEK_CUR) < 0) return NULL; }; /* we're just passed the newline for prior-line, or we're at 0 */ /* read current-line, then reset the pointer there */ gets(buf, len); if (pos != seek(pos, SEEK_SET)) return NULL; return buf; } /**********************************************************************/ #if 0 NamStreamFile::NamStreamFile(int fd) : NamStream(fd) { file_ = fdopen(fd, "r"); is_open_ = (NULL != file_); } #endif /* 0 */ NamStreamFile::NamStreamFile(const char *fn) : NamStream(fn) { #ifdef WIN32 // Open in raw binary mode; we'll get rid of \r\n manually. // Otherwise ftell() and fseek() does not work properly. :( file_ = fopen(fn, "rb"); #else file_ = fopen(fn, "r"); #endif is_open_ = (NULL != file_); if (!is_open_) perror(fn); } char * NamStreamFile::gets(char *buf, int len) { char ch = fgetc(file_); if (ch == '\n') { return fgets(buf, len-1, file_); } else { ungetc(ch, file_); return fgets(buf, len, file_); } } char NamStreamFile::get_char() { return getc(file_); } off_t NamStreamFile::seek(off_t offset, int whence) { if (0 == fseek(file_, offset, whence)) return ftell(file_); else return -1; } off_t NamStreamFile::tell() { return ftell(file_); } int NamStreamFile::close() { // fclose(NULL) crashes on Windows, so double check if (file_ == NULL) return 0; int e = fclose(file_); file_ = NULL; return e; } int NamStreamFile::eof() { // feof(NULL) crashes on Windows, so double check return (file_ == NULL) || feof(file_); } int NamStreamFile::read(char *buf, int size) { return fread(buf, 1, size, file_); } /**********************************************************************/ #ifdef HAVE_ZLIB_H /* * Beware: * nam *requires* zlib-1.1.3, as we trip over bugs in 1.1.2's gz* functions. */ NamStreamCompressedFile::NamStreamCompressedFile(const char *fn) : NamStream(fn) { #ifndef ZLIB_VERSION die("zlib version not specified.\n"); int a, b, c; if (3 != sscanf(ZLIB_VERSION, "%d.%d.%d", &a, &b, &c)) { die("zlib version: unknown format.\n"); }; if (!(a > 1 || (a == 1 && b > 1) || (a == 1 && b == 1 && c >= 3))) die("zlib version is too old, nam requires 1.1.3.\n"); #endif file_ = gzopen(fn, "r"); is_open_ = (NULL != file_); } char * NamStreamCompressedFile::gets(char *buf, int len) { char *b = gzgets(file_, buf, len); return b; } char NamStreamCompressedFile::get_char() { return gzgetc(file_); } off_t NamStreamCompressedFile::seek(off_t offset, int whence) { if (whence == SEEK_END) { /* * zlib doesn't support SEEK_END :-< * Walk our way to the end-of-file. */ off_t p = gzseek(file_, 0, SEEK_SET), q; #define STEP (16*1024) char buf[STEP]; int count; for (;;) { /* * Sigh. We actually move all the bytes. XXX * (we can't lseek because we'd lseek * past eof without knowing). */ count = gzread(file_, buf, STEP); if (count <= 0) break; p += count; }; q = gztell(file_); assert (p == q); return q; } else { return gzseek(file_, offset, whence); }; } off_t NamStreamCompressedFile::tell() { return gztell(file_); } int NamStreamCompressedFile::close() { int e = gzclose(file_); file_ = NULL; return e; } int NamStreamCompressedFile::eof() { return gzeof(file_); } int NamStreamCompressedFile::read(char *buf, int size) { int e = gzread(file_, buf, size); return e; } #endif /* HAVE_ZLIB */ /********************************************************************** * Implementation of class NamStreamPipe * - The read operation called by nam is always performed on the backup * file. * - Pipe data are copied to backup file and never returned to nam * directly. * - Pipes are checked when timer expires or nam executes read * operations. *********************************************************************/ // static data members NamStreamPipe* NamStreamPipe::head_ = NULL; int NamStreamPipe::instances_ = 0; Tcl_TimerToken NamStreamPipe::timer_ = NULL; /********************************************************************** * Timer handler that checks data availability of all pipes. **********************************************************************/ void NamStreamPipe::timer_handler(ClientData data) { if (read_pipe()) timer_ = Tcl_CreateTimerHandler(10, timer_handler, NULL); else timer_ = NULL; } /********************************************************************** * Read currently opened pipes. * * RETURN: the number of currently opened pipes. **********************************************************************/ #ifdef PIPE_BUF # define BUF_SIZE PIPE_BUF #else # define BUF_SIZE 8192 #endif int NamStreamPipe::read_pipe() { NamStreamPipe *p; int l, n, fileopen = 0; off_t off; static char buf[BUF_SIZE]; for (p = head_; p; p = p->next_) { // polling for all pipes if (p->front_ < 0) continue; l = ::read(p->front_, buf, BUF_SIZE); if (l < 0) { fileopen++; continue; } if (l == 0) { // end of file ::close(p->front_); p->front_ = -1; continue; } // there are data ready for read, copy to backup file off = ftell(p->back_); fseek(p->back_, 0, SEEK_END); n = fwrite(buf, l, 1, p->back_); if (n <= 0) { // fail to write to backup file ::close(p->front_); p->front_ = -1; // XXX notify user die("NamStreamPipe::read_pipe: tmpfile write problem.\n"); } else { p->back_len_ += l; fileopen++; } if (-1 == fseek(p->back_, off, SEEK_SET)) { // XXX notify user die("NamStreamPipe::read_pipe: fseek problem.\n"); } } return fileopen; } /********************************************************************** * Reentrant function for opening nam pipe stream. * * RETURN: pointer to an NamStreamPipe instance that is associated * with the specified pipe. **********************************************************************/ NamStreamPipe* NamStreamPipe::open_pipe(const char *fn) { NamStreamPipe *p; for (p = head_; p; p = p->next_) { if (! strcmp(fn, p->pipename_)) return p; } return new NamStreamPipe(fn); } NamStreamPipe::NamStreamPipe(const char *fn) : NamStream(fn), front_(-1),back_(NULL), back_len_(0), pipename_(NULL), prev_(NULL), next_(NULL) { #ifndef WIN32 // Windows doesn't have fcntl() function // open pipe and temporary file if (! strcmp(fn, "-")) { // stdin int flag = fcntl(0, F_GETFL, 0); if (flag < 0) goto exception; if (fcntl(0, F_SETFL, flag | O_NONBLOCK) < 0) goto exception; front_ = 0; } else front_ = ::open(fn, O_RDONLY | O_NONBLOCK); back_ = tmpfile(); #endif if ((front_ < 0) || (back_ == NULL)) goto exception; pipename_ = strdup(fn); is_open_ = 1; next_ = head_; if (next_) next_->prev_ = this; head_ = this; instances_++; if (! timer_) { // start the timer if it's idle timer_ = Tcl_CreateTimerHandler(10, timer_handler, NULL); } return; exception: if (front_ > 0) ::close(front_); if (back_) fclose(back_); } /********************************************************************** * Destructor: **********************************************************************/ NamStreamPipe::~NamStreamPipe() { fclose(back_); if (front_ > 0) ::close(front_); if (next_) next_->prev_ = prev_; if (prev_) prev_->next_ = next_; if (pipename_) delete pipename_; instances_--; } /********************************************************************** * Read a line. Nam requires the following semantics: * RETURN: NULL to indicate end-of-file * RETURN: a COMPLETE line, i.e. a complete nam command. It can't return * a partial line, otherwise nam will complain because nam * assumes NamStream is line-buffered. * We assume that there always is new data in the pipe. It's the * responsibility of the pipe data producer (i.e. the application that * generates the nam events to the pipe) to ensure we always have new * input. If we have no new input, then we simply block. **********************************************************************/ char * NamStreamPipe::gets(char *buf, int len) { char *ret = buf; off_t off = ftell(back_); read_pipe(); /* * Case 1: The pipe is just opened to read, but the other end * of the pipe has not open it to write. So, read_pipe() * will close it. * => Reopen the pipe, and return a fake initial command. * Case 2: Both ends have opened the pipe, but the writer has not * produced any data. * => Return a fake initial command. * The fake return value needs to be an initial command. Comment * lines or empty lines are not accepted by nam (e.g. see animator.tcl) * */ if (back_len_ == 0) { #ifndef WIN32 // Windows doesn't have O_NONBLOCK if (front_ == -1) { // it may be closed by read_pipe() front_ = ::open(pipename_, O_RDONLY | O_NONBLOCK); } #endif strcpy(buf, "V -t * -v 1.0a9 -a 0\n"); return buf; } while (1) { /* * Case 3: The backup file is over, but there is no new data in pipe. * => Busy waiting for new input. XXX * This is not a good idea. Since nam is a * single-thread application, busy waiting makes nam * unable to handle other events (e.g. Window, I/O events). * The problem here is that we don't know what to return * when there is no new input from the pipe. * (see netmodel.cc#NetModel::handle()) */ if (! fgets(buf, len, back_)) { // EOF backup file if (front_ == -1) return NULL; else read_pipe(); } /* * Case 4: The backup file is near to over and the last line is * not complete. * => Busy waiting for new input. XXX */ else { int n = strlen(buf) - 1; if (buf[n] == '\n') // here is the normal case return buf; if (front_ == -1) return NULL; fseek(back_, off, SEEK_SET); read_pipe(); } } return ret; } /********************************************************************** * Read a block of data. * XXX Not finished. Don't know what semantics is required by nam. **********************************************************************/ int NamStreamPipe::read(char *buf, int len) { read_pipe(); return fread(buf, 1, len, back_); } /********************************************************************** * Read a character. * XXX Not finished. Don't know what semantics is required by nam. **********************************************************************/ char NamStreamPipe::get_char() { read_pipe(); return fgetc(back_); } off_t NamStreamPipe::seek(off_t offset, int whence) { read_pipe(); if (0 == fseek(back_, offset, whence)) return ftell(back_); else return -1; } off_t NamStreamPipe::tell() { return ftell(back_); } int NamStreamPipe::close() { fseek(back_, 0, SEEK_SET); return 0; } int NamStreamPipe::eof() { if (front_ == -1) { return feof(back_); } else return 0; }