/*
* 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 <stdlib.h>
#include <sys/types.h>
#include <limits.h>
#include <assert.h>
#include <stdlib.h> // for atof
#include <fcntl.h> // for O_NONBLOCK
#include <sys/stat.h> // for lstat()
#include <tclcl.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1