コード例 #1
0
ファイル: sql_scenario.c プロジェクト: f7753/monetdb
static int
error(stream *out, char *str)
{
	char *p;

	if (!out)
		out = GDKerr;

	if (str == NULL)
		return 0;

	if (mnstr_errnr(out))
		return -1;
	while ((p = strchr(str, '\n')) != NULL) {
		p++;		/* include newline */
		if (*str !='!' && mnstr_write(out, "!", 1, 1) != 1)
			return -1;
		if (mnstr_write(out, str, p - str, 1) != 1)
			 return -1;
		str = p;
	}
	if (str &&*str) {
		if (*str !='!' && mnstr_write(out, "!", 1, 1) != 1)
			return -1;
		if (mnstr_write(out, str, strlen(str), 1) != 1 || mnstr_write(out, "\n", 1, 1) != 1)
			 return -1;
	}
	return 0;
}
コード例 #2
0
ファイル: gdk_atoms.c プロジェクト: MonetDB/MonetDB
int
ATOMprint(int t, const void *p, stream *s)
{
	ssize_t (*tostr) (char **, size_t *, const void *, bool);
	ssize_t res;

	if (p && t >= 0 && t < GDKatomcnt && (tostr = BATatoms[t].atomToStr)) {
		size_t sz;

		if (t != TYPE_bat && t < TYPE_str) {
			char buf[dblStrlen], *addr = buf;	/* use memory from stack */

			sz = dblStrlen;
			res = (*tostr) (&addr, &sz, p, true);
			if (res > 0)
				res = mnstr_write(s, buf, (size_t) res, 1);
		} else {
			str buf = NULL;

			sz = 0;
			res = (*tostr) (&buf, &sz, p, true);
			if (res > 0)
				res = mnstr_write(s, buf, (size_t) res, 1);
			GDKfree(buf);
		}
	} else {
		res = mnstr_write(s, "nil", 1, 3);
	}
	if (res < 0)
		GDKsyserror("ATOMprint: write failure\n");
	return (int) res;
}
コード例 #3
0
ファイル: sql_scan.c プロジェクト: Mytherin/MonetDBLite
static inline int
scanner_read_more(struct scanner *lc, int n)
{
	bstream *b = lc->rs;
	int more = 0;


	while (b->len < b->pos + lc->yycur + n) {

		if (lc->mode == LINE_1 || !lc->started)
			return EOF;

		/* query is not finished ask for more */
		if (b->eof || !isa_block_stream(b->s)) {
			if (mnstr_write(lc->ws, PROMPT2, sizeof(PROMPT2) - 1, 1) == 1)
				mnstr_flush(lc->ws);
			b->eof = 0;
			more = 1;
		}
		/* we need more query text */
		if (bstream_next(b) < 0 ||
		    /* we asked for more data but didn't get any */
		    (more && b->eof && b->len < b->pos + lc->yycur + n))
			return EOF;
	}
	return 1;
}
コード例 #4
0
ファイル: gdk_utils.c プロジェクト: lajus/monetinr
int
THRprintf(stream *s, const char *format, ...)
{
	str bf = THRprintbuf, p = 0;
	size_t bfsz = BUFSIZ;
	int n = 0;
	ptrdiff_t m = 0;
	char c;
	va_list ap;

	if (!s)
		return -1;

	MT_lock_set(&MT_system_lock, "THRprintf");
	if (*format != '!') {
		c = '#';
		if (*format == '#')
			format++;
	} else {
		c = '!';
		format++;
	}

	do {
		p = bf;
		*p++ = c;
		if (GDKdebug & THRDMASK) {
			sprintf(p, "%02d ", THRgettid());
			while (*p)
				p++;
		}
		m = p - bf;
		va_start(ap, format);
		n = vsnprintf(p, bfsz-m, format, ap);
		va_end(ap);
		if (n < 0)
			goto cleanup;
		if ((size_t) n < bfsz - m)
			break;	  /* normal loop exit, usually 1st iteration */
		bfsz = m + n + 1;	/* precisely what is needed */
		if (bf != THRprintbuf)
			free(bf);
		bf = (str) malloc(bfsz);
		assert(bf != NULL);
	} while (1);

	p += n;

	n = 0;
	if (mnstr_write(s, bf, p - bf, 1) != 1)
		n = -1;
      cleanup:
	if (bf != THRprintbuf)
		free(bf);
	MT_lock_unset(&MT_system_lock, "THRprintf");
	return n;
}
コード例 #5
0
ファイル: streams.c プロジェクト: Clay-Birkett/monetdb
str
mnstr_write_stringwrap(int *ret, Stream *S, str *data)
{
	stream *s = *(stream **)S;
	(void)ret;

	if (mnstr_write(s, *data, 1, strlen(*data)) < 0)
		throw(IO, "streams.writeStr", "failed to write string");

	return MAL_SUCCEED;
}
コード例 #6
0
ファイル: sql_scenario.c プロジェクト: f7753/monetdb
int
handle_error(mvc *m, stream *out, int pstatus)
{
	int go = 1;
	char *buf = GDKerrbuf;

	/* transaction already broken */
	if (m->type != Q_TRANS && pstatus < 0) {
		if (mnstr_write(out, TRANS_ABORTED, sizeof(TRANS_ABORTED) - 1, 1) != 1) {
			go = !go;
		}
	} else {
		if (error(out, m->errstr) < 0 || (buf && buf[0] && error(out, buf) < 0)) {
			go = !go;
		}
	}
	/* reset error buffers */
	m->errstr[0] = 0;
	if (buf)
		buf[0] = 0;
	return go;
}
コード例 #7
0
ファイル: sql_scan.c プロジェクト: Mytherin/MonetDBLite
int
sqllex(YYSTYPE * yylval, void *parm)
{
	int token;
	mvc *c = (mvc *) parm;
	struct scanner *lc = &c->scanner;
	int pos;

	/* store position for when view's query ends */
	pos = (int)lc->rs->pos + lc->yycur;

	token = sql_get_next_token(yylval, parm);
	
	if (token == NOT) {
		int next = sqllex(yylval, parm);

		if (next == NOT) {
			return sqllex(yylval, parm);
		} else if (next == BETWEEN) {
			token = NOT_BETWEEN;
		} else if (next == sqlIN) {
			token = NOT_IN;
		} else if (next == LIKE) {
			token = NOT_LIKE;
		} else if (next == ILIKE) {
			token = NOT_ILIKE;
		} else {
			lc->yynext = next;
		}
	} else if (token == UNION) {
		int next = sqllex(yylval, parm);

		if (next == JOIN) {
			token = UNIONJOIN;
		} else {
			lc->yynext = next;
		}
	} else if (token == NO) {
		int next = sqllex(yylval, parm);

		switch (next) {
			case MAXVALUE:
				token = NOMAXVALUE;
			break;
			case MINVALUE:
				token = NOMINVALUE;
			break;
			case CYCLE:
				token = NOCYCLE;
			break;
			default:
				lc->yynext = next;
			break;
		}
	} else if (token == SCOLON) {
		/* ignore semi-colon(s) following a semi-colon */
		if (lc->yylast == SCOLON) {
			int prev = lc->yycur;
			while ((token = sql_get_next_token(yylval, parm)) == SCOLON)
				prev = lc->yycur;

			/* skip the skipped stuff also in the buffer */
			lc->rs->pos += prev;
			lc->yycur -= prev;
			assert(lc->yycur >= 0);
		}
	}

	if (lc->log) 
		mnstr_write(lc->log, lc->rs->buf+pos, lc->rs->pos + lc->yycur - pos, 1);

	/* Don't include literals in the calculation of the key */
	if (token != STRING && token != sqlINT && token != OIDNUM && token != INTNUM && token != APPROXNUM && token != sqlNULL)
		lc->key ^= token;
	lc->started += (token != EOF);
	return token;
}
コード例 #8
0
ファイル: mal_client.c プロジェクト: digideskio/monetdb
/*
 * Input to be processed is collected in a Client specific buffer.  It
 * is filled by reading information from a stream, a terminal, or by
 * scheduling strings constructed internally.  The latter involves
 * removing any escape character needed to manipulate the string within
 * the kernel.  The buffer space is automatically expanded to
 * accommodate new information and the read pointers are adjusted.
 *
 * The input is read from a (blocked) stream and stored in the client
 * record input buffer. The storage area grows automatically upon need.
 * The origin of the input stream depends on the connectivity mode.
 *
 * Each operation received from a front-end consists of at least one
 * line.  To simplify misaligned communication with front-ends, we use
 * different prompts structures.
 *
 * The default action is to read information from an ascii-stream one
 * line at a time. This is the preferred mode for reading from terminal.
 * 
 * The next statement block is to be read. Send a prompt to warn the
 * front-end to issue the request.
 */
int
MCreadClient(Client c)
{
	bstream *in = c->fdin;

#ifdef MAL_CLIENT_DEBUG
	printf("# streamClient %d %d\n", c->idx, isa_block_stream(in->s));
#endif

	while (in->pos < in->len &&
		   (isspace((int) (in->buf[in->pos])) ||
			in->buf[in->pos] == ';' || !in->buf[in->pos]))
		in->pos++;

	if (in->pos >= in->len || in->mode) {
		ssize_t rd, sum = 0;

		if (in->eof || !isa_block_stream(c->fdout)) {
			if (!isa_block_stream(c->fdout) && c->promptlength > 0)
				mnstr_write(c->fdout, c->prompt, c->promptlength, 1);
			mnstr_flush(c->fdout);
			in->eof = 0;
		}
		while ((rd = bstream_next(in)) > 0 && !in->eof) {
			sum += rd;
			if (!in->mode) /* read one line at a time in line mode */
				break;
		}
		if (in->mode) { /* find last new line */
			char *p = in->buf + in->len - 1;

			while (p > in->buf && *p != '\n') {
				*(p + 1) = *p;
				p--;
			}
			if (p > in->buf)
				*(p + 1) = 0;
			if (p != in->buf + in->len - 1)
				in->len++;
		}
		if (sum == 0 && in->eof && isa_block_stream(in->s)) {
			/* we hadn't seen the EOF before, so just try again
			   (this time with prompt) */
#ifdef MAL_CLIENT_DEBUG
			printf("# retry stream reading %d %d\n", c->idx, in->eof);
#endif
			return MCreadClient(c);
		}
#ifdef MAL_CLIENT_DEBUG
		printf("# simple stream received %d sum " SZFMT "\n", c->idx, sum);
#endif
	}
	if (in->pos >= in->len) {
		/* end of stream reached */
#ifdef MAL_CLIENT_DEBUG
		printf("# end of stream received %d %d\n", c->idx, c->bak == 0);
#endif
		if (c->bak) {
			MCpopClientInput(c);
			if (c->fdin == NULL)
				return 0;
			return MCreadClient(c);
		}
		return 0;
	}
	if (*CURRENT(c) == '?') {
		showHelp(c->nspace, CURRENT(c) + 1, c->fdout);
		in->pos = in->len;
		return MCreadClient(c);
	}
#ifdef MAL_CLIENT_DEBUG
	printf("# finished stream read %d %d\n", (int) in->pos, (int) in->len);
	printf("#%s\n", in->buf);
#endif
	return 1;
}
コード例 #9
0
ファイル: sql_scenario.c プロジェクト: f7753/monetdb
/* #define _SQL_READER_DEBUG */
str
SQLreader(Client c)
{
	int go = TRUE;
	int more = TRUE;
	int commit_done = FALSE;
	backend *be = (backend *) c->sqlcontext;
	bstream *in = c->fdin;
	int language = -1;
	mvc *m = NULL;
	int blocked = isa_block_stream(in->s);

	if (SQLinitialized == FALSE) {
		c->mode = FINISHCLIENT;
		return NULL;
	}
	if (!be || c->mode <= FINISHCLIENT) {
#ifdef _SQL_READER_DEBUG
		mnstr_printf(GDKout, "#SQL client finished\n");
#endif
		c->mode = FINISHCLIENT;
		return NULL;
	}
#ifdef _SQL_READER_DEBUG
	mnstr_printf(GDKout, "#SQLparser: start reading SQL %s %s\n", (be->console ? " from console" : ""), (blocked ? "Blocked read" : ""));
#endif
	language = be->language;	/* 'S' for SQL, 'D' from debugger */
	m = be->mvc;
	m->errstr[0] = 0;
	/*
	 * Continue processing any left-over input from the previous round.
	 */

#ifdef _SQL_READER_DEBUG
	mnstr_printf(GDKout, "#pos %d len %d eof %d \n", in->pos, in->len, in->eof);
#endif
	/*
	 * Distinguish between console reading and mclient connections.
	 * The former comes with readline functionality.
	 */
	while (more) {
		more = FALSE;

		/* Different kinds of supported statements sequences
		   A;   -- single line                  s
		   A \n B;      -- multi line                   S
		   A; B;   -- compound single block     s
		   A;   -- many multi line
		   B \n C; -- statements in one block   S
		 */
		/* auto_commit on end of statement */
		if (m->scanner.mode == LINE_N && !commit_done) {
			go = SQLautocommit(c, m);
			commit_done = TRUE;
		}

		if (go && in->pos >= in->len) {
			ssize_t rd;

			if (c->bak) {
#ifdef _SQL_READER_DEBUG
				mnstr_printf(GDKout, "#Switch to backup stream\n");
#endif
				in = c->fdin;
				blocked = isa_block_stream(in->s);
				m->scanner.rs = c->fdin;
				c->fdin->pos += c->yycur;
				c->yycur = 0;
			}
			if (in->eof || !blocked) {
				language = (be->console) ? 'S' : 0;

				/* The rules of auto_commit require us to finish
				   and start a transaction on the start of a new statement (s A;B; case) */
				if (!(m->emod & mod_debug) && !commit_done) {
					go = SQLautocommit(c, m);
					commit_done = TRUE;
				}

				if (go && ((!blocked && mnstr_write(c->fdout, c->prompt, c->promptlength, 1) != 1) || mnstr_flush(c->fdout))) {
					go = FALSE;
					break;
				}
				in->eof = 0;
			}
			if (in->buf == NULL) {
				more = FALSE;
				go = FALSE;
			} else if (go && (rd = bstream_next(in)) <= 0) {
#ifdef _SQL_READER_DEBUG
				mnstr_printf(GDKout, "#rd %d  language %d eof %d\n", rd, language, in->eof);
#endif
				if (be->language == 'D' && in->eof == 0)
					return 0;

				if (rd == 0 && language !=0 && in->eof && !be->console) {
					/* we hadn't seen the EOF before, so just try again
					   (this time with prompt) */
					more = TRUE;
					continue;
				}
				go = FALSE;
				break;
			} else if (go && !be->console && language == 0) {
				if (in->buf[in->pos] == 's' && !in->eof) {
					while ((rd = bstream_next(in)) > 0)
						;
				}
				be->language = in->buf[in->pos++];
				if (be->language == 's') {
					be->language = 'S';
					m->scanner.mode = LINE_1;
				} else if (be->language == 'S') {
					m->scanner.mode = LINE_N;
				}
			}
#ifdef _SQL_READER_DEBUG
			mnstr_printf(GDKout, "#SQL blk:%s\n", in->buf + in->pos);
#endif
		}
	}
	if ( (c->stimeout && (GDKusec() - c->session) > c->stimeout) || !go || (strncmp(CURRENT(c), "\\q", 2) == 0)) {
		in->pos = in->len;	/* skip rest of the input */
		c->mode = FINISHCLIENT;
		return NULL;
	}
	return 0;
}
コード例 #10
0
ファイル: sensor.c プロジェクト: digideskio/monetdb
static void
produceDataStream(Sensor se)
{
	char buf[MYBUFSIZ + 1], *tuple, *c, *d;
	FILE *fd;
	int i, snr;
	int multiply=1000, usec = 0; 
	time_t lasttime = 0, tm = 0;
	struct tm stm;

	/* read a events of messages from a file or standard input.
	   It is processed multiple times.
	   The header is the delay imposed */

	snr = 0;
	do {
		if ( datafile == 0){
			fd = stdin;
		} else {
			fd = fopen(datafile, "r");
			if (fd == NULL) {
				mnstr_printf(SEout, "Could not open file '%s'\n", datafile);
				close_stream(se->toServer);
				se->toServer = NULL;
				return;
			}
		}

		/* read the event requests and sent when the becomes */
		while (fgets(buf, MYBUFSIZ, fd) != 0) {
			int newdelay = delay;
			tuple = buf;

			if ( timecolumn >= 0 ) {
				/* calculate the difference with previous event */
				/* use a simplistic csv file format */
				c= buf;
				for ( i = timecolumn; i> 0; i--){
					if ( (d=strchr(c,(int)','))){
						c= d+1;
					}
				}
				/* convert time to epoch in seconds*/
				memset(&stm, 0, sizeof(struct tm));
				if ( c ){
					c = strptime(c,"%Y-%m-%d %H:%M:%S", &stm);
					tm = mktime(&stm);
					if ( *c == '.') {
						/* microseconds */
						usec = atoi(c+1);
						if ( usec)
							multiply = 1;
					}
				} 
				/* calculate time differential in seconds */
				if ( lasttime )
					newdelay = (int) difftime(tm,lasttime) * multiply + usec;
				lasttime = tm;
				if (trace && newdelay)
					mnstr_printf(SEout, "delayed %d\n", newdelay);
				MT_sleep_ms(newdelay);
			} else
			if (delay > 0) {
				/* wait */
				MT_sleep_ms(delay);
			}
			if (delay < 0) {
				mnstr_printf(SEout, "%s", tuple);
				mnstr_printf(SEout, "send it?");
				getchar();
			}
			if (trace)
				mnstr_printf(SEout, "%s", tuple);
			if ((mnstr_write(se->toServer, tuple, 1, strlen(tuple))) == -1 && (errno == EPIPE)) {
				mnstr_printf(SEout, "errno:%s\n", strerror(errno));
				return;
			}
		}
		fclose(fd);
		snr++;
	} while (snr != events);
}
コード例 #11
0
ファイル: sensor.c プロジェクト: digideskio/monetdb
static void
produceStream(Sensor se)
{
	int b;
	int slen;
	char buf[MYBUFSIZ + 1]; /* compose an event message locally first */
	char tuple[MYBUFSIZ + 1];  /* scratch area for a single tuple element */
	int tlen, maxtuple = MYBUFSIZ;
	int buflen;
	int numberOFtuples = 0;

#ifdef SENSOR_DEBUG
	mnstr_printf(SEout, "#Start producing the stream\n");
	mnstr_printf(SEout, "#%d events, batchsize is %d, columns are %d\n", events, batchsize, columns);
#endif

	/* create scratch space for a single tuple, this should be enough for integer fields */
	buflen = 0;
	while (numberOFtuples < (events * batchsize) || events == -1) {
		int i;
		lng currenttsmp = 0;

		if (delay > 0) {
			/* wait */
			MT_sleep_ms(delay);
		}

		if (delay < 0) {
			mnstr_printf(SEout, "#send next?");
			getchar();
		}

		buf[0] = 0;
		slen = 0;
		tlen = 0;
		if (batchsize > 1) {
			snprintf(tuple, maxtuple, "#%d\n", batchsize);
			tlen += (int) strlen(tuple + tlen);
			strncpy(buf, tuple, tlen);
			slen += tlen;
		}
		/* construct a single event record batch */
		for (b = batchsize; b > 0; b--) {
			/* the first column is used for event identifier tagging */
			tlen = 0;
			if (autoincrement) {
				snprintf(tuple + tlen, maxtuple - tlen, "%d", autoincrement);
				tlen += (int) strlen(tuple + tlen);
				autoincrement++;
			}
			/* if timestamp is set then the next colum will contain
			   the wall-clock microstamp. This reduces the number of
			   additional columns to be produced by 1 */
			if (timestamp) {
				currenttsmp = GDKusec();

				snprintf(tuple + tlen, maxtuple - tlen, "%s" LLFMT "", (autoincrement ? separator[protocol] : ""), currenttsmp);
				tlen += (int) strlen(tuple + tlen);
				if (tlen >= maxtuple) {
					mnstr_printf(SEout, "Buffer not large enough to handle request.\n");
					mnstr_printf(SEout, "recompile with larger constant \n");
					return;
				}
			}

			/* we only generate integer based events for now */
			for (i = (timestamp ? 1 : 0) + (autoincrement ? 1 : 0); i < columns; i++) {
				if (i)
					snprintf(tuple + tlen, maxtuple - tlen, "%s%d", separator[protocol], rand());
				else
					snprintf(tuple + tlen, maxtuple - tlen, "%d", rand());
				tlen += (int) strlen(tuple + tlen);
			}
			snprintf(tuple + tlen, maxtuple - tlen, "\n");
			tlen += (int) strlen(tuple + tlen);
			/* now add the tuple to the buffer if there is room left*/
			if (MYBUFSIZ - buflen <= tlen) {
				mnstr_printf(SEout, "Buffer not large enough to handle request.\n");
				mnstr_printf(SEout, "recompile with larger constant \n");
				return;
			}
			strncpy(buf + slen, tuple, tlen);
			slen += tlen;
			numberOFtuples++;
		}
		/* the batch has now been created, it should be shipped */
		/* watch out, the buffer is not NULL terminated */
		if (mnstr_write(se->toServer, buf, 1, slen) == -1 &&
			((errno == EPIPE) || (errno == ECONNRESET))) {
			mnstr_printf(SEout, "errno:%s %d\n", strerror(errno), errno);
			close_stream(se->toServer);
			se->toServer = NULL;
			return;
		}
		if (trace) {
			buf[slen] = 0;
			mnstr_printf(SEout, "%s", buf);
			/*mnstr_flush(SEout);*/
		}
	}
	/* you should not close the stream to quickly
	   because then you may loose part of the input */

	if (protocol != DEB) {
		mnstr_printf(SEout, "Columns: %d\n", columns);
		mnstr_printf(SEout, "Batch size: %d\n", batchsize);
		mnstr_printf(SEout, "total Number of batches: %d\n", events);
		mnstr_printf(SEout, "Delay: %d\n", delay);
	}
	mnstr_printf(SEout, "ready to close connection?");
	(void) getchar();
	close_stream(se->toServer);
	se->toServer = NULL;
}
コード例 #12
0
ファイル: mal_client.c プロジェクト: MonetDB/MonetDB
/*
 * Input to be processed is collected in a Client specific buffer.  It
 * is filled by reading information from a stream, a terminal, or by
 * scheduling strings constructed internally.  The latter involves
 * removing any escape character needed to manipulate the string within
 * the kernel.  The buffer space is automatically expanded to
 * accommodate new information and the read pointers are adjusted.
 *
 * The input is read from a (blocked) stream and stored in the client
 * record input buffer. The storage area grows automatically upon need.
 * The origin of the input stream depends on the connectivity mode.
 *
 * Each operation received from a front-end consists of at least one
 * line.  To simplify misaligned communication with front-ends, we use
 * different prompts structures.
 *
 * The default action is to read information from an ascii-stream one
 * line at a time. This is the preferred mode for reading from terminal.
 * 
 * The next statement block is to be read. Send a prompt to warn the
 * front-end to issue the request.
 */
int
MCreadClient(Client c)
{
	bstream *in = c->fdin;

#ifdef MAL_CLIENT_DEBUG
	fprintf(stderr,"# streamClient %d %d\n", c->idx, isa_block_stream(in->s));
#endif

	while (in->pos < in->len &&
		   (isspace((unsigned char) (in->buf[in->pos])) ||
			in->buf[in->pos] == ';' || !in->buf[in->pos]))
		in->pos++;

	if (in->pos >= in->len || in->mode) {
		ssize_t rd, sum = 0;

		if (in->eof || !isa_block_stream(c->fdout)) {
			if (!isa_block_stream(c->fdout) && c->promptlength > 0)
				mnstr_write(c->fdout, c->prompt, c->promptlength, 1);
			mnstr_flush(c->fdout);
			in->eof = false;
		}
		while ((rd = bstream_next(in)) > 0 && !in->eof) {
			sum += rd;
			if (!in->mode) /* read one line at a time in line mode */
				break;
		}
		if (in->mode) { /* find last new line */
			char *p = in->buf + in->len - 1;

			while (p > in->buf && *p != '\n') {
				*(p + 1) = *p;
				p--;
			}
			if (p > in->buf)
				*(p + 1) = 0;
			if (p != in->buf + in->len - 1)
				in->len++;
		}
#ifdef MAL_CLIENT_DEBUG
		fprintf(stderr, "# simple stream received %d sum %zu\n", c->idx, sum);
#endif
	}
	if (in->pos >= in->len) {
		/* end of stream reached */
#ifdef MAL_CLIENT_DEBUG
		fprintf(stderr,"# end of stream received %d %d\n", c->idx, c->bak == 0);
#endif
		if (c->bak) {
			MCpopClientInput(c);
			if (c->fdin == NULL)
				return 0;
			return MCreadClient(c);
		}
		return 0;
	}
#ifdef MAL_CLIENT_DEBUG
	fprintf(stderr,"# finished stream read %d %d\n", (int) in->pos, (int) in->len);
	printf("#%s\n", in->buf);
#endif
	return 1;
}