xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/vstream.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: vstream.c,v 1.1.1.2 2011/03/02 19:32:46 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	vstream 3
6 /* SUMMARY
7 /*	light-weight buffered I/O package
8 /* SYNOPSIS
9 /*	#include <vstream.h>
10 /*
11 /*	VSTREAM	*vstream_fopen(path, flags, mode)
12 /*	const char *path;
13 /*	int	flags;
14 /*	mode_t	mode;
15 /*
16 /*	VSTREAM	*vstream_fdopen(fd, flags)
17 /*	int	fd;
18 /*	int	flags;
19 /*
20 /*	int	vstream_fclose(stream)
21 /*	VSTREAM	*stream;
22 /*
23 /*	int	vstream_fdclose(stream)
24 /*	VSTREAM	*stream;
25 /*
26 /*	VSTREAM	*vstream_printf(format, ...)
27 /*	const char *format;
28 /*
29 /*	VSTREAM	*vstream_fprintf(stream, format, ...)
30 /*	VSTREAM	*stream;
31 /*	const char *format;
32 /*
33 /*	int	VSTREAM_GETC(stream)
34 /*	VSTREAM	*stream;
35 /*
36 /*	int	VSTREAM_PUTC(ch, stream)
37 /*	int	ch;
38 /*
39 /*	int	VSTREAM_GETCHAR(void)
40 /*
41 /*	int	VSTREAM_PUTCHAR(ch)
42 /*	int	ch;
43 /*
44 /*	int	vstream_ungetc(stream, ch)
45 /*	VSTREAM *stream;
46 /*	int	ch;
47 /*
48 /*	int	vstream_fputs(str, stream)
49 /*	const char *str;
50 /*	VSTREAM	*stream;
51 /*
52 /*	off_t	vstream_ftell(stream)
53 /*	VSTREAM	*stream;
54 /*
55 /*	off_t	vstream_fseek(stream, offset, whence)
56 /*	VSTREAM	*stream;
57 /*	off_t	offset;
58 /*	int	whence;
59 /*
60 /*	int	vstream_fflush(stream)
61 /*	VSTREAM	*stream;
62 /*
63 /*	int	vstream_fpurge(stream, direction)
64 /*	VSTREAM	*stream;
65 /*	int     direction;
66 /*
67 /*	ssize_t	vstream_fread(stream, buf, len)
68 /*	VSTREAM	*stream;
69 /*	char	*buf;
70 /*	ssize_t	len;
71 /*
72 /*	ssize_t	vstream_fwrite(stream, buf, len)
73 /*	VSTREAM	*stream;
74 /*	const char *buf;
75 /*	ssize_t	len;
76 /*
77 /*	void	vstream_control(stream, name, ...)
78 /*	VSTREAM	*stream;
79 /*	int	name;
80 /*
81 /*	int	vstream_fileno(stream)
82 /*	VSTREAM	*stream;
83 /*
84 /*	const ssize_t vstream_req_bufsize(stream)
85 /*	VSTREAM *stream;
86 /*
87 /*	void	*vstream_context(stream)
88 /*	VSTREAM *stream;
89 /*
90 /*	int	vstream_ferror(stream)
91 /*	VSTREAM	*stream;
92 /*
93 /*	int	vstream_ftimeout(stream)
94 /*	VSTREAM	*stream;
95 /*
96 /*	int	vstream_feof(stream)
97 /*	VSTREAM	*stream;
98 /*
99 /*	int	vstream_clearerr(stream)
100 /*	VSTREAM	*stream;
101 /*
102 /*	const char *VSTREAM_PATH(stream)
103 /*	VSTREAM	*stream;
104 /*
105 /*	char	*vstream_vfprintf(vp, format, ap)
106 /*	const char *format;
107 /*	va_list	*ap;
108 /*
109 /*	ssize_t	vstream_bufstat(stream, command)
110 /*	VSTREAM	*stream;
111 /*	int	command;
112 /*
113 /*	ssize_t	vstream_peek(stream)
114 /*	VSTREAM	*stream;
115 /*
116 /*	int	vstream_setjmp(stream)
117 /*	VSTREAM	*stream;
118 /*
119 /*	void	vstream_longjmp(stream, val)
120 /*	VSTREAM	*stream;
121 /*	int	val;
122 /*
123 /*	time_t	vstream_ftime(stream)
124 /*	VSTREAM	*stream;
125 /*
126 /*	struct timeval vstream_ftimeval(stream)
127 /*	VSTREAM	*stream;
128 /* DESCRIPTION
129 /*	The \fIvstream\fR module implements light-weight buffered I/O
130 /*	similar to the standard I/O routines.
131 /*
132 /*	The interface is implemented in terms of VSTREAM structure
133 /*	pointers, also called streams. For convenience, three streams
134 /*	are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These
135 /*	streams are connected to the standard input, output and error
136 /*	file descriptors, respectively.
137 /*
138 /*	Although the interface is patterned after the standard I/O
139 /*	library, there are some major differences:
140 /* .IP \(bu
141 /*	File descriptors are not limited to the range 0..255. This
142 /*	was reason #1 to write these routines in the first place.
143 /* .IP \(bu
144 /*	The application can switch between reading and writing on
145 /*	the same stream without having to perform a flush or seek
146 /*	operation, and can change write position without having to
147 /*	flush.  This was reason #2. Upon position or direction change,
148 /*	unread input is discarded, and unwritten output is flushed
149 /*	automatically. Exception: with double-buffered streams, unread
150 /*	input is not discarded upon change of I/O direction, and
151 /*	output flushing is delayed until the read buffer must be refilled.
152 /* .IP \(bu
153 /*	A bidirectional stream can read and write with the same buffer
154 /*	and file descriptor, or it can have separate read/write
155 /*	buffers and/or file descriptors.
156 /* .IP \(bu
157 /*	No automatic flushing of VSTREAM_OUT upon program exit, or of
158 /*	VSTREAM_ERR at any time. No unbuffered or line buffered modes.
159 /*	This functionality may be added when it is really needed.
160 /* .PP
161 /*	vstream_fopen() opens the named file and associates a buffered
162 /*	stream with it.  The \fIpath\fR, \fIflags\fR and \fImode\fR
163 /*	arguments are passed on to the open(2) routine. The result is
164 /*	a null pointer in case of problems. The \fIpath\fR argument is
165 /*	copied and can be looked up with VSTREAM_PATH().
166 /*
167 /*	vstream_fdopen() takes an open file and associates a buffered
168 /*	stream with it. The \fIflags\fR argument specifies how the file
169 /*	was opened. vstream_fdopen() either succeeds or never returns.
170 /*
171 /*	vstream_fclose() closes the named buffered stream. The result
172 /*	is 0 in case of success, VSTREAM_EOF in case of problems.
173 /*	vstream_fclose() reports the same errors as vstream_ferror().
174 /*
175 /*	vstream_fdclose() leaves the file(s) open but is otherwise
176 /*	identical to vstream_fclose().
177 /*
178 /*	vstream_fprintf() formats its arguments according to the
179 /*	\fIformat\fR argument and writes the result to the named stream.
180 /*	The result is the stream argument. It understands the s, c, d, u,
181 /*	o, x, X, e, f and g format types, the l modifier, field width and
182 /*	precision, sign, and padding with zeros or spaces. In addition,
183 /*	vstream_fprintf() recognizes the %m format specifier and expands
184 /*	it to the error message corresponding to the current value of the
185 /*	global \fIerrno\fR variable.
186 /*
187 /*	vstream_printf() performs formatted output to the standard output
188 /*	stream.
189 /*
190 /*	VSTREAM_GETC() reads the next character from the named stream.
191 /*	The result is VSTREAM_EOF when end-of-file is reached or if a read
192 /*	error was detected. VSTREAM_GETC() is an unsafe macro that
193 /*	evaluates some arguments more than once.
194 /*
195 /*	VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN).
196 /*
197 /*	VSTREAM_PUTC() appends the specified character to the specified
198 /*	stream. The result is the stored character, or VSTREAM_EOF in
199 /*	case of problems. VSTREAM_PUTC() is an unsafe macro that
200 /*	evaluates some arguments more than once.
201 /*
202 /*	VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT).
203 /*
204 /*	vstream_ungetc() pushes back a character onto the specified stream
205 /*	and returns the character, or VSTREAM_EOF in case of problems.
206 /*	It is an error to push back before reading (or immediately after
207 /*	changing the stream offset via vstream_fseek()). Upon successful
208 /*	return, vstream_ungetc() clears the end-of-file stream flag.
209 /*
210 /*	vstream_fputs() appends the given null-terminated string to the
211 /*	specified buffered stream. The result is 0 in case of success,
212 /*	VSTREAM_EOF in case of problems.
213 /*
214 /*	vstream_ftell() returns the file offset for the specified stream,
215 /*	-1 if the stream is connected to a non-seekable file.
216 /*
217 /*	vstream_fseek() changes the file position for the next read or write
218 /*	operation. Unwritten output is flushed. With unidirectional streams,
219 /*	unread input is discarded. The \fIoffset\fR argument specifies the file
220 /*	position from the beginning of the file (\fIwhence\fR is SEEK_SET),
221 /*	from the current file position (\fIwhence\fR is SEEK_CUR), or from
222 /*	the file end (SEEK_END). The result value is the file offset
223 /*	from the beginning of the file, -1 in case of problems.
224 /*
225 /*	vstream_fflush() flushes unwritten data to a file that was
226 /*	opened in read-write or write-only mode.
227 /*	vstream_fflush() returns 0 in case of success, VSTREAM_EOF in
228 /*	case of problems. It is an error to flush a read-only stream.
229 /*	vstream_fflush() reports the same errors as vstream_ferror().
230 /*
231 /*	vstream_fpurge() discards the contents of the stream buffer.
232 /*	If direction is VSTREAM_PURGE_READ, it discards unread data,
233 /*	else if direction is VSTREAM_PURGE_WRITE, it discards unwritten
234 /*	data. In the case of a double-buffered stream, if direction is
235 /*	VSTREAM_PURGE_BOTH, it discards the content of both the read
236 /*	and write buffers. vstream_fpurge() returns 0 in case of success,
237 /*	VSTREAM_EOF in case of problems.
238 /*
239 /*	vstream_fread() and vstream_fwrite() perform unformatted I/O
240 /*	on the named stream. The result value is the number of bytes
241 /*	transferred. A short count is returned in case of end-of-file
242 /*	or error conditions.
243 /*
244 /*	vstream_control() allows the user to fine tune the behavior of
245 /*	the specified stream.  The arguments are a list of (name,
246 /*	value) pairs, terminated with VSTREAM_CTL_END.
247 /*	The following lists the names and the types of the corresponding
248 /*	value arguments.
249 /* .IP "VSTREAM_CTL_READ_FN (ssize_t (*)(int, void *, size_t, int, void *))"
250 /*	The argument specifies an alternative for the timed_read(3) function,
251 /*	for example, a read function that performs decryption.
252 /* .IP "VSTREAM_CTL_WRITE_FN (ssize_t (*)(int, void *, size_t, int, void *))"
253 /*	The argument specifies an alternative for the timed_write(3) function,
254 /*	for example, a write function that performs encryption.
255 /* .IP "VSTREAM_CTL_CONTEXT (char *)"
256 /*	The argument specifies application context that is passed on to
257 /*	the application-specified read/write routines. No copy is made.
258 /* .IP "VSTREAM_CTL_PATH (char *)"
259 /*	Updates the stored pathname of the specified stream. The pathname
260 /*	is copied.
261 /* .IP "VSTREAM_CTL_DOUBLE (no value)"
262 /*	Use separate buffers for reading and for writing.  This prevents
263 /*	unread input from being discarded upon change of I/O direction.
264 /* .IP "VSTREAM_CTL_READ_FD (int)
265 /*	The argument specifies the file descriptor to be used for reading.
266 /*	This feature is limited to double-buffered streams, and makes the
267 /*	stream non-seekable.
268 /* .IP "VSTREAM_CTL_WRITE_FD (int)
269 /*	The argument specifies the file descriptor to be used for writing.
270 /*	This feature is limited to double-buffered streams, and makes the
271 /*	stream non-seekable.
272 /* .IP "VSTREAM_CTL_SWAP_FD (VSTREAM *)"
273 /*	The argument specifies a VSTREAM pointer; the request swaps the
274 /*	file descriptor members of the two streams. This feature is limited
275 /*	to streams that are both double-buffered or both single-buffered.
276 /* .IP "VSTREAM_CTL_DUPFD (int)"
277 /*	The argument specifies a minimum file descriptor value. If
278 /*	the actual stream's file descriptors are below the minimum,
279 /*	reallocate the descriptors to the first free value greater
280 /*	than or equal to the minimum. The VSTREAM_CTL_DUPFD macro
281 /*	is defined only on systems with fcntl() F_DUPFD support.
282 /* .IP "VSTREAM_CTL_WAITPID_FN (int (*)(pid_t, WAIT_STATUS_T *, int))"
283 /*	A pointer to function that behaves like waitpid(). This information
284 /*	is used by the vstream_pclose() routine.
285 /* .IP "VSTREAM_CTL_TIMEOUT (int)
286 /*	The deadline for a descriptor to become readable in case of a read
287 /*	request, or writable in case of a write request. Specify a value
288 /*	<= 0 to disable deadlines.
289 /* .IP "VSTREAM_CTL_EXCEPT (no value)"
290 /*	Enable exception handling with vstream_setjmp() and vstream_longjmp().
291 /*	This involves allocation of additional memory that normally isn't
292 /*	used.
293 /* .IP "VSTREAM_CTL_BUFSIZE (ssize_t)"
294 /*	Specify a non-default write buffer size, or zero to implement
295 /*	a no-op. Requests to shrink an existing buffer size are
296 /*	ignored. Requests to change a fixed-size buffer (stdin,
297 /*	stdout, stderr) are not allowed.
298 /*
299 /*	NOTE: the VSTREAM_CTL_BUFSIZE request specifies intent, not
300 /*	reality.  Actual buffer sizes are not updated immediately.
301 /*	Instead, an existing write buffer will be resized when it
302 /*	is full, and an existing read buffer will be resized when
303 /*	the buffer is filled.
304 /*
305 /*	NOTE: the VSTREAM_CTL_BUFSIZE argument type is ssize_t, not
306 /*	int. Use an explicit cast to avoid problems on LP64
307 /*	environments and other environments where ssize_t is larger
308 /*	than int.
309 /* .PP
310 /*	vstream_fileno() gives access to the file handle associated with
311 /*	a buffered stream. With streams that have separate read/write
312 /*	file descriptors, the result is the current descriptor.
313 /*
314 /*	vstream_req_bufsize() returns the requested buffer size for
315 /*	the named stream (default: VSTREAM_BUFSIZE). The result
316 /*	value reflects intent, not reality: actual buffer sizes are
317 /*	not updated immediately when the requested buffer size is
318 /*	specified with vstream_control().  Instead, an existing
319 /*	write buffer will be resized when it is full, and an existing
320 /*	read buffer will be resized when the buffer is filled.
321 /*
322 /*	vstream_context() returns the application context that is passed on to
323 /*	the application-specified read/write routines.
324 /*
325 /*	VSTREAM_PATH() is an unsafe macro that returns the name stored
326 /*	with vstream_fopen() or with vstream_control(). The macro is
327 /*	unsafe because it evaluates some arguments more than once.
328 /*
329 /*	vstream_feof() returns non-zero when a previous operation on the
330 /*	specified stream caused an end-of-file condition.
331 /*	Although further read requests after EOF may complete
332 /*	succesfully, vstream_feof() will keep returning non-zero
333 /*	until vstream_clearerr() is called for that stream.
334 /*
335 /*	vstream_ferror() returns non-zero when a previous operation on the
336 /*	specified stream caused a non-EOF error condition, including timeout.
337 /*	After a non-EOF error on a stream, no I/O request will
338 /*	complete until after vstream_clearerr() is called for that stream.
339 /*
340 /*	vstream_ftimeout() returns non-zero when a previous operation on the
341 /*	specified stream caused a timeout error condition. See
342 /*	vstream_ferror() for error persistence details.
343 /*
344 /*	vstream_clearerr() resets the timeout, error and end-of-file indication
345 /*	of the specified stream, and returns no useful result.
346 /*
347 /*	vstream_vfprintf() provides an alternate interface
348 /*	for formatting an argument list according to a format string.
349 /*
350 /*	vstream_bufstat() provides input and output buffer status
351 /*	information.  The command is one of the following:
352 /* .IP VSTREAM_BST_IN_PEND
353 /*	Return the number of characters that can be read without
354 /*	refilling the read buffer.
355 /* .IP VSTREAM_BST_OUT_PEND
356 /*	Return the number of characters that are waiting in the
357 /*	write buffer.
358 /* .PP
359 /*	vstream_peek() returns the number of characters that can be
360 /*	read from the named stream without refilling the read buffer.
361 /*	This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND).
362 /*
363 /*	vstream_setjmp() saves processing context and makes that context
364 /*	available for use with vstream_longjmp().  Normally, vstream_setjmp()
365 /*	returns zero.  A non-zero result means that vstream_setjmp() returned
366 /*	through a vstream_longjmp() call; the result is the \fIval\fR argment
367 /*	given to vstream_longjmp().
368 /*
369 /*	NB: non-local jumps such as vstream_longjmp() are not safe
370 /*	for jumping out of any routine that manipulates VSTREAM data.
371 /*	longjmp() like calls are best avoided in signal handlers.
372 /*
373 /*	vstream_ftime() returns the time of initialization, the last buffer
374 /*	fill operation, or the last buffer flush operation for the specified
375 /*	stream. This information is maintained only when stream timeouts are
376 /*	enabled.
377 /*
378 /*	vstream_ftimeval() is like vstream_ftime() but returns more
379 /*	detail.
380 /* DIAGNOSTICS
381 /*	Panics: interface violations. Fatal errors: out of memory.
382 /* SEE ALSO
383 /*	timed_read(3) default read routine
384 /*	timed_write(3) default write routine
385 /*	vbuf_print(3) formatting engine
386 /*	setjmp(3) non-local jumps
387 /* BUGS
388 /*	Should use mmap() on reasonable systems.
389 /* LICENSE
390 /* .ad
391 /* .fi
392 /*	The Secure Mailer license must be distributed with this software.
393 /* AUTHOR(S)
394 /*	Wietse Venema
395 /*	IBM T.J. Watson Research
396 /*	P.O. Box 704
397 /*	Yorktown Heights, NY 10598, USA
398 /*--*/
399 
400 /* System library. */
401 
402 #include <sys_defs.h>
403 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
404 #include <stdarg.h>
405 #include <stddef.h>
406 #include <unistd.h>
407 #include <fcntl.h>
408 #include <time.h>
409 #include <errno.h>
410 #include <string.h>
411 
412 /* Utility library. */
413 
414 #include "mymalloc.h"
415 #include "msg.h"
416 #include "vbuf_print.h"
417 #include "iostuff.h"
418 #include "vstring.h"
419 #include "vstream.h"
420 
421 /* Application-specific. */
422 
423  /*
424   * Forward declarations.
425   */
426 static int vstream_buf_get_ready(VBUF *);
427 static int vstream_buf_put_ready(VBUF *);
428 static int vstream_buf_space(VBUF *, ssize_t);
429 
430  /*
431   * Initialization of the three pre-defined streams. Pre-allocate a static
432   * I/O buffer for the standard error stream, so that the error handler can
433   * produce a diagnostic even when memory allocation fails.
434   */
435 static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE];
436 
437 VSTREAM vstream_fstd[] = {
438     {{
439 	    0,				/* flags */
440 	    0, 0, 0, 0,			/* buffer */
441 	    vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
442     }, STDIN_FILENO, (VSTREAM_FN) timed_read, (VSTREAM_FN) timed_write,
443     VSTREAM_BUFSIZE,},
444     {{
445 	    0,				/* flags */
446 	    0, 0, 0, 0,			/* buffer */
447 	    vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
448     }, STDOUT_FILENO, (VSTREAM_FN) timed_read, (VSTREAM_FN) timed_write,
449     VSTREAM_BUFSIZE,},
450     {{
451 	    VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE,
452 	    vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf,
453 	    vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
454     }, STDERR_FILENO, (VSTREAM_FN) timed_read, (VSTREAM_FN) timed_write,
455     VSTREAM_BUFSIZE,},
456 };
457 
458 #define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR)
459 
460  /*
461   * A bunch of macros to make some expressions more readable. XXX We're
462   * assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2.
463   */
464 #define VSTREAM_ACC_MASK(f)	((f) & (O_APPEND | O_WRONLY | O_RDWR))
465 
466 #define VSTREAM_CAN_READ(f)	(VSTREAM_ACC_MASK(f) == O_RDONLY \
467 				|| VSTREAM_ACC_MASK(f) == O_RDWR)
468 #define VSTREAM_CAN_WRITE(f)	(VSTREAM_ACC_MASK(f) & O_WRONLY \
469 				|| VSTREAM_ACC_MASK(f) & O_RDWR \
470 				|| VSTREAM_ACC_MASK(f) & O_APPEND)
471 
472 #define VSTREAM_BUF_COUNT(bp, n) \
473 	((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n))
474 
475 #define VSTREAM_BUF_AT_START(bp) { \
476 	(bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \
477 	(bp)->ptr = (bp)->data; \
478     }
479 
480 #define VSTREAM_BUF_AT_OFFSET(bp, offset) { \
481 	(bp)->ptr = (bp)->data + (offset); \
482 	(bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \
483     }
484 
485 #define VSTREAM_BUF_AT_END(bp) { \
486 	(bp)->cnt = 0; \
487 	(bp)->ptr = (bp)->data + (bp)->len; \
488     }
489 
490 #define VSTREAM_BUF_ZERO(bp) { \
491 	(bp)->flags = 0; \
492 	(bp)->data = (bp)->ptr = 0; \
493 	(bp)->len = (bp)->cnt = 0; \
494     }
495 
496 #define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \
497 	(bp)->get_ready = (get_action); \
498 	(bp)->put_ready = (put_action); \
499 	(bp)->space = (space_action); \
500     }
501 
502 #define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \
503 	stream->buffer = stream->buf; \
504 	stream->filedes = stream->fd; \
505     }
506 
507 #define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \
508 	stream->buffer.flags = stream->buf.flags; \
509 	stream->buf = stream->buffer; \
510 	stream->fd = stream->filedes; \
511     } while(0)
512 
513 #define VSTREAM_FORK_STATE(stream, buffer, filedes) { \
514 	stream->buffer = stream->buf; \
515 	stream->filedes = stream->fd; \
516 	stream->buffer.data = stream->buffer.ptr = 0; \
517 	stream->buffer.len = stream->buffer.cnt = 0; \
518 	stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \
519     };
520 
521 #define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE)
522 #define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE)
523 
524 #define VSTREAM_FFLUSH_SOME(stream) \
525 	vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt)
526 
527 /* vstream_buf_init - initialize buffer */
528 
529 static void vstream_buf_init(VBUF *bp, int flags)
530 {
531 
532     /*
533      * Initialize the buffer such that the first data access triggers a
534      * buffer boundary action.
535      */
536     VSTREAM_BUF_ZERO(bp);
537     VSTREAM_BUF_ACTIONS(bp,
538 			VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0,
539 			VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0,
540 			vstream_buf_space);
541 }
542 
543 /* vstream_buf_alloc - allocate buffer memory */
544 
545 static void vstream_buf_alloc(VBUF *bp, ssize_t len)
546 {
547     VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
548     ssize_t used = bp->ptr - bp->data;
549     const char *myname = "vstream_buf_alloc";
550 
551     if (len < bp->len)
552 	msg_panic("%s: attempt to shrink buffer", myname);
553     if (bp->flags & VSTREAM_FLAG_FIXED)
554 	msg_panic("%s: unable to extend fixed-size buffer", myname);
555 
556     /*
557      * Late buffer allocation allows the user to override the default policy.
558      * If a buffer already exists, allow for the presence of (output) data.
559      */
560     bp->data = (unsigned char *)
561 	(bp->data ? myrealloc((char *) bp->data, len) : mymalloc(len));
562     bp->len = len;
563     if (bp->flags & VSTREAM_FLAG_READ) {
564 	bp->ptr = bp->data + used;
565 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
566 	    VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
567     } else {
568 	VSTREAM_BUF_AT_OFFSET(bp, used);
569 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
570 	    VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
571     }
572 }
573 
574 /* vstream_buf_wipe - reset buffer to initial state */
575 
576 static void vstream_buf_wipe(VBUF *bp)
577 {
578     if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data)
579 	myfree((char *) bp->data);
580     VSTREAM_BUF_ZERO(bp);
581     VSTREAM_BUF_ACTIONS(bp, 0, 0, 0);
582 }
583 
584 /* vstream_fflush_some - flush some buffered data */
585 
586 static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush)
587 {
588     const char *myname = "vstream_fflush_some";
589     VBUF   *bp = &stream->buf;
590     ssize_t used;
591     ssize_t left_over;
592     char   *data;
593     ssize_t len;
594     ssize_t n;
595 
596     /*
597      * Sanity checks. It is illegal to flush a read-only stream. Otherwise,
598      * if there is buffered input, discard the input. If there is buffered
599      * output, require that the amount to flush is larger than the amount to
600      * keep, so that we can memcpy() the residue.
601      */
602     if (bp->put_ready == 0)
603 	msg_panic("%s: read-only stream", myname);
604     switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
605     case VSTREAM_FLAG_READ:			/* discard input */
606 	VSTREAM_BUF_AT_END(bp);
607 	/* FALLTHROUGH */
608     case 0:					/* flush after seek? */
609 	return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
610     case VSTREAM_FLAG_WRITE:			/* output buffered */
611 	break;
612     case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
613 	msg_panic("%s: read/write stream", myname);
614     }
615     used = bp->len - bp->cnt;
616     left_over = used - to_flush;
617 
618     if (msg_verbose > 2 && stream != VSTREAM_ERR)
619 	msg_info("%s: fd %d flush %ld", myname, stream->fd, (long) to_flush);
620     if (to_flush < 0 || left_over < 0)
621 	msg_panic("%s: bad to_flush %ld", myname, (long) to_flush);
622     if (to_flush < left_over)
623 	msg_panic("%s: to_flush < left_over", myname);
624     if (to_flush == 0)
625 	return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
626     if (bp->flags & VSTREAM_FLAG_ERR)
627 	return (VSTREAM_EOF);
628 
629     /*
630      * When flushing a buffer, allow for partial writes. These can happen
631      * while talking to a network. Update the cached file seek position, if
632      * any.
633      */
634     for (data = (char *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
635 	if ((n = stream->write_fn(stream->fd, data, len, stream->timeout, stream->context)) <= 0) {
636 	    bp->flags |= VSTREAM_FLAG_ERR;
637 	    if (errno == ETIMEDOUT)
638 		bp->flags |= VSTREAM_FLAG_TIMEOUT;
639 	    return (VSTREAM_EOF);
640 	}
641 	if (stream->timeout)
642 	    GETTIMEOFDAY(&stream->iotime);
643 	if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush)
644 	    msg_info("%s: %d flushed %ld/%ld", myname, stream->fd,
645 		     (long) n, (long) to_flush);
646     }
647     if (bp->flags & VSTREAM_FLAG_SEEK)
648 	stream->offset += to_flush;
649 
650     /*
651      * Allow for partial buffer flush requests. We use memcpy() for reasons
652      * of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-).
653      * This is OK because we have already verified that the to_flush count is
654      * larger than the left_over count.
655      */
656     if (left_over > 0)
657 	memcpy(bp->data, bp->data + to_flush, left_over);
658     bp->cnt += to_flush;
659     bp->ptr -= to_flush;
660     return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
661 }
662 
663 /* vstream_fflush_delayed - delayed stream flush for double-buffered stream */
664 
665 static int vstream_fflush_delayed(VSTREAM *stream)
666 {
667     int     status;
668 
669     /*
670      * Sanity check.
671      */
672     if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE)
673 	msg_panic("vstream_fflush_delayed: bad flags");
674 
675     /*
676      * Temporarily swap buffers and flush unwritten data. This may seem like
677      * a lot of work, but it's peanuts compared to the write(2) call that we
678      * already have avoided. For example, delayed flush is never used on a
679      * non-pipelined SMTP connection.
680      */
681     stream->buf.flags &= ~VSTREAM_FLAG_READ;
682     VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
683     stream->buf.flags |= VSTREAM_FLAG_WRITE;
684     VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
685 
686     status = VSTREAM_FFLUSH_SOME(stream);
687 
688     stream->buf.flags &= ~VSTREAM_FLAG_WRITE;
689     VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
690     stream->buf.flags |= VSTREAM_FLAG_READ;
691     VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
692 
693     return (status);
694 }
695 
696 /* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */
697 
698 static int vstream_buf_get_ready(VBUF *bp)
699 {
700     VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
701     const char *myname = "vstream_buf_get_ready";
702     ssize_t n;
703 
704     /*
705      * Detect a change of I/O direction or position. If so, flush any
706      * unwritten output immediately when the stream is single-buffered, or
707      * when the stream is double-buffered and the read buffer is empty.
708      */
709     switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
710     case VSTREAM_FLAG_WRITE:			/* change direction */
711 	if (bp->ptr > bp->data)
712 	    if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0
713 		|| stream->read_buf.cnt >= 0)
714 		if (VSTREAM_FFLUSH_SOME(stream))
715 		    return (VSTREAM_EOF);
716 	bp->flags &= ~VSTREAM_FLAG_WRITE;
717 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
718 	    VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
719 	/* FALLTHROUGH */
720     case 0:					/* change position */
721 	bp->flags |= VSTREAM_FLAG_READ;
722 	if (bp->flags & VSTREAM_FLAG_DOUBLE) {
723 	    VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
724 	    if (bp->cnt < 0)
725 		return (0);
726 	}
727 	/* FALLTHROUGH */
728     case VSTREAM_FLAG_READ:			/* no change */
729 	break;
730     case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
731 	msg_panic("%s: read/write stream", myname);
732     }
733 
734     /*
735      * If this is the first GET operation, allocate a buffer. Late buffer
736      * allocation gives the application a chance to override the default
737      * buffering policy.
738      */
739     if (bp->len < stream->req_bufsize)
740 	vstream_buf_alloc(bp, stream->req_bufsize);
741 
742     /*
743      * If the stream is double-buffered and the write buffer is not empty,
744      * this is the time to flush the write buffer. Delayed flushes reduce
745      * system call overhead, and on TCP sockets, avoid triggering Nagle's
746      * algorithm.
747      */
748     if ((bp->flags & VSTREAM_FLAG_DOUBLE)
749 	&& stream->write_buf.len > stream->write_buf.cnt)
750 	if (vstream_fflush_delayed(stream))
751 	    return (VSTREAM_EOF);
752 
753     /*
754      * Did we receive an EOF indication?
755      */
756     if (bp->flags & VSTREAM_FLAG_EOF)
757 	return (VSTREAM_EOF);
758 
759     /*
760      * Fill the buffer with as much data as we can handle, or with as much
761      * data as is available right now, whichever is less. Update the cached
762      * file seek position, if any.
763      */
764     switch (n = stream->read_fn(stream->fd, bp->data, bp->len, stream->timeout, stream->context)) {
765     case -1:
766 	bp->flags |= VSTREAM_FLAG_ERR;
767 	if (errno == ETIMEDOUT)
768 	    bp->flags |= VSTREAM_FLAG_TIMEOUT;
769 	return (VSTREAM_EOF);
770     case 0:
771 	bp->flags |= VSTREAM_FLAG_EOF;
772 	return (VSTREAM_EOF);
773     default:
774 	if (stream->timeout)
775 	    GETTIMEOFDAY(&stream->iotime);
776 	if (msg_verbose > 2)
777 	    msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n);
778 	bp->cnt = -n;
779 	bp->ptr = bp->data;
780 	if (bp->flags & VSTREAM_FLAG_SEEK)
781 	    stream->offset += n;
782 	return (0);
783     }
784 }
785 
786 /* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */
787 
788 static int vstream_buf_put_ready(VBUF *bp)
789 {
790     VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
791     const char *myname = "vstream_buf_put_ready";
792 
793     /*
794      * Sanity checks. Detect a change of I/O direction or position. If so,
795      * discard unread input, and reset the buffer to the beginning.
796      */
797     switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
798     case VSTREAM_FLAG_READ:			/* change direction */
799 	bp->flags &= ~VSTREAM_FLAG_READ;
800 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
801 	    VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
802 	/* FALLTHROUGH */
803     case 0:					/* change position */
804 	bp->flags |= VSTREAM_FLAG_WRITE;
805 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
806 	    VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
807 	else
808 	    VSTREAM_BUF_AT_START(bp);
809 	/* FALLTHROUGH */
810     case VSTREAM_FLAG_WRITE:			/* no change */
811 	break;
812     case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
813 	msg_panic("%s: read/write stream", myname);
814     }
815 
816     /*
817      * Remember the direction. If this is the first PUT operation for this
818      * stream or if the buffer is smaller than the requested size, allocate a
819      * new buffer; obviously there is no data to be flushed yet. Otherwise,
820      * flush the buffer.
821      */
822     if (bp->len < stream->req_bufsize) {
823 	vstream_buf_alloc(bp, stream->req_bufsize);
824     } else if (bp->cnt <= 0) {
825 	if (VSTREAM_FFLUSH_SOME(stream))
826 	    return (VSTREAM_EOF);
827     }
828     return (0);
829 }
830 
831 /* vstream_buf_space - reserve space ahead of time */
832 
833 static int vstream_buf_space(VBUF *bp, ssize_t want)
834 {
835     VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
836     ssize_t used;
837     ssize_t incr;
838     ssize_t shortage;
839     const char *myname = "vstream_buf_space";
840 
841     /*
842      * Sanity checks. Reserving space implies writing. It is illegal to write
843      * to a read-only stream. Detect a change of I/O direction or position.
844      * If so, reset the buffer to the beginning.
845      */
846     if (bp->put_ready == 0)
847 	msg_panic("%s: read-only stream", myname);
848     switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
849     case VSTREAM_FLAG_READ:			/* change direction */
850 	bp->flags &= ~VSTREAM_FLAG_READ;
851 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
852 	    VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
853 	/* FALLTHROUGH */
854     case 0:					/* change position */
855 	bp->flags |= VSTREAM_FLAG_WRITE;
856 	if (bp->flags & VSTREAM_FLAG_DOUBLE)
857 	    VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
858 	else
859 	    VSTREAM_BUF_AT_START(bp);
860 	/* FALLTHROUGH */
861     case VSTREAM_FLAG_WRITE:			/* no change */
862 	break;
863     case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
864 	msg_panic("%s: read/write stream", myname);
865     }
866 
867     /*
868      * See if enough space is available. If not, flush a multiple of
869      * VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of
870      * VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt
871      * to keep file updates block-aligned for better performance.
872      */
873 #define VSTREAM_TRUNCATE(count, base)	(((count) / (base)) * (base))
874 #define VSTREAM_ROUNDUP(count, base)	VSTREAM_TRUNCATE(count + base - 1, base)
875 
876     if (want > bp->cnt) {
877 	if ((used = bp->len - bp->cnt) > stream->req_bufsize)
878 	    if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize)))
879 		return (VSTREAM_EOF);
880 	if ((shortage = (want - bp->cnt)) > 0) {
881 	    if ((bp->flags & VSTREAM_FLAG_FIXED)
882 		|| shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) {
883 		bp->flags |= VSTREAM_FLAG_ERR;
884 	    } else {
885 		incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize);
886 		vstream_buf_alloc(bp, bp->len + incr);
887 	    }
888 	}
889     }
890     return (vstream_ferror(stream) ? VSTREAM_EOF : 0);	/* mmap() may fail */
891 }
892 
893 /* vstream_fpurge - discard unread or unwritten content */
894 
895 int     vstream_fpurge(VSTREAM *stream, int direction)
896 {
897     const char *myname = "vstream_fpurge";
898     VBUF   *bp = &stream->buf;
899 
900 #define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \
901 	VSTREAM_BUF_AT_START((b))
902 #define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \
903 	VSTREAM_BUF_AT_END((b))
904 
905     /*
906      * To discard all unread contents, position the read buffer at its end,
907      * so that we skip over any unread data, and so that the next read
908      * operation will refill the buffer.
909      *
910      * To discard all unwritten content, position the write buffer at its
911      * beginning, so that the next write operation clobbers any unwritten
912      * data.
913      */
914     switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) {
915     case VSTREAM_FLAG_READ_DOUBLE:
916 	VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
917 	/* FALLTHROUGH */
918     case VSTREAM_FLAG_READ:
919 	VSTREAM_MAYBE_PURGE_READ(direction, bp);
920 	break;
921     case VSTREAM_FLAG_DOUBLE:
922 	VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
923 	VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
924 	break;
925     case VSTREAM_FLAG_WRITE_DOUBLE:
926 	VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
927 	/* FALLTHROUGH */
928     case VSTREAM_FLAG_WRITE:
929 	VSTREAM_MAYBE_PURGE_WRITE(direction, bp);
930 	break;
931     case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE:
932     case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
933 	msg_panic("%s: read/write stream", myname);
934     }
935 
936     /*
937      * Invalidate the cached file seek position.
938      */
939     bp->flags &= ~VSTREAM_FLAG_SEEK;
940     stream->offset = 0;
941 
942     return (0);
943 }
944 
945 /* vstream_fseek - change I/O position */
946 
947 off_t   vstream_fseek(VSTREAM *stream, off_t offset, int whence)
948 {
949     const char *myname = "vstream_fseek";
950     VBUF   *bp = &stream->buf;
951 
952     /*
953      * Flush any unwritten output. Discard any unread input. Position the
954      * buffer at the end, so that the next GET or PUT operation triggers a
955      * buffer boundary action.
956      */
957     switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
958     case VSTREAM_FLAG_WRITE:
959 	if (bp->ptr > bp->data) {
960 	    if (whence == SEEK_CUR)
961 		offset += (bp->ptr - bp->data);	/* add unwritten data */
962 	    else if (whence == SEEK_END)
963 		bp->flags &= ~VSTREAM_FLAG_SEEK;
964 	    if (VSTREAM_FFLUSH_SOME(stream))
965 		return (-1);
966 	}
967 	VSTREAM_BUF_AT_END(bp);
968 	break;
969     case VSTREAM_FLAG_READ:
970 	if (whence == SEEK_CUR)
971 	    offset += bp->cnt;			/* subtract unread data */
972 	else if (whence == SEEK_END)
973 	    bp->flags &= ~VSTREAM_FLAG_SEEK;
974 	/* FALLTHROUGH */
975     case 0:
976 	VSTREAM_BUF_AT_END(bp);
977 	break;
978     case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
979 	msg_panic("%s: read/write stream", myname);
980     }
981 
982     /*
983      * Clear the read/write flags to inform the buffer boundary action
984      * routines that we may have changed I/O position.
985      */
986     bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE);
987 
988     /*
989      * Shave an unnecessary system call.
990      */
991     if (bp->flags & VSTREAM_FLAG_NSEEK) {
992 	errno = ESPIPE;
993 	return (-1);
994     }
995 
996     /*
997      * Update the cached file seek position.
998      */
999     if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) {
1000 	bp->flags |= VSTREAM_FLAG_NSEEK;
1001     } else {
1002 	bp->flags |= VSTREAM_FLAG_SEEK;
1003     }
1004     bp->flags &= ~VSTREAM_FLAG_EOF;
1005     return (stream->offset);
1006 }
1007 
1008 /* vstream_ftell - return file offset */
1009 
1010 off_t   vstream_ftell(VSTREAM *stream)
1011 {
1012     VBUF   *bp = &stream->buf;
1013 
1014     /*
1015      * Shave an unnecessary syscall.
1016      */
1017     if (bp->flags & VSTREAM_FLAG_NSEEK) {
1018 	errno = ESPIPE;
1019 	return (-1);
1020     }
1021 
1022     /*
1023      * Use the cached file offset when available. This is the offset after
1024      * the last read, write or seek operation.
1025      */
1026     if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) {
1027 	if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) {
1028 	    bp->flags |= VSTREAM_FLAG_NSEEK;
1029 	    return (-1);
1030 	}
1031 	bp->flags |= VSTREAM_FLAG_SEEK;
1032     }
1033 
1034     /*
1035      * If this is a read buffer, subtract the number of unread bytes from the
1036      * cached offset. Remember that read counts are negative.
1037      */
1038     if (bp->flags & VSTREAM_FLAG_READ)
1039 	return (stream->offset + bp->cnt);
1040 
1041     /*
1042      * If this is a write buffer, add the number of unwritten bytes to the
1043      * cached offset.
1044      */
1045     if (bp->flags & VSTREAM_FLAG_WRITE)
1046 	return (stream->offset + (bp->ptr - bp->data));
1047 
1048     /*
1049      * Apparently, this is a new buffer, or a buffer after seek, so there is
1050      * no need to account for unread or unwritten data.
1051      */
1052     return (stream->offset);
1053 }
1054 
1055 /* vstream_fdopen - add buffering to pre-opened stream */
1056 
1057 VSTREAM *vstream_fdopen(int fd, int flags)
1058 {
1059     VSTREAM *stream;
1060 
1061     /*
1062      * Sanity check.
1063      */
1064     if (fd < 0)
1065 	msg_panic("vstream_fdopen: bad file %d", fd);
1066 
1067     /*
1068      * Initialize buffers etc. but do as little as possible. Late buffer
1069      * allocation etc. gives the application a chance to override default
1070      * policies. Either this, or the vstream*open() routines would have to
1071      * have a really ugly interface with lots of mostly-unused arguments (can
1072      * you say VMS?).
1073      */
1074     stream = (VSTREAM *) mymalloc(sizeof(*stream));
1075     stream->fd = fd;
1076     stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_FN) timed_read : 0;
1077     stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_FN) timed_write : 0;
1078     vstream_buf_init(&stream->buf, flags);
1079     stream->offset = 0;
1080     stream->path = 0;
1081     stream->pid = 0;
1082     stream->waitpid_fn = 0;
1083     stream->timeout = 0;
1084     stream->context = 0;
1085     stream->jbuf = 0;
1086     stream->iotime.tv_sec = stream->iotime.tv_usec = 0;
1087     stream->req_bufsize = VSTREAM_BUFSIZE;
1088     return (stream);
1089 }
1090 
1091 /* vstream_fopen - open buffered file stream */
1092 
1093 VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode)
1094 {
1095     VSTREAM *stream;
1096     int     fd;
1097 
1098     if ((fd = open(path, flags, mode)) < 0) {
1099 	return (0);
1100     } else {
1101 	stream = vstream_fdopen(fd, flags);
1102 	stream->path = mystrdup(path);
1103 	return (stream);
1104     }
1105 }
1106 
1107 /* vstream_fflush - flush write buffer */
1108 
1109 int     vstream_fflush(VSTREAM *stream)
1110 {
1111     if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE)
1112 	== VSTREAM_FLAG_READ_DOUBLE
1113 	&& stream->write_buf.len > stream->write_buf.cnt)
1114 	vstream_fflush_delayed(stream);
1115     return (VSTREAM_FFLUSH_SOME(stream));
1116 }
1117 
1118 /* vstream_fclose - close buffered stream */
1119 
1120 int     vstream_fclose(VSTREAM *stream)
1121 {
1122     int     err;
1123 
1124     /*
1125      * NOTE: Negative file descriptors are not part of the external
1126      * interface. They are for internal use only, in order to support
1127      * vstream_fdclose() without a lot of code duplication. Applications that
1128      * rely on negative VSTREAM file descriptors will break without warning.
1129      */
1130     if (stream->pid != 0)
1131 	msg_panic("vstream_fclose: stream has process");
1132     if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0 && stream->fd >= 0)
1133 	vstream_fflush(stream);
1134     /* Do not remove: vstream_fdclose() depends on this error test. */
1135     err = vstream_ferror(stream);
1136     if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
1137 	if (stream->read_fd >= 0)
1138 	    err |= close(stream->read_fd);
1139 	if (stream->write_fd != stream->read_fd)
1140 	    if (stream->write_fd >= 0)
1141 		err |= close(stream->write_fd);
1142 	vstream_buf_wipe(&stream->read_buf);
1143 	vstream_buf_wipe(&stream->write_buf);
1144 	stream->buf = stream->read_buf;
1145     } else {
1146 	if (stream->fd >= 0)
1147 	    err |= close(stream->fd);
1148 	vstream_buf_wipe(&stream->buf);
1149     }
1150     if (stream->path)
1151 	myfree(stream->path);
1152     if (stream->jbuf)
1153 	myfree((char *) stream->jbuf);
1154     if (!VSTREAM_STATIC(stream))
1155 	myfree((char *) stream);
1156     return (err ? VSTREAM_EOF : 0);
1157 }
1158 
1159 /* vstream_fdclose - close stream, leave file(s) open */
1160 
1161 int     vstream_fdclose(VSTREAM *stream)
1162 {
1163 
1164     /*
1165      * Flush unwritten output, just like vstream_fclose(). Errors are
1166      * reported by vstream_fclose().
1167      */
1168     if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
1169 	(void) vstream_fflush(stream);
1170 
1171     /*
1172      * NOTE: Negative file descriptors are not part of the external
1173      * interface. They are for internal use only, in order to support
1174      * vstream_fdclose() without a lot of code duplication. Applications that
1175      * rely on negative VSTREAM file descriptors will break without warning.
1176      */
1177     if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
1178 	stream->fd = stream->read_fd = stream->write_fd = -1;
1179     } else {
1180 	stream->fd = -1;
1181     }
1182     return (vstream_fclose(stream));
1183 }
1184 
1185 /* vstream_printf - formatted print to stdout */
1186 
1187 VSTREAM *vstream_printf(const char *fmt,...)
1188 {
1189     VSTREAM *stream = VSTREAM_OUT;
1190     va_list ap;
1191 
1192     va_start(ap, fmt);
1193     vbuf_print(&stream->buf, fmt, ap);
1194     va_end(ap);
1195     return (stream);
1196 }
1197 
1198 /* vstream_fprintf - formatted print to buffered stream */
1199 
1200 VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...)
1201 {
1202     va_list ap;
1203 
1204     va_start(ap, fmt);
1205     vbuf_print(&stream->buf, fmt, ap);
1206     va_end(ap);
1207     return (stream);
1208 }
1209 
1210 /* vstream_fputs - write string to stream */
1211 
1212 int     vstream_fputs(const char *str, VSTREAM *stream)
1213 {
1214     int     ch;
1215 
1216     while ((ch = *str++) != 0)
1217 	if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF)
1218 	    return (VSTREAM_EOF);
1219     return (0);
1220 }
1221 
1222 /* vstream_control - fine control */
1223 
1224 void    vstream_control(VSTREAM *stream, int name,...)
1225 {
1226     const char *myname = "vstream_control";
1227     va_list ap;
1228     int     floor;
1229     int     old_fd;
1230     ssize_t req_bufsize = 0;
1231     VSTREAM *stream2;
1232 
1233 #define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0)
1234 
1235     for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
1236 	switch (name) {
1237 	case VSTREAM_CTL_READ_FN:
1238 	    stream->read_fn = va_arg(ap, VSTREAM_FN);
1239 	    break;
1240 	case VSTREAM_CTL_WRITE_FN:
1241 	    stream->write_fn = va_arg(ap, VSTREAM_FN);
1242 	    break;
1243 	case VSTREAM_CTL_CONTEXT:
1244 	    stream->context = va_arg(ap, char *);
1245 	    break;
1246 	case VSTREAM_CTL_PATH:
1247 	    if (stream->path)
1248 		myfree(stream->path);
1249 	    stream->path = mystrdup(va_arg(ap, char *));
1250 	    break;
1251 	case VSTREAM_CTL_DOUBLE:
1252 	    if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) {
1253 		stream->buf.flags |= VSTREAM_FLAG_DOUBLE;
1254 		if (stream->buf.flags & VSTREAM_FLAG_READ) {
1255 		    VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
1256 		    VSTREAM_FORK_STATE(stream, write_buf, write_fd);
1257 		} else {
1258 		    VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
1259 		    VSTREAM_FORK_STATE(stream, read_buf, read_fd);
1260 		}
1261 	    }
1262 	    break;
1263 	case VSTREAM_CTL_READ_FD:
1264 	    if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
1265 		msg_panic("VSTREAM_CTL_READ_FD requires double buffering");
1266 	    stream->read_fd = va_arg(ap, int);
1267 	    stream->buf.flags |= VSTREAM_FLAG_NSEEK;
1268 	    break;
1269 	case VSTREAM_CTL_WRITE_FD:
1270 	    if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
1271 		msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering");
1272 	    stream->write_fd = va_arg(ap, int);
1273 	    stream->buf.flags |= VSTREAM_FLAG_NSEEK;
1274 	    break;
1275 	case VSTREAM_CTL_SWAP_FD:
1276 	    stream2 = va_arg(ap, VSTREAM *);
1277 	    if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE)
1278 		!= (stream2->buf.flags & VSTREAM_FLAG_DOUBLE))
1279 		msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between "
1280 			  "single-buffered and double-buffered streams");
1281 	    if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
1282 		SWAP(int, stream->read_fd, stream2->read_fd);
1283 		SWAP(int, stream->write_fd, stream2->write_fd);
1284 		stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ?
1285 			      stream->write_fd : stream->read_fd);
1286 	    } else {
1287 		SWAP(int, stream->fd, stream2->fd);
1288 	    }
1289 	    break;
1290 	case VSTREAM_CTL_TIMEOUT:
1291 	    if (stream->timeout == 0)
1292 		GETTIMEOFDAY(&stream->iotime);
1293 	    stream->timeout = va_arg(ap, int);
1294 	    break;
1295 	case VSTREAM_CTL_EXCEPT:
1296 	    if (stream->jbuf == 0)
1297 		stream->jbuf =
1298 		    (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF));
1299 	    break;
1300 
1301 #ifdef VSTREAM_CTL_DUPFD
1302 
1303 #define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \
1304 	if (((backup) = (fd)) < floor) { \
1305 	    if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \
1306 		msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \
1307 	    (void) close(backup); \
1308 	} \
1309     } while (0)
1310 
1311 	case VSTREAM_CTL_DUPFD:
1312 	    floor = va_arg(ap, int);
1313 	    if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
1314 		VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor);
1315 		if (stream->write_fd == old_fd)
1316 		    stream->write_fd = stream->read_fd;
1317 		else
1318 		    VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor);
1319 		stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ?
1320 		    stream->read_fd : stream->write_fd;
1321 	    } else {
1322 		VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor);
1323 	    }
1324 	    break;
1325 #endif
1326 
1327 	    /*
1328 	     * Postpone memory (re)allocation until the space is needed.
1329 	     */
1330 	case VSTREAM_CTL_BUFSIZE:
1331 	    req_bufsize = va_arg(ap, ssize_t);
1332 	    if (req_bufsize < 0)
1333 		msg_panic("VSTREAM_CTL_BUFSIZE with negative size: %ld",
1334 			  (long) req_bufsize);
1335 	    if (stream != VSTREAM_ERR
1336 		&& req_bufsize > stream->req_bufsize)
1337 		stream->req_bufsize = req_bufsize;
1338 	    break;
1339 	default:
1340 	    msg_panic("%s: bad name %d", myname, name);
1341 	}
1342     }
1343     va_end(ap);
1344 }
1345 
1346 /* vstream_vfprintf - formatted print engine */
1347 
1348 VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap)
1349 {
1350     vbuf_print(&vp->buf, format, ap);
1351     return (vp);
1352 }
1353 
1354 /* vstream_bufstat - get stream buffer status */
1355 
1356 ssize_t vstream_bufstat(VSTREAM *vp, int command)
1357 {
1358     VBUF   *bp;
1359 
1360     switch (command & VSTREAM_BST_MASK_DIR) {
1361     case VSTREAM_BST_FLAG_IN:
1362 	if (vp->buf.flags & VSTREAM_FLAG_READ) {
1363 	    bp = &vp->buf;
1364 	} else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
1365 	    bp = &vp->read_buf;
1366 	} else {
1367 	    bp = 0;
1368 	}
1369 	switch (command & ~VSTREAM_BST_MASK_DIR) {
1370 	case VSTREAM_BST_FLAG_PEND:
1371 	    return (bp ? -bp->cnt : 0);
1372 	    /* Add other requests below. */
1373 	}
1374 	break;
1375     case VSTREAM_BST_FLAG_OUT:
1376 	if (vp->buf.flags & VSTREAM_FLAG_WRITE) {
1377 	    bp = &vp->buf;
1378 	} else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
1379 	    bp = &vp->write_buf;
1380 	} else {
1381 	    bp = 0;
1382 	}
1383 	switch (command & ~VSTREAM_BST_MASK_DIR) {
1384 	case VSTREAM_BST_FLAG_PEND:
1385 	    return (bp ? bp->len - bp->cnt : 0);
1386 	    /* Add other requests below. */
1387 	}
1388 	break;
1389     }
1390     msg_panic("vstream_bufstat: unknown command: %d", command);
1391 }
1392 
1393 #undef vstream_peek			/* API binary compatibility. */
1394 
1395 /* vstream_peek - peek at a stream */
1396 
1397 ssize_t vstream_peek(VSTREAM *vp)
1398 {
1399     if (vp->buf.flags & VSTREAM_FLAG_READ) {
1400 	return (-vp->buf.cnt);
1401     } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
1402 	return (-vp->read_buf.cnt);
1403     } else {
1404 	return (0);
1405     }
1406 }
1407