[net.lang.c] Missing stdio features.

jpo@cs.nott.ac.uk (Julian Onions) (07/28/86)

I try to use stdio as often as possible for several reasons (such as
portability, efficiency, laziness ...) but I feel it lacks one or two calls
to achieve some of the less every-day options.
Most of these can be got around if you `know' the internal stdio
structure - but that negates the portability win. Anyway, anyone care to
a) comment on these
b) mention their own pet stdio hates
c) show portable ways to implement these
d) tell me they're already implemented ==> RTFM.

-------------------------

Addition:	fpending(fp)

Synopsis:	returns the number of characters that are buffered up
		on the stream pointed to by fp.

Use:		Has several uses, in particular useful when you are
		applying select(2) to a FILE, select may say there is
		nothing to read when in actual fact there is stuff
		buffered up. The only other way around this is to
		set buffering to 0 which is rather inefficient.
		It's trivial to add this as another macro.

-------------------------

Addition:	finvbuf(fp)

Synopsis:	invalidate in-core stdio buffer for FILE *fp.

Use:		If you know by other information that a file you
		currently have open has changed then you need to
		force a reread of the file ensuring that all buffered
		information is junked. The only safe/portable alternative
		is to fclose/fopen the file descriptor again which is
		reasonably expensive. It should probably not guarantee
		anything about the offset or else do a rewind(3).

-------------------------

Addition:	fslam(fp)

Synopsis:	shut the given descriptor in a hurry without flushing it.

Use:		If you know that flushing a descriptor will cause a block.
		e.g. you have a timeout writing to a terminal cos someones
		^S'd it, so you decide to ignore that terminal & carry on.
		How can you shut it, fclose will try and flush the data
		and will hang the program again. If finvbuf() is in place
		this could be used instead. I suppose you could get around
		it with
			close(fileno(fp));
			fclose(fp);
		but it seems a bit messy and may cause indigestion for
		the fclose routine.

-------------------------

Addition:	fbufsiz(fp), fbuftype(fp)

Synopsis:	fbuzsiz returns size of buffer in use, fbuftype indicates
		what sort of buffering is in use (none, line or full).

Use:		To determine whats going on dynamically. E.g.
		if( fbufsiz(stderr) == 0 || fbuftype(stderr) == _IONBF)
		{
			printf("stderr non buffered - fixing\n");
			fclose(stderr);
			stderr = fdopen(2, "w")
			setbuf(stderr, buffer);
		}
		basically, you can set lots of interesting buffering modes
		but you can't discover whats in use.

-------------------------

An inconsistency - why is there no ungetchar(c) defined??


Julian.

jpo@cs.nott.ac.uk				mcvax!ukc!nott-cs!jpo
-- 
Julian Onions

henry@utzoo.UUCP (Henry Spencer) (07/31/86)

> Addition:	fpending(fp)
> ...
> Use:		Has several uses, in particular useful when you are
> 		applying select(2) to a FILE...

Clearly the right solution to that is fselect(), not fpending().  I'm
surprised that there isn't an fselect(), in fact.

The other suggestions seem plausible at first glance.
-- 
EDEC:  Stupidly non-standard
brain-damaged incompatible	Henry Spencer @ U of Toronto Zoology
proprietary protocol used.	{allegra,ihnp4,decvax,pyramid}!utzoo!henry

david@ism780 (08/04/86)

My wish list for stdio would include the following function: 

	flushall ();

Flushall would be used to make sure that all output done to stdio 
files was actually written to the file  system.   Flushall  would
fflush all stdio files that are currently in output mode, similar 
to  what  _cleanup  does but without closing the files.  It would
not affect files that are in input mode.  This allows one to  not
be concerned with having things flushed until it is required, for 
example if one uses the functions fork, exec, sync, ustat, quota, 
or abort.  

mouse@mcgill-vision.UUCP (08/09/86)

> I try to use stdio as often as possible for several reasons [...] but
> I feel it lacks one or two calls [...]
> a) comment on these
> b) mention their own pet stdio hates
> c) show portable ways to implement these
> d) tell me they're already implemented ==> RTFM.

> Addition:	fpending(fp)
(Henry Spencer comments that "the right solution to that is fselect(),
not fpending().".  I disagree; suppose you want to select on a FILE*
and on a file descriptor both?  How do you tell the difference?

> Addition:	finvbuf(fp)
I wanted this one, so I wrote it.  But I called mine fdumpbuf().

> Addition:	fslam(fp)
Interesting.  Never wanted it, though.

> Addition:	fbufsiz(fp), fbuftype(fp)
Never wanted 'em.

> An inconsistency - why is there no ungetchar(c) defined??
I have wondered.

Here are the things I have wanted badly enough to add to stdio:

fdumpbuf(FILE *f)
	Mentioned above, throws away the in-core buffer.

FILE *fopenfxn(int (*fxn)(), char *mode)
	Function-stream I/O.  Returns a FILE*, open for read or write
	(not both, "r+" is treated as "r").  When a character is
	written to (resp. read from) the stream, the function is
	called.  On write, the character written is passed as an
	argument; on read, the returned value is returned to the user
	(or to scanf etc).

FILE *fopenstr(char *str, int len, char *mode)
	An extension to sprintf() and sscanf().  This returns a stream
	which performs I/O to a string (this makes sprintf() and
	sscanf() unnecessay, though they are still convenient).

unfdopen(FILE *f)
	Undoes fdopen(), that is, closes the FILE* without close()ing
	the underlying file descriptor.

Unfdopen is trivial; fopenfxn and fopenstr are implemented by making
the stream appear unbuffered to putc and getc and then changing _flsbuf
and _filbuf to add the functionality.

Comments anyone?
-- 
					der Mouse

USA: {ihnp4,decvax,akgua,utzoo,etc}!utcsri!mcgill-vision!mouse
     think!mosart!mcgill-vision!mouse
Europe: mcvax!decvax!utcsri!mcgill-vision!mouse
ARPAnet: utcsri!mcgill-vision!mouse@uw-beaver.arpa

"Come with me a few minutes, mortal, and we shall talk."
			- Thanatos (Piers Anthony's Bearing an Hourglass)

argv@sri-spam.ARPA (AAAARRRRGGGGv) (08/12/86)

> Here are the things I have wanted badly enough to add to stdio:
> 

> 
> FILE *fopenfxn(int (*fxn)(), char *mode)
> 	Function-stream I/O.  Returns a FILE*, open for read or write
> 	(not both, "r+" is treated as "r").  When a character is
> 	written to (resp. read from) the stream, the function is
> 	called.  On write, the character written is passed as an
> 	argument; on read, the returned value is returned to the user
> 	(or to scanf etc).

This shouldn't be that hard to do:
first, take the fildes of the open stream, set it to be asynch IO, then loop.
When data is there to be read, you'll get a SIGIO.  For some strange reason,
I seem to remember that this is can only be done on pseudo-tty's or other
types of devices (I use it frequently for windows on the SUN); so I don't know
if this will work well on normal files or sockets.

/* (no error checking here -- don't try this at home without mom's help! */

#include <fcntl.h>
...
int fd = open ("/dev/tty", 0);  /* read a tty for example */
signal (SIGIO, io_handler);  /* this proc will get a sigio if data on a stream
			       is ready to be read */
ioctl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | FASYNC)); /* set async IO */

for (;;)
    looping_proc();
...

io_handler()
{
    char buf[BUFSIZ]l
    puts("Hey, there's data to be read on the fildes!");
    read(fd, buf, BUFSIZ);
    do_something_with_data(buf);
}

> FILE *fopenstr(char *str, int len, char *mode)
> 	An extension to sprintf() and sscanf().  This returns a stream
> 	which performs I/O to a string (this makes sprintf() and
> 	sscanf() unnecessay, though they are still convenient).

I request you more fully describe this functionality. I don't see how adding
the routines of an IO library is going to speed up what sprintf and sscanf
already do.  Perhaps to do something like this would be "interesting", but
that's about it.  Again, please elaborate.

> unfdopen(FILE *f)
> 	Undoes fdopen(), that is, closes the FILE* without close()ing
> 	the underlying file descriptor.
This would be nice.

> Unfdopen is trivial; fopenfxn and fopenstr are implemented by making
> the stream appear unbuffered to putc and getc and then changing _flsbuf
> and _filbuf to add the functionality.

Looking at the source to puts (fputs) and so forth, I've found that it
is much faster to never unbuffer anything and to simply macro that which
you'd like to have "unbuffered":
#define unbuffered_puts(s,f)   fputs(s,f), fflush(f)

The reason for this is that if the data is unbuffered, fputs merely sets
the buffer to a buffer within the routine, sets a few flags, goes thru a
while loop, using the macro in stdio to putc to the stream, then fflushes
again, finally resettting all the flags it started with.  Finallly (not
that this has anything to do with it), it returns the last character put
to the stream (undocumented).

In any event, what's preventing you from free()ing the structure and leaving
the fd open?  The biggest problem with routines that people write that do
popen like
read_write_popen(command, &Stdin, &Stdout, &Stderr);
/* open a process and be able to read and write to/from it */
is that they pipe for each fildes, fdopen, and in case of error,
return without closing all open fildes's, unallocating the FILE*'s
and closing the process if necessary. A more elegant routine to 
unfdopen (as you described) would be a matter of convenience.


dan  <argv@sri-spam.arpa>

"Wouldn't ya like to be a pointer, too...
     (be a pointer, be a double pointer, [repeat and fade])"

jpo@cs.nott.ac.uk (Julian Onions) (08/18/86)

In article <479@mcgill-vision.UUCP> mouse@mcgill-vision.UUCP writes:
>> Addition:	fpending(fp)
>(Henry Spencer comments that "the right solution to that is fselect(),
>not fpending().".  I disagree; suppose you want to select on a FILE*
>and on a file descriptor both?  How do you tell the difference?

OK, I'm greedy, I want both. fpending(fp) I wanted after some code
that checked the input for pending characters using FIONREAD.
fselect is rather involved requiring several lines of code,
fpending comes out as something like
#define fpending(fp) ((fp)->_cnt)
Give either fpending or fselect the other can be built and fpending seems
simplest, but maybe not the answer to the right question.
All in all, fselect is the right way to go (stdio features tracking
system calls), but fpending is so trivial it might as well be thrown in.

>> Addition:	fbufsiz(fp), fbuftype(fp)
>Never wanted 'em.
Neither have I, but for the sake of consistency it'd be nice to tell
what buffering is set as well as being able to set buffering.

>Here are the things I have wanted badly enough to add to stdio:
>FILE *fopenfxn(int (*fxn)(), char *mode)
>	Function-stream I/O.
Can't say I've ever thought about this one. I'm not sure when you'd
use it either.

>FILE *fopenstr(char *str, int len, char *mode)
>	An extension to sprintf() and sscanf().  This returns a stream
>	which performs I/O to a string (this makes sprintf() and
>	sscanf() unnecessay, though they are still convenient).
I like this one, C++ has something like this in its streams stuff,
and I liked the idea there. Being able to treat incore strings and
files the same seems a good idea.
e.g.
	if(argc>1)
		if(( fp = fopen(argv[1], "r")) == NULL)
			process(fp = fopenstr(argv[1], strlen(argv[1]), "r"));
		else	process(fp);
	else
		process(fp = stdin);
	fclose(fp);

>unfdopen(FILE *f)
>	Undoes fdopen(), that is, closes the FILE* without close()ing
>	the underlying file descriptor.
Yes, I'd thought of that, however as you say its trivial and can be
done reasonably portably e.g.
unfdopen(fp)
FILE *fp;
{	int oldf, tmpf;
	tmpf = dup(oldf = fileno(fp));
	fclose(fp);
	return dup2(tmpf, oldf);
}
Seems like a good idea. When you've finished playing around with
fprintf's etc. and just want the file descriptor for locking or
some other nefarious process and not all the expensive buffering space.
I'd prefer fdclose as the name though.

Julian.

jpo@cs.nott.ac.uk
jpo%cs.nott.ac.uk@ucl-cs.arpa
mcvax!ukc!nott-cs!jpo
-- 
Julian Onions

guy@sun.uucp (Guy Harris) (08/21/86)

> >Here are the things I have wanted badly enough to add to stdio:
> >FILE *fopenfxn(int (*fxn)(), char *mode)
> >	Function-stream I/O.
> Can't say I've ever thought about this one. I'm not sure when you'd
> use it either.

It might be useful for I/O on "streams" that aren't implemented by a file or
a string.  I suspect somebody out there has wanted such a beast (for
instance, one might use it with the 4.2BSD "syslog" facility, so that each
line written to the stream was logged.

> >FILE *fopenstr(char *str, int len, char *mode)
> >	An extension to sprintf() and sscanf().  This returns a stream
> >	which performs I/O to a string (this makes sprintf() and
> >	sscanf() unnecessay, though they are still convenient).

It looks straightforward to implement IF you have the 4BSD version of
standard I/O, where there's a "this is a string" flag for each FILE
structure.  The S5 version, unfortunately, has only a "char" for the flags,
not a "short", so it says that the "last" FILE entry, and only that entry,
is a stream.  If AT&T-IS is willing to put up with the terrifying
possibility of forcing people to delete *object* files and recompile them
for S5R4 (not *executable images*, they'll work fine as long as they don't
use shared libraries; if you can control which version of a shared library
you bind to, you can even make images built against shared libraries work),
they can do this too.  (Which way did V7 do this?)

Given "fopenstr" you can actually provide a version of "sprintf" that
performs *bounds checking* on the string it's printing into.  (Wow, what a
unique concept, software that doesn't scribble its data space when a user
types in a string longer than it expected!)

> Seems like a good idea. When you've finished playing around with
> fprintf's etc. and just want the file descriptor for locking or
> some other nefarious process and not all the expensive buffering space.
> I'd prefer fdclose as the name though.

That name might imply that the "fd" gets "close"d.  "unfdopen" is,
admittedly, a bit awkward.
-- 
	Guy Harris
	{ihnp4, decvax, seismo, decwrl, ...}!sun!guy
	guy@sun.com (or guy@sun.arpa)

karl@haddock (09/03/86)

jpo@cs.nott.ac.uk (Julian Onions) writes:
>All in all, fselect is the right way to go (stdio features tracking
>system calls), ...

Hasn't anyone noticed that at least part of the purpose of stdio is to make
code portable?  Some systems (even some UNIXes) don't have any way to write
a function such as fselect().

>>FILE *fopenfxn(int (*fxn)(), char *mode)
>>       Function-stream I/O.
>Can't say I've ever thought about this one. I'm not sure when you'd
>use it either.

Potentially useful, though kind of slow if it has to pass one character at a
time through the function.  But (ignoring error conditions) the first arg
should be "char (*)(void)" if the second is "r", or "void (*)(char)" if the
second is "w"; this could cause problems with type-checking.  (Best solution
is to drop the second arg and use two functions, I think.)

Actually, the function argument should probably be analogous to read/write
rather than getc/putc.  But there should be one more argument to fopenfxn(),
viz. a (void *) argument to be passed to fxn() to distinguish streams.

>>FILE *fopenstr(char *str, int len, char *mode)
>>      An extension to sprintf() and sscanf().  This returns a stream
>>      which performs I/O to a string (this makes sprintf() and
>>      sscanf() unnecessay, though they are still convenient).
>I like this one, C++ has something like this in its streams stuff,
>and I liked the idea there. Being able to treat incore strings and
>files the same seems a good idea.

I like it too, but maybe it should be called "stropen" (cf. "fdopen").  It's
a good idea to require the len argument even for string reading, so '\0' can
be treated as an ordinary character if desired.

Karl W. Z. Heuer (ima!haddock!karl; karl@haddock.isc.com), The Walking Lint

karl@haddock (09/03/86)

sun!guy (Guy Harris) writes:
> >FILE *fopenstr(char *str, int len, char *mode)
>It looks straightforward to implement IF you have the 4BSD version of
>standard I/O, where there's a "this is a string" flag for each FILE
>structure.  The S5 version, unfortunately, has only a "char" for the flags,
>not a "short", so it says that the "last" FILE entry, and only that entry,
>is a stream.

Making the last FILE special was indeed a botch, but it isn't necessary to
expand the _flag field to fix it.  Just use (iop->_file == -1) on string
files.

>Given "fopenstr" you can actually provide a version of "sprintf" that
>performs *bounds checking* on the string it's printing into.  (Wow, what a
>unique concept, software that doesn't scribble its data space when a user
>types in a string longer than it expected!)

And given "fopenfxn", you could provide a version of "sprintf" that not only
detects the overflow, but *fixes* it with a realloc()!

Karl W. Z. Heuer (ima!haddock!karl; karl@haddock.isc.com), The Walking Lint

mouse@mcgill-vision.UUCP (der Mouse) (09/04/86)

In article <6224@sri-spam.ARPA>, argv@sri-spam.ARPA (AAAARRRRGGGGv) writes:
[>> is AAAARRRRGGGGv quoting me]
>> Here are the things I have wanted badly enough to add to stdio:
>> FILE *fopenfxn(int (*fxn)(), char *mode)
>>	Function-stream I/O.  Returns a FILE*, open for read or write
>> 	(not both, "r+" is treated as "r").  When a character is
>> 	written to (resp. read from) the stream, the function is
>> 	called.
> This shouldn't be that hard to do:
> first, take the fildes of the open stream, set it to be asynch IO,
> then loop.  When data is there to be read, you'll get a SIGIO.  For
> some strange reason, I seem to remember that this is can only be done
> on pseudo-tty's or other types of devices (I use it frequently for
> windows on the SUN); so I don't know if this will work well on normal
> files or sockets.
(By the way, the "strange reason" is that the code isn't in the kernel
for it!)
[excerpted example follows:]
> int fd = open ("/dev/tty", 0);
> signal (SIGIO, io_handler);
> ioctl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | FASYNC));
> io_handler()
> {
>     puts("Hey, there's data to be read on the fildes!");
> }

You apparently misunderstood (ok, let's be nice, I didn't make myself
clear).  What fopenfxn does is not what the above does (or even what it
appears to be trying to do).  There is no file descriptor associated
with a function stream!  For example, if we do
	f = fopenfxn(foo_fxn,"w");
then
	putc(c,f);
and
	foo_fxn(c,f);
are equivalent.  The advantage is that functions like printf() can be
used on the resulting stream, and call the function once for each
character generated.  Reading works analogously:
	f = fopenfxn(foo_fxn,"r");
	c = getc(f);
is equivalent to
	c = foo_fxn(f);
but makes scanf() and friends work as well.

>> FILE *fopenstr(char *str, int len, char *mode)
>> 	An extension to sprintf() and sscanf().  This returns a stream
>> 	which performs I/O to a string (this makes sprintf() and
>> 	sscanf() unnecessay, though they are still convenient).
>
> I request you more fully describe this functionality. I don't see how
> adding the routines of an IO library is going to speed up what
> sprintf and sscanf already do.  Perhaps to do something like this
> would be "interesting", but that's about it.  Again, please
> elaborate.

Sure.

	f = fopenstr(buf,"w");
	fprintf(f,"Current square is: ");
	print_square(f,current_square);
	fprintf(f,"and no other!");
	fclose(f);

Or

	f = fopenstr(buf,"w");
	fprintf(f,"List is:");
	for (element=list;element;element=element->link)
	 { fprintf(f," %d",element->value);
	 }
	fprintf(f,".");
	fclose(f);

Using this instead of sprintf or sscanf should produce no speed
difference (well, actually, it should be slightly slower because of the
extra function calls involved....).

>> unfdopen(FILE *f)
>> 	Undoes fdopen(), that is, closes the FILE* without close()ing
>> 	the underlying file descriptor.
> This would be nice.

Yeah.  To get it with vanilla stdio requires playing games with dup,
and will cause lots more pain if (as for stdin/out/err) you want the
original descriptor number.  Even then, you gotta have a descriptor
free.

>> Unfdopen is trivial; fopenfxn and fopenstr are implemented by making
>> the stream appear unbuffered to putc and getc and then changing _flsbuf
>> and _filbuf to add the functionality.
>
> Looking at the source to puts (fputs) and so forth, I've found that
> it is much faster to never unbuffer anything and to simply macro that
> which you'd like to have "unbuffered":
> #define unbuffered_puts(s,f)   fputs(s,f), fflush(f)

The point of telling stdio that the stream is unbuffered is that then
other stdio functions such as printf and scanf will call _flsbuf (resp.
_filbuf) for every character.

> The reason for this is that if the data is unbuffered, fputs merely
> sets the buffer to a buffer within the routine, sets a few flags,
> goes thru a while loop, using the macro in stdio to putc to the
> stream, then fflushes again, finally resettting all the flags it
> started with.  Finallly (not that this has anything to do with it),
> it returns the last character put to the stream (undocumented).

You are running 4.3, right?  It was fun making these work when we
shifted to 4.3....4.2 puts (and similar functions) didn't do this
"let's pretend it's buffered for a little while...." trick.

> In any event, what's preventing you from free()ing the structure and
> leaving the fd open?

Huh?  What does this have to do with any of the above?

chris@umcp-cs.UUCP (Chris Torek) (09/04/86)

In article <86900034@haddock> karl@haddock writes:
>>>FILE *fopenfxn(int (*fxn)(), char *mode)
>Actually, the function argument should probably be analogous to read/write
>rather than getc/putc.  But there should be one more argument to fopenfxn(),
>viz. a (void *) argument to be passed to fxn() to distinguish streams.

Indeed, there would be a certain symmetry to the whole thing if one
could write

	reader(f, buf, len)
		FILE *f;
	{

		return (read(fileno(f), buf, len));
	}

	...
		FILE *f = fopenrf(reader, "r");
		fileno(f) = fd;

instead of

		FILE *f = fdopen(fd, "r");
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1516)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu

chris@umcp-cs.UUCP (Chris Torek) (09/04/86)

In article <3265@umcp-cs.UUCP>, I left out a declaration:
>	reader(f, buf, len)
>		FILE *f;
		char *buf;
		int len;
>	{
	...

(`len' would have defaulted properly, of course.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1516)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu

karl@haddock (09/08/86)

umcp-cs!chris (Chris Torek) writes:
>In article <86900034@haddock> karl@haddock writes:
>>>>FILE *fopenfxn(int (*fxn)(), char *mode)
>>Actually, the function argument should probably be analogous to read/write
>>rather than getc/putc.  But there should be one more argument to fopenfxn(),
>>viz. a (void *) argument to be passed to fxn() to distinguish streams.
>
>Indeed, there would be a certain symmetry to the whole thing if one
>could write
>    reader(f, buf, len) FILE *f; { return (read(fileno(f), buf, len)); }
>    FILE *f = fopenrf(reader, "r");
>    fileno(f) = fd;
>instead of
>    FILE *f = fdopen(fd, "r");

As I mentioned, I think it has to be (void *) in general, thus
   reader(void *v, char *buf, int len) {
       return (read(*(int *)v, buf, len));
   }
   FILE *f = fopenrf(&fd, reader, "r");
so that the more general cases could be supported, e.g. read from string:
   typedef struct { char *t_start, *t_end; } tbuf;
   reader(void *v, char *buf, int len) {
       register tbuf *t = (tbuf *)v;
       len = min(len, t->t_end - t->t_start);
       memcpy(buf, t->t_start, len);
       t->start += len;
       return (len);
   }
   FILE *f = fopenrf(&t, reader, "r");

You could get away with having the first arg to reader() be "FILE *", but in
any case "void *_id" needs to replace or supplement the existing "int _file"
in the FILE structure.

If there are separate functions fopenrf() and fopenwf(), is there any need
for the third argument?

Karl W. Z. Heuer (ima!haddock!karl; karl@haddock.isc.com), The Walking Lint

chris@umcp-cs.UUCP (Chris Torek) (09/14/86)

I wrote:
>>Indeed, there would be a certain symmetry to the whole thing if one
>>could write
>>    reader(f, buf, len) FILE *f; { return (read(fileno(f), buf, len)); }
>>    FILE *f = fopenrf(reader, "r");
>>    fileno(f) = fd;
>>instead of
>>    FILE *f = fdopen(fd, "r");

In article <86900044@haddock> karl@haddock writes:
>As I mentioned, I think it has to be (void *) in general, thus
>   reader(void *v, char *buf, int len) {
>       return (read(*(int *)v, buf, len));
>   }
>   FILE *f = fopenrf(&fd, reader, "r");

I agree.  Indeed, I had a more voluminous example that used generic
pointers, but I thought that the idea would be hidden by all the
implementation details, so I trimmed it down to the one in >> above.

>You could get away with having the first arg to reader() be "FILE *", but in
>any case "void *_id" needs to replace or supplement the existing "int _file"
>in the FILE structure.

I think the generic pointer is better.  The reading or writing
routine should need no access to the stdio structures, just as
read() and write() need none.

>If there are separate functions fopenrf() and fopenwf(), is there any need
>for the third argument?

I had a reason for including it at the time, but now I cannot recall
it.  Ah well.  There should be some provision for read-and-write modes,
though:

	typedef int (*iofunc)(void *cookie, char *buf, int len);
	FILE *fopenrwf(void *cookie, iofunc r, iofunc w);

(to specify types everywhere).
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1516)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu