/* libhw/generic/io.h - Device-independent I/O definitions * * Copyright (C) 2024-2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBHW_GENERIC_IO_H_ #define _LIBHW_GENERIC_IO_H_ #include /* for size_t */ #include /* for uintptr_t, uint{n}_t, UINT{n}_MAX */ #include #include /* structs ********************************************************************/ /* uoff_t ==========================================================*/ typedef uint64_t uoff_t; #define UOFF_MAX UINT64_MAX DECLARE_ERROR_OR(uoff_t); DECLARE_ERROR_AND(uoff_t); /* iovec ===========================================================*/ #if __unix__ #include #else struct iovec { void *iov_base; size_t iov_len; }; #endif typedef struct iovec iovec; DECLARE_ERROR_OR(iovec); DECLARE_ERROR_AND(iovec); /* duplex_iovec ====================================================*/ #define IOVEC_DISCARD ((void*)(~((uintptr_t)0))) struct duplex_iovec { /** * NULL is a valid pointer value in iov_read_to and * iov_write_from. To skip a read or write, use the special * value IOVEC_DISCARD. */ void *iov_read_to; void *iov_write_from; size_t iov_len; }; typedef struct duplex_iovec duplex_iovec; DECLARE_ERROR_OR(duplex_iovec); DECLARE_ERROR_AND(duplex_iovec); /* iovec utilities ************************************************************/ /* If byte_max_cnt == 0, then there is no maximum. */ /* slice iovec lists */ int io_slice_cnt ( const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); void io_slice (struct iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); int io_duplex_slice_cnt( const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); void io_duplex_slice (struct duplex_iovec *dst, const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); /* convert iovec lists */ void io_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt); void io_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt); /* slice and convert in one go */ void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); /* basic interfaces ***********************************************************/ /* * Conventions: * * - Naming: * + The "p"[osition] prefix means an explicit `uoff_t offset`, * instead of using an internal cursor. * + The "v"[ec] suffix means a sequence of iovecs, instead of a * simple buf+len. * * - Errors: * + Short *reads* are *not* errors (and so return size_t *or* * error). * + Short *writes* *are* errors (and so return size_t *and* * (possibly-null) error). */ /* read ============================================================*/ /** * Return bytes-read on success. A short read is *not* an error * (unlike `write` methods). * * It is invalid to call readv when the sum length of iovecs is 0. */ #define io_reader_LO_IFACE \ LO_FUNC(size_t_or_error, readv, const struct iovec *iov, int iovcnt) LO_INTERFACE(io_reader); #define io_readv(r, iov, iovcnt) LO_CALL(r, readv, iov, iovcnt) #define io_read(r, buf, count) LO_CALL(r, readv, &((struct iovec){.iov_base = buf, .iov_len = count}), 1) /** * Returns bytes-read on success. A short read is *not* an error * (unlike `write` methods). * * It is invalid to call preadv when the sum length of iovecs is 0. */ #define io_preader_LO_IFACE \ LO_FUNC(size_t_or_error, preadv, const struct iovec *iov, int iovcnt, uoff_t offset) LO_INTERFACE(io_preader); #define io_preadv(r, iov, iovcnt, off) LO_CALL(r, preadv, iov, iovcnt, off) #define io_pread(r, buf, count, off) LO_CALL(r, preadv, &((struct iovec){.iov_base = buf, .iov_len = count}), 1, off) /* write ===========================================================*/ /** * Return bytes-written. A short write *is* an error (unlike `read` * methods). * * Writes are *not* guaranteed to be atomic, so if you have concurrent * writers then you should arrange for a mutex to protect the writer. * * It is invalid to call writev when the sum length of iovecs is 0. */ #define io_writer_LO_IFACE \ LO_FUNC(size_t_and_error, writev, const struct iovec *iov, int iovcnt) LO_INTERFACE(io_writer); #define io_writev(w, iov, iovcnt) LO_CALL(w, writev, iov, iovcnt) #define io_write(w, buf, count) LO_CALL(w, writev, &((struct iovec){.iov_base = buf, .iov_len = count}), 1) /** * Returns bytes-written. A short write *is* an error (unlike `read` * methods). * * Writes are *not* guaranteed to be atomic, so if you have concurrent * writers then you should arrange for a mutex to protect the writer. * * It is invalid to call writev when the sum length of iovecs is 0. */ #define io_pwriter_LO_IFACE \ LO_FUNC(size_t_and_error, pwritev, const struct iovec *iov, int iovcnt, uoff_t offset) LO_INTERFACE(io_pwriter); #define io_pwritev(r, iov, iovcnt, off) LO_CALL(r, pwritev, iov, iovcnt, off) #define io_pwrite(r, buf, count, off) LO_CALL(r, pwritev, &((struct iovec){.iov_base = buf, .iov_len = count}), 1, off) /* readwrite =======================================================*/ /** * A short read/write *is* an error (like `write` methods and unlike * `read` methods). * * Reads/writes are *not* guaranteed to be atomic, so if you have * concurrent readers/writers then you should arrange for a mutex to * protect the duplex_readwriter. * * It is invalid to call readwritev when the sum length of iovecs is 0. */ #define io_duplex_readwriter_LO_IFACE \ LO_FUNC(size_t_and_error, readwritev, const struct duplex_iovec *iov, int iovcnt) LO_INTERFACE(io_duplex_readwriter); #define io_readwritev(rw, iov, iovcnt) \ LO_CALL(rw, readwritev, iov, iovcnt) /* read then write =================================================*/ /** * LO_CALL(src, pread_to, dst, src_offset, count) is functionally: * * size_t_and_error copy(lo_interface io_writer dst, lo_interface io_preader src, uoff_t src_offset, size_t count) { * buf = malloc(count); * size_t_or_error read_r = io_pread(src, buf, count, src_offset); * if (read_r.is_err) * return ERROR_AND(size_t, 0, read_r.err); * size_t_and_error write_r = io_write(dst, buf, read_r.size_t); * free(buf); * return write_r; * } * * except that it does not need to allocate a buffer. That is: It * allows the reader to dump its data directly to the writer. This * allows us to save a copy when the reader already has an in-memory * buffer containing the data. * * The above code defines when a short read/write is an error or not. * * It must call writev() exactly 0-or-1 times, and if it does call it, * then it must be called with exactly 1 iovec. */ #define io_preader_to_LO_IFACE \ LO_FUNC(size_t_and_error, pread_to, lo_interface io_writer dst, uoff_t src_offset, size_t count) LO_INTERFACE(io_preader_to); /* close ===========================================================*/ #define io_closer_LO_IFACE \ LO_FUNC(error, close) LO_INTERFACE(io_closer); #define io_close(c) LO_CALL(c, close) #define io_bidi_closer_LO_IFACE \ LO_NEST(io_closer) \ LO_FUNC(error, close_read) \ LO_FUNC(error, close_write) LO_INTERFACE(io_bidi_closer); #define io_close_read(c) LO_CALL(c, close_read) #define io_close_write(c) LO_CALL(c, close_write) /* aggregate interfaces *******************************************************/ #define io_readwriter_LO_IFACE \ LO_NEST(io_reader) \ LO_NEST(io_writer) LO_INTERFACE(io_readwriter); #endif /* _LIBHW_GENERIC_IO_H_ */