Line data Source code
1 : #ifndef HEADER_fd_src_util_io_fd_io_h
2 : #define HEADER_fd_src_util_io_fd_io_h
3 :
4 : /* API for platform agnostic high performance stream I/O. Summary:
5 :
6 : Read at least min bytes directly from stream fd into size max buffer
7 : buf (1<=min<=max):
8 :
9 : ulong rsz; int err = fd_io_read( fd, buf, min, max, &rsz );
10 : if ( FD_LIKELY( err==0 ) ) ... success, rsz in [min,max], buf updated
11 : else if( FD_LIKELY( err< 0 ) ) ... EOF encountered, rsz is [0,min), buf updated
12 : else ... I/O error, rsz is zero, buf clobbered
13 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
14 : ... fd should be considered failed
15 :
16 : This usage will block (even if the stream is non-blocking) until min
17 : bytes are read, EOF is encountered or there is an I/O error. As
18 : such, the min==1 case behaves like a POSIX read on a blocking stream.
19 : The min==max case will read an exact number of bytes from the stream
20 : and return an error if this is not possible. The 1<min<max case is
21 : useful for implementing buffered I/O.
22 :
23 : A non-blocking read on a non-blocking stream is possible by setting
24 : min==0:
25 :
26 : ulong rsz; int err = fd_io_read( fd, buf, 0UL, max, &rsz );
27 : if ( FD_LIKELY( err==0 ) ) ... success, rsz in [1,max], buf updated
28 : else if( FD_LIKELY( err==EAGAIN ) ) ... try again later, rsz is zero, buf unchanged
29 : else if( FD_LIKELY( err< 0 ) ) ... EOF encountered, rsz is zero, buf unchanged
30 : else ... I/O error, rsz is zero, buf clobbered
31 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
32 : ... fd should be considered failed
33 :
34 : (If min==0 and stream fd is blocking, the EAGAIN case will never
35 : occur.)
36 :
37 : Write is nearly symmetrical to read.
38 :
39 : Write at least min byte and at most max bytes from buf into stream fd
40 : (1<=min<=max):
41 :
42 : ulong wsz; int err = fd_io_write( fd, buf, min, max, &wsz );
43 : if( FD_LIKELY( err==0 ) ) ... success, wsz in [min,max]
44 : else ... I/O error, wsz is zero, how much of buf was streamed before the error is unknown
45 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
46 : ... fd should be considered failed
47 :
48 : This usage will block (even if the stream is non-blocking) until min
49 : bytes are written or there is an I/O error. As such, the min==1 case
50 : behaves like a POSIX write on a stream. The min==max case will write
51 : an exact number of bytes to fd and give an error if this is not
52 : possible. The 1<min<max case is useful for implementing buffered
53 : I/O.
54 :
55 : A non-blocking write on a non-blocking stream is possible by setting
56 : min==0:
57 :
58 : ulong wsz; int err = fd_io_write( fd, buf, 0UL, max, &wsz );
59 : if ( FD_LIKELY( err==0 ) ) ... success, wsz in [1,max]
60 : else if( FD_LIKELY( err==EAGAIN ) ) ... try again later, wsz is zero
61 : else ... I/O error, wsz is zero, how much of buf was streamed before the error is unknown
62 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
63 : ... fd should be considered failed
64 :
65 : (If min==0 and stream fd is blocking, the EAGAIN case will never
66 : occur.)
67 :
68 : Each call above typically requires at least one system call. This
69 : can be inefficient if doing lots of tiny reads and writes. For
70 : higher performance usage in cases like this, buffered I/O APIs are
71 : also provided.
72 :
73 : Buffered reads:
74 :
75 : ... setup buffered reads from stream fd using the rbuf_sz size
76 : ... buffer rbuf (rbuf_sz>0) as the read buffer
77 :
78 : fd_io_buffered_istream_t in[1];
79 : fd_io_buffered_istream_init( in, fd, rbuf, rbuf_sz );
80 : ... in is initialized and has ownership of fd and rbuf
81 :
82 : ... accessors (these return the values used to init in)
83 :
84 : int fd = fd_io_buffered_istream_fd ( in );
85 : void * rbuf = fd_io_buffered_istream_rbuf ( in );
86 : ulong rbuf_sz = fd_io_buffered_istream_rbuf_sz( in );
87 :
88 : ... read sz bytes from a buffered stream
89 :
90 : int err = fd_io_buffered_istream_read( in, buf, sz );
91 : if ( FD_LIKELY( err==0 ) ) ... success, buf holds the next sz bytes of stream
92 : else if( FD_LIKELY( err< 0 ) ) ... EOF before sz bytes could be read, buf clobbered
93 : else ... I/O error, buf clobbered
94 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
95 : ... in and fd should be considered failed
96 :
97 : ... skip sz bytes in a buffered stream
98 :
99 : int err = fd_io_buffered_istream_skip( in, sz );
100 : if ( FD_LIKELY( err==0 ) ) ... success, sz bytes skipped
101 : else if( FD_LIKELY( err< 0 ) ) ... EOF before sz bytes could be skipped
102 : else ... I/O error
103 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
104 : ... in and fd should be considered failed
105 :
106 : ... zero-copy read bytes from a buffered stream
107 :
108 : ulong peek_sz = fd_io_buffered_istream_peek_sz( in ); ... returns number of bytes currently buffered
109 : void const * peek = fd_io_buffered_istream_peek ( in ); ... returns location of current buffered bytes
110 :
111 : fd_io_buffered_istream_seek( in, sz ); ... consume sz currently buffered bytes, sz in [0,peek_sz]
112 :
113 : ... read buffering control
114 :
115 : int err = fd_io_buffered_istream_fetch( in );
116 : if ( FD_LIKELY( err==0 ) ) ... success, peek_sz updated to at most rbuf_sz
117 : else if( FD_LIKELY( err< 0 ) ) ... end-of-file, peek_sz updated to unconsumed bytes remaining (at most rbuf_sz)
118 : else if( FD_LIKELY( err==EAGAIN ) ) ... try again later, peek_sz unchanged
119 : else ... I/O error, peek_sz unchanged
120 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
121 : ... in and fd should be considered failed
122 :
123 : (The EAGAIN case only applies for a non-blocking stream.)
124 :
125 : TODO: consider option to do block until a min level?
126 :
127 : ... finish using buffered stream
128 :
129 : fd_io_buffered_istream_fini( in );
130 : ... in is not in use and no longer has ownership of fd and rbuf.
131 : ... IMPORTANT! buffering might have pushed fd's file offset beyond
132 : ... the bytes the user has actually consumed. It is the user's
133 : ... responsibility for handling this. (Usually nothing needs to be
134 : ... or can be done as either the next operation is to close fd or
135 : ... the fd does not support seeking.)
136 :
137 : There are nearly symmetric APIs for buffered writes.
138 :
139 : ... start buffered writing to stream fd using the wbuf_sz size
140 : ... buffer wbuf (wbuf_sz>0) as the write buffer
141 :
142 : fd_io_buffered_ostream_t out[1];
143 : fd_io_buffered_ostream_init( out, fd, wbuf, wbuf_sz );
144 : ... out is initialized and has ownership of fd and wbuf
145 :
146 : ... accessors (these return the values used to init in)
147 :
148 : int fd = fd_io_buffered_ostream_fd ( out );
149 : void * wbuf = fd_io_buffered_ostream_wbuf ( out );
150 : ulong wbuf_sz = fd_io_buffered_ostream_wbuf_sz( out );
151 :
152 : ... write sz bytes to a buffered stream
153 :
154 : int err = fd_io_buffered_ostream_write( out, buf, sz );
155 : if( FD_LIKELY( err==0 ) ) ... success, sz bytes from have been written from the caller's POV
156 : else ... I/O error, err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
157 : ... out and fd should be considered failed
158 :
159 : ... zero-copy write bytes to a buffered stream
160 :
161 : ulong peek_sz = fd_io_buffered_ostream_peek_sz( out ); ... returns amount of unused write buffer space, in [0,wbuf_sz]
162 : void * peek = fd_io_buffered_ostream_peek ( out ); ... returns location of unused write buffer space
163 :
164 : fd_io_buffered_ostream_seek( in, sz ); ... commit sz unused bytes of write buffer, sz in [0,peek_sz]
165 :
166 : ... write buffer control
167 :
168 : int err = fd_io_buffered_ostream_flush( out );
169 : if( FD_LIKELY( err==0 ) ) ... success, all buffered bytes have been drained to fd
170 : else ... I/O error, err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
171 : ... out and fd should be considered failed
172 :
173 : (This will block even for a non-blocking stream.)
174 :
175 : ... finish using buffered stream
176 :
177 : fd_io_buffered_ostream_fini( out );
178 : ... out is not in use and no longer has ownership of fd and wbuf.
179 : ... IMPORTANT! fini does not do any flushing of buffered writes (as
180 : ... such fini is guaranteed to always succeed, can be applied to
181 : ... out's that have failed, etc). It is the users responsibility
182 : ... to do any final flush before calling fini.
183 :
184 : More details below. */
185 :
186 : #include "../bits/fd_bits.h"
187 :
188 : /* fd_io_buffered_{istream,ostream}_t is an opaque handle of an
189 : {input,output} stream with buffered {reads,writes}. The internals
190 : are visible here to facilitate inlining various operations. This is
191 : declaration friendly (e.g. usually should just do
192 : "fd_io_buffered_istream_t in[1];" on the stack to get a suitable
193 : memory region for the stream state). These are not meant to be
194 : persistent or shared IPC. */
195 :
196 : struct fd_io_buffered_istream_private {
197 : int fd; /* Open normal-ish file descriptor of stream */
198 : uchar * rbuf; /* Read buffer, non-NULL, indexed [0,rbuf_sz), arb alignment */
199 : ulong rbuf_sz; /* Read buffer size, positive */
200 : ulong rbuf_lo; /* Buf bytes [0,rbuf_lo) have already been consumed */
201 : ulong rbuf_ready; /* Number of buffered byte that haven't been consumed, 0<=rbuf_lo<=(rbuf_lo+rbuf_ready)<=rbuf_sz */
202 : };
203 :
204 : typedef struct fd_io_buffered_istream_private fd_io_buffered_istream_t;
205 :
206 : struct fd_io_buffered_ostream_private {
207 : int fd; /* Open normal-ish file descriptor of stream */
208 : uchar * wbuf; /* Write buffer, non-NULL, indexed [0,wbuf_sz), arb alignment */
209 : ulong wbuf_sz; /* Write buffer size, positive */
210 : ulong wbuf_used; /* Number buffered bytes that haven't been written to fd, in [0,wbuf_sz] */
211 : };
212 :
213 : typedef struct fd_io_buffered_ostream_private fd_io_buffered_ostream_t;
214 :
215 : FD_PROTOTYPES_BEGIN
216 :
217 : /* fd_io_read streams at least dst_min bytes from the given file
218 : descriptor into the given memory region. fd should be an open
219 : normal-ish file descriptor (it is okay for fd to be non-blocking).
220 : dst points in the caller's address space with arbitrary alignment to
221 : the first byte of the dst_max byte memory region to use (assumes dst
222 : non-NULL, and dst_min is at most dst_max). The caller should not
223 : read or write this region during the call and no interest in dst is
224 : retained on return. If dst_min is 0, this will try to read dst_max
225 : from the stream exactly once. If dst_max is 0, is a no-op.
226 :
227 : Returns 0 on success. On success, *_dst_sz will be the number of
228 : bytes read into dst. Will be in [dst_min,dst_max].
229 :
230 : Returns a negative number if end-of-file was encountered before
231 : reading dst_min bytes. *_dst_sz will be the number bytes read into
232 : dst when the end-of-file was encountered. Will be in [0,dst_min).
233 :
234 : Returns an errno compatible error code on failure (note that all
235 : errnos are positive). If errno is anything other than EAGAIN, the
236 : underlying fd should be considered to be in a failed state such that
237 : the only valid operation on fd is to close it. *_dst_sz will be zero
238 : and the contents of dst will be undefined. This API fixes up the
239 : POSIX glitches around EWOULDBLOCK / EAGAIN: if the underlying target
240 : has EWOULDBLOCK different from EAGAIN and read uses EWOULDBLOCK
241 : instead of EAGAIN, this will still just return EAGAIN. EAGAIN will
242 : only be returned if the underlying fd is non-blocking and dst_min is
243 : zero.
244 :
245 : TL;DR
246 :
247 : - dst_min is positive:
248 :
249 : ulong dst_sz; int err = fd_io_read( fd, dst, dst_min, dst_max, &dst_sz );
250 : if ( FD_LIKELY( err==0 ) ) ... success, dst_sz in [dst_min,dst_max], dst updated
251 : else if( FD_LIKELY( err< 0 ) ) ... EOF, dst_sz in [0,dst_min), dst updated
252 : else ... I/O error, dst_sz is zero, dst clobbered
253 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
254 : ... fd should be considered failed
255 :
256 : This is equivalent to looping over reads of up to dst_max in size
257 : from fd until at least dst_min bytes are read. It does not matter
258 : if fd is blocking or not.
259 :
260 : - dst_min is zero and fd is blocking:
261 :
262 : ulong dst_sz; int err = fd_io_read( fd, dst, dst_min, dst_max, &dst_sz );
263 : if ( FD_LIKELY( err==0 ) ) ... success, dst_sz in [1,dst_max], dst updated
264 : else if( FD_LIKELY( err< 0 ) ) ... EOF, dst_sz is zero, dst unchanged
265 : else ... I/O error, dst_sz is zero, dst clobbered
266 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
267 : ... fd should be considered failed
268 :
269 : This is equivalent to a single read of dst_max size on a blocking
270 : fd.
271 :
272 : - dst_min is zero and fd is non-blocking:
273 :
274 : ulong dst_sz; int err = fd_io_read( fd, dst, dst_min, dst_max, &dst_sz );
275 : if ( FD_LIKELY( err==0 ) ) ... success, dst_sz in [1,dst_max], dst updated
276 : else if( FD_LIKELY( err==EAGAIN ) ) ... no data available now, try again later, dst_sz is zero, dst unchanged
277 : else if( FD_LIKELY( err< 0 ) ) ... EOF, dst_sz is zero, dst unchanged
278 : else ... I/O error, dst_sz is zero, dst clobbered
279 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
280 : ... fd should be considered failed
281 :
282 : This is equivalent to a single read of dst_max size on a
283 : non-blocking fd (with the POSIX glitches around EAGAIN /
284 : EWOULDBLOCK cleaned up). */
285 :
286 : int
287 : fd_io_read( int fd,
288 : void * dst,
289 : ulong dst_min,
290 : ulong dst_max,
291 : ulong * _dst_sz );
292 :
293 : /* fd_io_write behaves virtually identical to fd_io_read but the
294 : direction of the transfer is from memory to the stream and there is
295 : no notion of EOF handling. Assumes src is non-NULL,
296 : src_min<=src_max, src_sz is non-NULL and non-overlapping with src.
297 : If src_max is 0, is a no-op. Summarizing:
298 :
299 : - src_min is positive:
300 :
301 : ulong src_sz; int err = fd_io_write( fd, src, src_min, src_max, &src_sz );
302 : if( FD_LIKELY( err==0 ) ) ... success, src_sz in [src_min,src_max]
303 : else ... I/O error, src_sz is zero
304 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
305 : ... fd should be considered failed
306 :
307 : This is equivalent to looping over writes of up to src_max in size
308 : to fd until at least src_min bytes are written. It does not matter
309 : if fd is blocking or not.
310 :
311 : - src_min is zero and fd is blocking:
312 :
313 : ulong src_sz; int err = fd_io_write( fd, src, src_min, src_max, &src_sz );
314 : if( FD_LIKELY( err==0 ) ) ... success, src_sz in [1,src_max]
315 : else ... I/O error, src_sz is zero
316 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
317 : ... fd should be considered failed
318 :
319 : This is equivalent to a single write of src_max size on a blocking
320 : fd.
321 :
322 : - src_min is zero and fd is non-blocking:
323 :
324 : ulong src_sz; int err = fd_io_write( fd, src, src_min, src_max, &src_sz );
325 : if ( FD_LIKELY( err==0 ) ) ... success, src_sz in [1,src_max]
326 : else if( FD_LIKELY( err==EAGAIN ) ) ... no bytes written, try again later, src_sz is zero
327 : else ... I/O error, src_sz is zero
328 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
329 : ... fd should be considered failed
330 :
331 : This is equivalent to a single write of src_max size on a
332 : non-blocking fd (with the POSIX glitches around EAGAIN /
333 : EWOULDBLOCK fixed up). */
334 :
335 : int
336 : fd_io_write( int fd,
337 : void const * src,
338 : ulong src_min,
339 : ulong src_max,
340 : ulong * _src_sz );
341 :
342 : /* fd_io_buffered_read is like fd_io_read but can consolidate many
343 : tiny reads into a larger fd_io_read via the given buffer. Unlike
344 : fd_io_read, dst NULL is okay if dst_sz is 0 (dst_sz 0 is a no-op that
345 : immediately returns success). Will block the caller until the read
346 : is complete or an end-of-file was encountered.
347 :
348 : rbuf points to the first byte of a rbuf_sz size memory region in the
349 : caller's address space used for read buffering (assumes rbuf is
350 : non-NULL with arbitrary alignment and rbuf_sz is positive). On entry
351 : *_rbuf_lo is where the first byte of unconsumed buffered reads are
352 : located and *_rbuf_ready is number of unconsumed buffered bytes.
353 : Assumes 0<=*_rbuf_lo<=(*_rbuf_lo+*_rbuf_ready)<=rbuf_sz.
354 :
355 : Returns 0 on success. dst will hold dst_sz bytes from the stream.
356 : *_rbuf_lo and *_rbuf_ready will be updated and the above invariant on
357 : *_rbuf_lo and *_rbuf_ready will still hold.
358 :
359 : Returns non-zero on failure. Failure indicates that dst_sz bytes
360 : could not be read because of an I/O error (return will be a positive
361 : errno compatible error code) or an end-of-file was encountered
362 : (return will be a negative number). dst should be assumed to have
363 : been clobbered, *_rbuf_lo and *_rbuf_ready will be zero. If an I/O
364 : error, the fd should be considered to be in a failed state such that
365 : the only valid operation on it is to close it.
366 :
367 : IMPORTANT! This function will only read from fd in multiples of
368 : rbuf_sz (except for a potentially last incomplete block before an
369 : end-of-file). This can be useful for various ultra high performance
370 : contexts.
371 :
372 : This API usually should not be used directly. It is mostly useful
373 : for implementing higher level APIs like fd_io_buffered_istream below. */
374 :
375 : int
376 : fd_io_buffered_read( int fd,
377 : void * dst,
378 : ulong dst_sz,
379 : void * rbuf,
380 : ulong rbuf_sz,
381 : ulong * _rbuf_lo,
382 : ulong * _rbuf_ready );
383 :
384 : /* fd_io_buffered_skip is like fd_io_buffered_read but will skip over
385 : skip_sz bytes of the stream without copying them into a user buffer.
386 : If stream fd is seekable (e.g. a normal file), this should be O(1).
387 : If not (e.g. fd is pipe / socket / stdin / etc), this will block the
388 : caller until skip_sz bytes have been skipped or an I/O error occurs.
389 :
390 : IMPORTANT! If stream fd is seekable, POSIX behaviors allow seeking
391 : past end-of-file (apparently even if fd is read only). Whether or
392 : not this is a good idea is debatable. The result though is this API
393 : will usually not return an error if skip_sz moves past the
394 : end-of-file (however, if skip_sz is so large that it causes the file
395 : offset to overflow, this will return EOVERFLOW). In particular, this
396 : API cannot be used to detect end-of-file.
397 :
398 : IMPORTANT! This function makes no effort to skip in multiples of
399 : rbuf_sz. Such is up to the caller to do if such is desirable.
400 :
401 : This API usually should not be used directly. It is mostly useful
402 : for implementing higher level APIs like fd_io_buffered_istream below. */
403 :
404 : int
405 : fd_io_buffered_skip( int fd,
406 : ulong skip_sz,
407 : void * rbuf,
408 : ulong rbuf_sz,
409 : ulong * _rbuf_lo,
410 : ulong * _rbuf_ready );
411 :
412 : /* fd_io_buffered_write is like fd_io_write but can consolidate many
413 : tiny writes into a larger fd_io_write via the given buffer. Unlike
414 : fd_io_write, src NULL is okay if src_sz is 0 (src_sz 0 is a no-op
415 : that immediately returns success). Will block the caller until the
416 : write is complete or an end-of-file was encountered.
417 :
418 : wbuf points to the first byte of a wbuf_sz size memory region in the
419 : caller's address space (assumes wbuf is non-NULL with arbitrary
420 : alignment and wbuf_sz is positive). On entry *_wbuf_used is the
421 : number of bytes in wbuf from previous buffered writes that have not
422 : yet been streamed out. Assumes *_wbuf_used is in [0,wbuf_sz].
423 :
424 : Returns 0 on success. wbuf will hold *_wbuf_used bytes not yet
425 : written to fd by write and/or previous buffered writes. The above
426 : invariant on *_wbuf_used will still hold.
427 :
428 : Returns non-zero on failure. Failure indicates that src_sz bytes
429 : could not be written because of an I/O error (return will be a
430 : positive errno compatible error code). fd should be considered to be
431 : in a failed state such that the only valid operation on it is to
432 : close it. *_wbuf_used will be 0 and the contents of wbuf will be
433 : undefined. Zero or more bytes of previously buffered writes and/or
434 : src might have been written before the failure.
435 :
436 : IMPORTANT! This function will only write to fd in multiples of
437 : wbuf_sz. This can be useful for various ultra high performance
438 : contexts.
439 :
440 : This API usually should not be used directly. It is mostly useful
441 : for implementing higher level APIs like fd_io_buffered_ostream below. */
442 :
443 : int
444 : fd_io_buffered_write( int fd,
445 : void const * src,
446 : ulong src_sz,
447 : void * wbuf,
448 : ulong wbuf_sz,
449 : ulong * _wbuf_used );
450 :
451 : /* fd_io_buffered_istream_init initializes in to do buffered reads from
452 : the given file descriptor. in is an unused location that should hold
453 : the buffering state, fd is an open normal-ish file descriptor, rbuf
454 : points to the first byte in the caller's address space to an unused
455 : rbuf_sz size memory region to use for read buffering (assumes rbuf is
456 : non-NULL with arbitrary alignment and rbuf_sz is positive). Returns
457 : in and on return in will be initialized. in will have ownership of
458 : fd and rbuf while initialized. */
459 :
460 : static inline fd_io_buffered_istream_t *
461 : fd_io_buffered_istream_init( fd_io_buffered_istream_t * in,
462 : int fd,
463 : void * rbuf,
464 339 : ulong rbuf_sz ) {
465 339 : in->fd = fd;
466 339 : in->rbuf = (uchar *)rbuf;
467 339 : in->rbuf_sz = rbuf_sz;
468 339 : in->rbuf_lo = 0UL;
469 339 : in->rbuf_ready = 0UL;
470 339 : return in;
471 339 : }
472 :
473 : /* fd_io_buffered_istream_{fd,rbuf,rbuf_sz} return the corresponding
474 : value used to initialize in. Assumes in is initialized. */
475 :
476 3 : FD_FN_PURE static inline int fd_io_buffered_istream_fd ( fd_io_buffered_istream_t const * in ) { return in->fd; }
477 3 : FD_FN_PURE static inline void * fd_io_buffered_istream_rbuf ( fd_io_buffered_istream_t const * in ) { return in->rbuf; }
478 3 : FD_FN_PURE static inline ulong fd_io_buffered_istream_rbuf_sz( fd_io_buffered_istream_t const * in ) { return in->rbuf_sz; }
479 :
480 : /* fd_io_buffered_istream_fini finalizes a buffered input stream.
481 : Assumes in is initialized. On return in will no longer be
482 : initialized and ownership the underlying fd and rbuf will return to
483 : the caller.
484 :
485 : IMPORTANT! THIS WILL NOT REPOSITION THE UNDERLYING FD FILE OFFSET
486 : (SUCH MIGHT NOT EVEN BE POSSIBLE) TO "UNREAD" ANY UNCONSUMED BUFFERED
487 : DATA. */
488 :
489 : static inline void
490 39 : fd_io_buffered_istream_fini( fd_io_buffered_istream_t * in ) {
491 39 : (void)in;
492 39 : }
493 :
494 : /* fd_io_buffered_istream_read reads dst_sz bytes from in to dst,
495 : reading ahead as convenient. Assumes in is initialized. dst /
496 : dst_sz have the same meaning / restrictions as fd_io_buffered_read.
497 : Returns 0 on success and non-zero on failure. Failure interpretation
498 : is the same as fd_io_buffered_read. On failure, in and the
499 : underlying file descriptor should be considered to be in a failed
500 : state (e.g. the only valid thing to do to in is fini and the only
501 : valid thing to do to fd is close).
502 :
503 : IMPORTANT! If fd_io_buffered_istream_{fetch,skip} below are never
504 : used (or only used to skip in multiplies of rbuf_sz), all the reads
505 : from the underlying stream will always be at multiples of rbuf_sz
506 : from the file offset when the in was initialized and a multiple of
507 : rbuf_sz in size (except possibly a final read to the end-of-file).
508 : This can be beneficial in various high performance I/O regimes. */
509 :
510 : FD_FN_UNUSED static int /* Work around -Winline */
511 : fd_io_buffered_istream_read( fd_io_buffered_istream_t * in,
512 : void * dst,
513 37311 : ulong dst_sz ) {
514 : /* We destructure in to avoid pointer escapes that might inhibit
515 : optimizations of other in inlines. */
516 37311 : ulong rbuf_lo = in->rbuf_lo;
517 37311 : ulong rbuf_ready = in->rbuf_ready;
518 37311 : int err = fd_io_buffered_read( in->fd, dst, dst_sz, in->rbuf, in->rbuf_sz, &rbuf_lo, &rbuf_ready );
519 37311 : in->rbuf_lo = rbuf_lo;
520 37311 : in->rbuf_ready = rbuf_ready;
521 37311 : return err;
522 37311 : }
523 :
524 : /* fd_io_buffered_istream_skip skips skip_sz bytes from in. Assumes in
525 : is initialized. Returns 0 on success and non-zero on failure.
526 : Failure interpretation is the same as fd_io_buffered_read. On a
527 : failure, in and the underlying file descriptor should be considered
528 : to be in a failed state (e.g. the only valid thing to do to in is
529 : fini and the only valid thing to do to fd is close).
530 :
531 : If the fd underlying in is seekable (e.g. a file), this will be very
532 : fast. If not (e.g. fd is pipe / socket / etc), this can block the
533 : caller until skip_sz bytes have arrived or an I/O error is detected.
534 :
535 : IMPORTANT! See note in fd_io_buffered_istream_read above about the
536 : impact of this on file pointer alignment. */
537 :
538 : static inline int
539 : fd_io_buffered_istream_skip( fd_io_buffered_istream_t * in,
540 1197 : ulong skip_sz ) {
541 : /* We destructure in to avoid pointer escapes that might inhibit
542 : optimizations of other in inlines. */
543 1197 : ulong rbuf_lo = in->rbuf_lo;
544 1197 : ulong rbuf_ready = in->rbuf_ready;
545 1197 : int err = fd_io_buffered_skip( in->fd, skip_sz, in->rbuf, in->rbuf_sz, &rbuf_lo, &rbuf_ready );
546 1197 : in->rbuf_lo = rbuf_lo;
547 1197 : in->rbuf_ready = rbuf_ready;
548 1197 : return err;
549 1197 : }
550 :
551 : /* fd_io_buffered_istream_peek returns a pointer in the caller's address
552 : space to the first byte that has been read but not yet consumed.
553 : Assumes in is initialized. The returned pointer can have arbitrary
554 : alignment and the returned pointer lifetime is until the next read,
555 : fetch, or fini. */
556 :
557 : FD_FN_PURE static inline void const *
558 7578 : fd_io_buffered_istream_peek( fd_io_buffered_istream_t * in ) {
559 7578 : return in->rbuf + in->rbuf_lo;
560 7578 : }
561 :
562 : /* fd_io_buffered_istream_peek_sz returns the number of bytes that have
563 : been read but not yet consumed. Assumes in is initialized. Returned
564 : value will be in [0,rbuf_sz] and will be valid until the next read,
565 : fetch, seek or fini. */
566 :
567 : FD_FN_PURE static inline ulong
568 11055 : fd_io_buffered_istream_peek_sz( fd_io_buffered_istream_t * in ) {
569 11055 : return in->rbuf_ready;
570 11055 : }
571 :
572 : /* fd_io_buffered_istream_seek consumes sz buffered bytes from in.
573 : Assumes in is initialized and that sz is at most peek_sz. */
574 :
575 : static inline void
576 : fd_io_buffered_istream_seek( fd_io_buffered_istream_t * in,
577 7578 : ulong sz ) {
578 7578 : in->rbuf_lo += sz;
579 7578 : in->rbuf_ready -= sz;
580 7578 : }
581 :
582 : /* fd_io_buffered_istream_fetch tries to fill up the stream's read
583 : buffer with as many unconsumed bytes as possible. Assumes in is
584 : initialized. Returns 0 on success (rbuf is filled to rbuf_sz with
585 : unconsumed data) and non-zero on failure (see below for
586 : interpretation). On failure, in and the underlying file descriptor
587 : should be considered to be in a failed state (e.g. the only valid
588 : thing to do out on is fini and the only valid thing to do on fd is
589 : close). That is:
590 :
591 : int err = fd_io_buffered_istream_fetch( in );
592 : if( FD_LIKELY( err==0 ) ) ... success, peek_sz() updated to at most rbuf_sz
593 : else if( FD_LIKELY( err< 0 ) ) ... end-of-file, peek_sz() updated to at most rbuf_sz and is num unconsumed bytes to EOF
594 : else if( FD_LIKELY( err==EAGAIN ) ) ... try again, peek_sz() unchanged, only possible if fd is non-blocking
595 : else ... I/O error, peek_sz() unchanged,
596 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
597 : ... in and fd should be considered failed
598 :
599 : IMPORTANT! See note in fd_io_buffered_istream_read above about the
600 : impact of fetch on file pointer alignment. */
601 :
602 : FD_FN_UNUSED static int /* Work around -Winline */
603 3471 : fd_io_buffered_istream_fetch( fd_io_buffered_istream_t * in ) {
604 3471 : uchar * rbuf = in->rbuf;
605 3471 : ulong rbuf_sz = in->rbuf_sz;
606 3471 : ulong rbuf_lo = in->rbuf_lo;
607 3471 : ulong rbuf_ready = in->rbuf_ready;
608 3471 : if( FD_UNLIKELY( rbuf_ready>=rbuf_sz ) ) return 0; /* buffer already full */
609 3468 : if( FD_LIKELY( (!!rbuf_ready) & (!!rbuf_lo) ) ) memmove( rbuf, rbuf+rbuf_lo, rbuf_ready ); /* Move unconsumed to beginning */
610 3468 : ulong rsz;
611 3468 : int err = fd_io_read( in->fd, rbuf+rbuf_ready, 0UL, rbuf_sz-rbuf_ready, &rsz );
612 3468 : in->rbuf_lo = 0UL;
613 3468 : in->rbuf_ready = rbuf_ready + rsz;
614 3468 : return err;
615 3471 : }
616 :
617 : /* fd_io_buffered_ostream_init initializes out to do buffered writes to
618 : the given file descriptor. out is an unused location that should
619 : hold the stream state, fd is an open normal-ish file descriptor to
620 : buffer, wbuf points to the first byte in the caller's address space
621 : to an unused wbuf_sz size memory region to use for the buffering
622 : (assumes wbuf is non-NULL with arbitrary alignment and wbuf_sz is
623 : positive). Returns out and on return out will be initialized. On
624 : return out will have ownership of fd and wbuf. */
625 :
626 : static inline fd_io_buffered_ostream_t *
627 : fd_io_buffered_ostream_init( fd_io_buffered_ostream_t * out,
628 : int fd,
629 : void * wbuf,
630 312 : ulong wbuf_sz ) {
631 312 : out->fd = fd;
632 312 : out->wbuf = (uchar *)wbuf;
633 312 : out->wbuf_sz = wbuf_sz;
634 312 : out->wbuf_used = 0UL;
635 312 : return out;
636 312 : }
637 :
638 : /* fd_io_buffered_ostream_{fd,wbuf,wbuf_sz} return the corresponding
639 : value used to initialize out. Assumes out is initialized. */
640 :
641 3 : FD_FN_PURE static inline int fd_io_buffered_ostream_fd ( fd_io_buffered_ostream_t const * out ) { return out->fd; }
642 3 : FD_FN_PURE static inline void * fd_io_buffered_ostream_wbuf ( fd_io_buffered_ostream_t const * out ) { return out->wbuf; }
643 3 : FD_FN_PURE static inline ulong fd_io_buffered_ostream_wbuf_sz( fd_io_buffered_ostream_t const * out ) { return out->wbuf_sz; }
644 :
645 : /* fd_io_buffered_ostream_fini finalizes a buffered output stream.
646 : Assumes out is initialized. On return out will no longer be
647 : initialized and the caller will have ownership of the underlying fd
648 : and wbuf.
649 :
650 : IMPORTANT! THIS WILL NOT DO ANY FINAL FLUSH OF BUFFERED BYTES. IT
651 : IS THE CALLER'S RESPONSIBILITY TO DO THIS IN THE NORMAL FINI CASE. */
652 :
653 : static inline void
654 12 : fd_io_buffered_ostream_fini( fd_io_buffered_ostream_t * out ) {
655 12 : (void)out;
656 12 : }
657 :
658 : /* fd_io_buffered_ostream_write writes src_sz bytes from src to the
659 : stream, temporarily buffering zero or more bytes as convenient.
660 : Assume out is initialized. src / src_sz have the same meaning /
661 : restrictions as fd_io_buffered_write. Returns 0 on success and
662 : non-zero on failure. Failure interpretation is the same as
663 : fd_io_buffered_write. On failure, out and the underlying file
664 : descriptor should be considered to be in a failed state (e.g. the
665 : only valid thing to do to out is fini and the only valid thing to do
666 : to fd is close).
667 :
668 : IMPORTANT! If fd_io_buffered_ostream_flush is only used to do a
669 : final flush before fini, all the writes to the underlying stream will
670 : always be at multiples of wbuf_sz offset from the initial file offset
671 : when the out was initialized and all the write sizes (except
672 : potentially the final flush) will be a multiple of wbuf_sz in size.
673 : This can be beneficial in various high performance I/O regimes. */
674 :
675 : static inline int
676 : fd_io_buffered_ostream_write( fd_io_buffered_ostream_t * out,
677 : void const * src,
678 36174 : ulong src_sz ) {
679 : /* We destructure out to avoid pointer escapes that might inhibit
680 : optimizations of other inlines that operate on out. */
681 36174 : ulong wsz = out->wbuf_used;
682 36174 : int err = fd_io_buffered_write( out->fd, src, src_sz, out->wbuf, out->wbuf_sz, &wsz );
683 36174 : out->wbuf_used = wsz;
684 36174 : return err;
685 36174 : }
686 :
687 : /* fd_io_buffered_ostream_peek returns a pointer in the caller's address
688 : space where the caller can prepare bytes to be streamed out. Assumes
689 : out is initialized. The returned pointer can have arbitrary
690 : alignment and the returned pointer lifetime is until the next write,
691 : flush, or fini. */
692 :
693 : FD_FN_PURE static inline void *
694 3321 : fd_io_buffered_ostream_peek( fd_io_buffered_ostream_t * out ) {
695 3321 : return out->wbuf + out->wbuf_used;
696 3321 : }
697 :
698 : /* fd_io_buffered_istream_peek_sz returns the number of bytes available
699 : at the peek location. Assumes out is initialized. Returned value
700 : will be in [0,wbuf_sz] and will be valid until the next write, fetch,
701 : seek or fini. */
702 :
703 : FD_FN_PURE static inline ulong
704 5022 : fd_io_buffered_ostream_peek_sz( fd_io_buffered_ostream_t * out ) {
705 5022 : return out->wbuf_sz - out->wbuf_used;
706 5022 : }
707 :
708 : /* fd_io_buffered_istream_seek commits the next sz unused write buffer
709 : bytes to be streamed out. Assumes out is initialized and that sz is
710 : at most peek_sz. */
711 :
712 : static inline void
713 : fd_io_buffered_ostream_seek( fd_io_buffered_ostream_t * out,
714 3282 : ulong sz ) {
715 3282 : out->wbuf_used += sz;
716 3282 : }
717 :
718 : /* fd_io_buffered_ostream_flush writes any buffered bytes in the stream's
719 : write buffer to the underlying file descriptor. Assume out is
720 : initialized. Returns 0 on success (all buffered bytes written to fd)
721 : and non-zero on failure (see below for interpretation). In both
722 : cases, the write buffer will be empty on return. On failure, out and
723 : the underlying file descriptor should be considered to be in a failed
724 : state (e.g. the only valid thing to do to out is fini and the only
725 : valid thing to do to fd is close).
726 :
727 : int err = fd_io_buffered_ostream_flush( out );
728 : if( FD_LIKELY( err==0 ) ) ... success, write buffer empty
729 : else ... I/O error, write buffer empty
730 : ... err is strerror compat, err is neither EAGAIN nor EWOULDBLOCK
731 : ... in and fd should be considered failed
732 :
733 : IMPORTANT! See note in fd_io_buffered_ostream_write below about the
734 : impact of doing this outside a final flush. */
735 :
736 : FD_FN_UNUSED static int /* Work around -Winline */
737 2088 : fd_io_buffered_ostream_flush( fd_io_buffered_ostream_t * out ) {
738 2088 : ulong wbuf_used = out->wbuf_used;
739 2088 : if( FD_UNLIKELY( !wbuf_used ) ) return 0; /* optimize for lots of tiny writes */
740 2070 : out->wbuf_used = 0UL;
741 2070 : ulong wsz;
742 2070 : return fd_io_write( out->fd, out->wbuf, wbuf_used, wbuf_used, &wsz );
743 2088 : }
744 :
745 : /* Misc APIs */
746 :
747 : /* fd_io_strerror converts an fd_io error code (i.e. negative ->
748 : end-of-file, 0 -> success, positive -> strerror compatible) into a
749 : human readable cstr. Unlike strerror, the lifetime of the returned
750 : pointer is infinite and the call itself is thread safe. The
751 : returned pointer is always to a non-NULL cstr. */
752 :
753 : FD_FN_CONST char const *
754 : fd_io_strerror( int err );
755 :
756 : /* fd_io_strsignal converts a signal code (like returned by WTERMSIG)
757 : into a human readable cstr. Unlike strsignal, the lifetime of the
758 : returned pointer is infinite and the call itself is thread safe.
759 : Unlike the glibc strsignal implementation in particular, it does
760 : not call `brk(3)` or `futex(2)` internally. The returned pointer
761 : is always to a non-NULL cstr. */
762 : FD_FN_CONST char const *
763 : fd_io_strsignal( int err );
764 :
765 : /* TODO: ASYNC IO APIS */
766 :
767 : FD_PROTOTYPES_END
768 :
769 : #endif /* HEADER_fd_src_util_io_fd_io_h */
|