diff --git a/.gitignore b/.gitignore index 1f459def..d54475c2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /src/tests/proctests /src/tests/loadtests /src/tests/envtests +/src/tests/iostreamtests /src/tests/cptests/cptests /src/tests/cptests/corpus /src/tests/cptests/fuzz diff --git a/src/dinit-iostream.cc b/src/dinit-iostream.cc new file mode 100644 index 00000000..4e04b99a --- /dev/null +++ b/src/dinit-iostream.cc @@ -0,0 +1,737 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +// See dinit-iostream.h for inforamtion about the usage + +namespace { + +// A simple trick for calculating how much digits can be in a type with compile-time integer +// logarithm like function. +template constexpr unsigned type_max_numdigits(T num = std::numeric_limits::max(), + unsigned pow = 0) +{ + return (num == 0) ? pow : type_max_numdigits(num / 10, pow + 1); +} + +// We should have space for sign in signed variables. +constexpr unsigned UINT_MAX_CHARS = type_max_numdigits(); +constexpr unsigned INT_MAX_CHARS = type_max_numdigits() + 1; +constexpr unsigned ULONG_MAX_CHARS = type_max_numdigits(); +constexpr unsigned LONG_MAX_CHARS = type_max_numdigits() + 1; + +} /* anonymous namespace */ + +namespace dio { + +// class io_base + +streambuf *io_base::get_buf() noexcept +{ + return buf.get(); +} + +bool io_base::is_open() noexcept +{ + return (fd >= 0); +} + +void io_base::set_fd(const int newfd) noexcept +{ + fd = newfd; +} + +int io_base::get_fd() noexcept +{ + return fd; +} + +// class ostream + +ssize_t ostream::put(const char *msg, size_t count) noexcept +{ + if (!good()) { + return -1; + } + + // When we need to write some part of msg, we capture written chars in this var. + // That means the actual size of msg will be "cur_msg_in_buf_count + count". + size_t cur_msg_in_buf_count = 0; + + if (count == 0) { + return 0; // Null/Empty message + } + else if (count > buf->get_size()) { + bool r = flush_nx(); + if (!r) { + // io_error was set by flush_nx() call + return -1; + } + else while (count > static_cast(buf->get_free())) { + int prev_freespace = buf->get_free(); + buf->append(msg, prev_freespace); + msg += prev_freespace; + cur_msg_in_buf_count += prev_freespace; + count -= prev_freespace; + bool r = flush_nx(); + if (!r) { + // io_error was set by flush_nx() call + return (cur_msg_in_buf_count > 0) ? cur_msg_in_buf_count : -1; + } + } + buf->append(msg, count); + return cur_msg_in_buf_count + count; + } + else while (count > static_cast(buf->get_free())) { + // If we haven't enough storage for caputring the message Firstly we try to fill buffer as + // much as possible and then write the buffer. + int prev_freespace = buf->get_free(); + buf->append(msg, buf->get_free()); + msg += prev_freespace; + cur_msg_in_buf_count += prev_freespace; + count -= prev_freespace; + bool r = flush_nx(); + if (!r) { + // io_error was set by flush_nx() + return (cur_msg_in_buf_count > 0) ? cur_msg_in_buf_count : -1; + } + } + + buf->append(msg, count); + + return cur_msg_in_buf_count + count; +} + +void ostream::throw_exception_on(const int states) +{ + if ((states & io_states::buffer_failbit) && buffer_failure()) { + throw iostream_internal_err("Cannot use stream's buffer: buffer is nullptr!"); + } + if ((states & io_states::io_failbit) && io_failure()) { + throw iostream_system_err(io_failure()); + } +} + +bool ostream::open_nx(const char *path) noexcept +{ + fd = bp_sys::open(path, O_WRONLY); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool ostream::open(const char *path) +{ + bool r = open_nx(path); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool ostream::open_nx(const char *path, const int flags) noexcept +{ + fd = bp_sys::open(path, O_WRONLY | flags); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool ostream::open(const char *path, const int flags) +{ + bool r = open_nx(path, flags); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool ostream::open_nx(const char *path, const int flags, const mode_t mode) noexcept +{ + fd = bp_sys::open(path, O_WRONLY | flags, mode); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool ostream::open(const char *path, const int flags, const mode_t mode) +{ + bool r = open_nx(path, flags, mode); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool ostream::close_nx() noexcept +{ + if (!flush_nx()) { + // io_error was set by flush_nx() call + return false; + } + if (bp_sys::close(fd) < 0) { + io_error = errno; + return false; // Failed + } + + fd = -1; + return true; +} + +bool ostream::close() +{ + bool r = close_nx(); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +int ostream::current_state() noexcept +{ + int bits = 0; + if (!buf) bits |= io_states::buffer_failbit; + if (io_error) bits |= io_states::io_failbit; + return bits; +} + +bool ostream::good() noexcept +{ + return (current_state() == 0); +} + +bool ostream::buffer_failure() noexcept +{ + return (!buf); +} + +int ostream::io_failure() noexcept +{ + return io_error; +} + +void ostream::clear() noexcept +{ + io_error = 0; +} + +bool ostream::flush_nx() noexcept +{ + if (!good()) { + return -1; + } + else while (buf->get_length() > 0) { + char *ptr = buf->get_ptr(0); + unsigned len = buf->get_contiguous_length(ptr); + ssize_t r = bp_sys::write(fd, ptr, len); + + if (r < 0) { + io_error = errno; + return false; + } + buf->consume(r); + } + + return true; +} + +bool ostream::flush() +{ + bool r = flush_nx(); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const char *msg) noexcept +{ + size_t len = strlen(msg); + return (put(msg, len) == static_cast(len)); +} + +bool ostream::write(const char *msg) +{ + bool r = write_nx(msg); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(char msg) noexcept +{ + return (put(&msg, 1) == 1); +} + +bool ostream::write(char msg) +{ + bool r = write_nx(msg); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const std::string &msg) noexcept +{ + return (put(msg.c_str(), msg.size()) == static_cast(msg.size())); +} + +bool ostream::write(const std::string &msg) +{ + bool r = write_nx(msg); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const int num) noexcept +{ + char tmp[INT_MAX_CHARS + 1]; + int len = snprintf(tmp, INT_MAX_CHARS + 1, "%d", num); + return (put(tmp, len) == len); +} + +bool ostream::write(const int num) +{ + bool r = write_nx(num); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const unsigned num) noexcept +{ + char tmp[UINT_MAX_CHARS + 1]; + int len = snprintf(tmp, UINT_MAX_CHARS + 1, "%d", num); + return (put(tmp, len) == len); +} + +bool ostream::write(const unsigned num) +{ + bool r = write_nx(num); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const long num) noexcept +{ + char tmp[LONG_MAX_CHARS + 1]; + int len = snprintf(tmp, LONG_MAX_CHARS + 1, "%ld", num); + return (put(tmp, len) == len); +} + +bool ostream::write(const long num) +{ + bool r = write_nx(num); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const unsigned long num) noexcept +{ + char tmp[ULONG_MAX_CHARS + 1]; + int len = snprintf(tmp, ULONG_MAX_CHARS + 1, "%ld", num); + return (put(tmp, len) == len); +} + +bool ostream::write(const unsigned long num) +{ + bool r = write_nx(num); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const endline &) noexcept +{ + int r = put("\n", 1); + r += flush_nx(); + return (r >= 1); +} + +bool ostream::write(const endline &) +{ + bool r = write_nx(endl); + if (!r) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +bool ostream::write_nx(const flushbuf &) noexcept +{ + return (flush_nx()); +} + +bool ostream::write(const flushbuf &) +{ + return (flush()); +} + +ssize_t ostream::write_buf_nx(const char *msg) noexcept +{ + return put(msg, strlen(msg)); +} + +ssize_t ostream::write_buf(const char *msg) +{ + ssize_t r = write_buf_nx(msg); + if (r < 0) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +ssize_t ostream::write_buf_nx(const std::string &msg) noexcept +{ + return put(msg.c_str(), msg.size()); +} + +ssize_t ostream::write_buf(const std::string &msg) +{ + ssize_t r = write_buf_nx(msg); + if (r < 0) { + // This will definitely throw an exception + throw_exception_on(io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +ostream::operator bool() noexcept +{ + return good(); +} + +ostream::~ostream() noexcept +{ + // We don't use close()/close_nx() functions because we cannot do anything about flush_nx() failure + // in this stage. + if (!is_open()) return; + flush_nx(); + bp_sys::close(get_fd()); +} + +// Standard output "cout" on STDOUT_FILENO file descriptor +ostream cout(STDOUT_FILENO); +// Standard error output "cerr" on STDERR_FILENO file descriptor +ostream cerr(STDERR_FILENO); + +// class istream + +int istream::load_into_buf(unsigned len) noexcept +{ + int r = buf->fill(fd, len); + if (r < 0) { + io_error = errno; + return -1; + } + return r; +} + +void istream::throw_exception_on(const int states) +{ + if ((states & io_states::eofbit) && eof()) { + throw iostream_eof("End Of File has been reached."); + } + if ((states & io_states::buffer_failbit) && buffer_failure()) { + throw iostream_internal_err("Cannot use stream's buffer: buffer is nullptr!"); + } + if ((states & io_states::string_failbit) && string_failure()) { + throw iostream_internal_err("Cannot put lines into given std::string."); + } + if ((states & io_states::io_failbit) && io_failure()) { + throw iostream_system_err(io_failure()); + } +} + +bool istream::open_nx(const char *path) noexcept +{ + fd = bp_sys::open(path, O_RDONLY); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool istream::open(const char *path) +{ + bool r = open_nx(path); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool istream::open_nx(const char *path, const int flags) noexcept +{ + fd = bp_sys::open(path, O_RDONLY | flags); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool istream::open(const char *path, const int flags) +{ + bool r = open_nx(path, flags); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool istream::open_nx(const char *path, const int flags, const mode_t mode) noexcept +{ + fd = bp_sys::open(path, O_RDONLY | flags, mode); + + if (fd < 0) { + io_error = errno; + return false; + } + return true; +} + +bool istream::open(const char *path, const int flags, const mode_t mode) +{ + bool r = open_nx(path, flags, mode); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +bool istream::close_nx() noexcept +{ + if (bp_sys::close(fd) < 0) { + io_error = errno; + return false; // Failed + } + + fd = -1; + return true; +} + +bool istream::close() +{ + bool r = close_nx(); + if (!r) { + throw_exception_on(io_states::io_failbit); + } + return r; +} + +int istream::current_state() noexcept +{ + int bits = 0; + if (eof_state) bits |= io_states::eofbit; + if (!buf) bits |= io_states::buffer_failbit; + if (string_failed) bits |= io_states::string_failbit; + if (io_error) bits |= io_states::io_failbit; + return bits; +} + +bool istream::good() noexcept +{ + return (current_state() == 0); +} + +bool istream::eof() noexcept +{ + return eof_state; +} + +bool istream::buffer_failure() noexcept +{ + return (!buf); +} + +bool istream::string_failure() noexcept +{ + return string_failed; +} + +int istream::io_failure() noexcept +{ + return io_error; +} + +void istream::clear() noexcept +{ + eof_state = false; + string_failed = false; + io_error = 0; +} + +int istream::getc_nx() noexcept +{ + if (eof() || buffer_failure() || io_failure()) { + // We don't care about string_failure() in getc() function. + return -1; + } + if (buf->get_length() == 0) { + int r = load_into_buf(1); + if (r == 0) { + eof_state = true; + return -1; + } + else if (r < 0) { + io_error = errno; + return -1; + } + } + char result = *buf->get_ptr(0); + buf->consume(1); + return result; +} + +int istream::getc() +{ + int r = getc_nx(); + if (r < 0) { + throw_exception_on(io_states::eofbit | io_states::buffer_failbit | io_states::io_failbit); + } + return r; +} + +ssize_t istream::get_line_nx(std::string &dest, char delim) noexcept +{ + if (!good()) { + return -1; + } + if (buf->get_length() == 0) { + int r = load_into_buf(buf->get_size()); + if (r == 0) { + eof_state = true; + return 0; + } + if (r < 0) { + io_error = errno; + return -1; + } + } + dest.clear(); + + char *ptr = buf->get_ptr(0); + unsigned len = buf->get_contiguous_length(ptr); + char *eptr = static_cast(std::memchr(ptr, delim, len)); + + if (eptr == ptr) { + buf->consume(1); + return 1; // An empty line + } + while (eptr == nullptr) { + // If it hasn't any delim; We try to read as much as possible to reach the EOF or find a + // "delim" character. + try { + // dest.append() may throw out_of_range, length_error or bad_alloc + dest.append(ptr, len); + } + catch (const std::exception &e) { + string_failed = true; + return -1; + } + buf->consume(len); + + if (buf->get_length() == 0) { + int r = load_into_buf(buf->get_size()); + if (r == 0) { + return dest.size(); + } + if (r < 0) { + io_error = errno; + return -1; + } + } + + ptr = buf->get_ptr(0); + len = buf->get_contiguous_length(ptr); + eptr = static_cast(std::memchr(ptr, delim, len)); + } + unsigned index = eptr - ptr; + try { + // dest.append() may throw out_of_range, length_error or bad_alloc + dest.append(ptr, index); + } + catch (const std::exception &e) { + string_failed = true; + return -1; + } + buf->consume(index + 1); // Consume the delimiter character as well + return dest.size(); +} + +ssize_t istream::get_line(std::string &dest, char delim) +{ + ssize_t r = get_line_nx(dest, delim); + if (r <= 0) { + // This will definitely throw an exception + throw_exception_on(io_states::eofbit | io_states::buffer_failbit | io_states::string_failbit + | io_states::io_failbit); + } + return r; +} + +ssize_t istream::get_line_until_eof(std::string &dest, char delim) +{ + ssize_t r = get_line_nx(dest, delim); + if (r < 0) { + throw_exception_on(io_states::buffer_failbit | io_states::string_failbit | io_states::io_failbit); + } + return r; +} + +istream::operator bool() noexcept +{ + return good(); +} + +istream::~istream() noexcept +{ + if (!is_open()) return; + close_nx(); +} + +// Standard input "cin" on STDIN_FILENO file descriptor +istream cin(STDIN_FILENO); + +} /* dio namespace */ diff --git a/src/includes/dinit-iostream.h b/src/includes/dinit-iostream.h new file mode 100644 index 00000000..3aa02a21 --- /dev/null +++ b/src/includes/dinit-iostream.h @@ -0,0 +1,738 @@ +#ifndef DINIT_IOSTREAM_H_INCLUDED +#define DINIT_IOSTREAM_H_INCLUDED 1 + +#include +#include +#include +#include +#include + +#include + +/* + * dinit-iostream is a specialized library providing input/output wrappers around system I/O interfaces. + * The purpose is to replace standard library iostreams with something more usable, robust, and + * lightweight. One of the major issues with stdlib iostreams is the difficulty of obtaining useful + * error messages in a standardized way. The language spec allows for it, but implementations often + * map all error conditions to the same unhelpful message. std::ios::failure extends std::system_error + * but doesn't use errno as the error code. Also, they are heavyweight due to support for different + * locales (which is unnecessary in dinit). + * + * This library provides two classes for output and input: ostream and istream. + * See documentation for each class at the top of it. + * + * All functionality of this library is located in the "dio" namespace. + * + * Global members based on ostream: + * -------------------------------- + * + * ostream cout // stdout output object on STDOUT_FILENO file descriptor + * + * ostream cerr // stderr output object on STDERR_FILENO file descriptor + * + * print(...) // Writing to stdout + * + * print_err(...) // Writing to stderr + * + * "cout" and "cerr" are global objects to write to standard/error output. + * They provide two global functions: print() and print_err(). They are guaranteed + * to be exception-free and not to throw any exceptions anyway. + * + * See "ostream" class documentation for low-level functionality details. + * + * Usage: + * + * print("Hello Standard output :)\n") // -> stdout + * print("2 * 2 equals to: ", 2 * 2, endl); // -> stdout + * print_err("Hello Error output :)\n") // -> stderr + * + * + * Global members based on istream: + * -------------------------------- + * + * istream cin // stdin input object on STDIN_FILENO file descriptor + * + * get_char() // Read one character from cin + * + * get_line(dio::istream &stream, std::string &dest, char delim = '\n') + * // Read file until delim is found and push to std::string + * + * get_line_nx(dio::istream &stream, std::string &dest, char delim = '\n') + * // Exception-free variant of get_line() + * + * get_line_until_eof(dio::istream &stream, std::string &dest, char delim = '\n') + * // Same as get_line() but doesn't throw exception on EOF + * + * "cin" is a global object to interact with standard input, which provides get_char(). + * + * get_char() is guaranteed to be exception-free and not to throw any exceptions anyway. + * Will return -1 on failure or EOF. + * + * get_line() (and all of its variants) are used to read one line from the passed istream and push + * it to the passed std::string. Will return 0 on EOF and -1 on failure. + * + * Note: get_line() erases all contents of the passed std::string when, there is something available + * in associated file! + * + * get_line() may throw these exceptions: + * On eofbit (get_line_until_eof() doesn't throw this): + * dio::iostream_eof("End Of File has been reached.") + * On buffer_failbit: + * dio::iostream_internal_err("Cannot use stream's buffer: buffer is nullptr!") + * On string_failbit: + * dio::iostream_internal_err("Cannot put lines into given std::string.") + * On io_failbit: + * dio::iostream_system_err(errno) + * + * Usage: + * + * // Getting a line from stdin via get_line_nx() + * while(get_line_nx(cin, my_string)) { + * my_output.write(my_string, "\n"); + * } + * + * // Getting a character from stdin via get_char() + * int a_character = get_char(); + * if (a_character == -1) return; // Check for EOF or error + * my_output.write((char)a_character, "\n"); + */ + +constexpr unsigned IOSTREAM_BUFSIZE = 16384; // Size of the buffers + +namespace dio { + +/* + * Stream state bits: + * + * 1. "eofbit" indicates that the End Of File has been reached, and there is nothing left in + * the buffer (istream only). + * 2. "buffer_failbit" indicates an attempt to use the buffer when the buffer pointer was nullptr + * (e.g. failure to allocate a unique_ptr of streambuf) (ostream/istream). + * 3. "string_failbit" indicates a failed operation in std::string related calls (e.g. failure in + * pushing a line from the buffer to the given std::string in get_line()) (istream only). + * 4. "io_failbit" indicates that a system I/O function failed, and the errno was set (ostream/istream). + * + * If all of the above bits are false, the stream status is "good". + * + * The current_state() function returns the stream's current status based on this table. + */ +enum io_states +{ + eofbit = 0x01, + buffer_failbit = 0x02, + string_failbit = 0x04, + io_failbit = 0x08 +}; + +// Some empty classes for buffer operations implementation. +// Used for accepting endl and flush in ostream::write() functions. +class endline { }; +class flushbuf { }; +// Passing endl to those functions will put a '\n' and flush the stream's buffer. +static constexpr endline endl; +// Passing flush to those functions will flush the stream's buffer. +static constexpr flushbuf flush; + +// Specialise system_error & runtime_error for more meaningful exceptions on state bits/vars: + +// on eofbit +class iostream_eof : public std::runtime_error +{ + public: + iostream_eof(const char *msg) : runtime_error(msg) + { + } +}; + +// on buffer_failbit, string_failbit +class iostream_internal_err : public std::runtime_error +{ + public: + iostream_internal_err(const char *msg) : runtime_error(msg) + { + } +}; + +// on io_failbit +class iostream_system_err : public std::system_error +{ + public: + iostream_system_err(const int error_code) : system_error(error_code, std::system_category()) + { + } +}; + +using streambuf = cpbuffer; + +class io_base +{ + protected: + int fd = -1; + std::unique_ptr buf; + + public: + // Get raw pointer of current buffer + // Note: The buffer may be null if allocation failed (buffer_failbit will be set in this case) + streambuf *get_buf() noexcept; + + // Is current stream's file descriptor open? + bool is_open() noexcept; + + // Set file descriptor + // Note: Setting a new fd will replace the current fd in the stream and the buffer must be flushed + // first before replacing it. Also The previous file descriptor remains open and may need to be closed + // by other means to avoid a file descriptor leak. + void set_fd(const int newfd) noexcept; + + // Get current file descriptor + int get_fd() noexcept; +}; + +/* + * ostream + * =-=-=-= + * + * This class provides file output functionality. It holds a std::unique_ptr of streambuf (a fixed-size + * circular buffer which that's size can be changed at compile-time) and controls its file descriptor + * and handle it gracefully when needed. + * + * Constructors + * ------------ + * + * The ostream class has four constructors: + * + * // Simple construction + * dio::ostream output_a; + * if (output_a.buffer_failure()) return -1; // Failure in buffer allocation + * + * // Construction with moving a pre-allocated unique_ptr of the buffer + * try { + * std::unique_ptr my_buffer(new streambuf); + * } + * catch(const std::bad_alloc &) { + * print_err("Cannot allocate memory for the output buffer!\n"); + * return -1; + * } + * dio::ostream output_b(std::move(my_buffer)); // Takes ownership of my_buffer + * + * // Construction with a file descriptor number + * dio::ostream output_c(3); + * if (output_c.buffer_failure()) return -1; // Failure in buffer allocation + * + * // Construction with a combination of a file descriptor and moving a + * // pre-allocated unique_ptr of the buffer + * dio::ostream output_d(3, std::move(buffer_2)); + * + * Setting (or changing) the file descriptor after construction is available through the set_fd() + * function (See note in top of set_fd() func). opening and closing files is possible through open() + * function. + * + * Copy/move constructors/assignments and destructor + * ------------------------------------------------- + * + * ostream objects cannot be moved, copied or assigned. + * + * The ostream destructor will write all of the buffer content into its file descriptor through the + * flush_nx() call and close the its current fd. + * + * Note: Failures in ostream destructor are impossible to catch. + * + * Public member inherited from io_base: is_open() + * ------------------------------------------------------ + * + * ostream through base io_base class provides is_open() function which return true when current + * file path is open or false when it's not. + * + * Error handling and Stream states + * -------------------------------- + * + * There are two possible error states in an ostream: buffer failure and I/O failure. These are each + * represented by a bit in the value returned by the current_state() function. + * + * 1. buffer_failbit is false by default and means the class constructor failed to allocate a unique_ptr + * of the buffer at construction time. + * 2. io_failbit is false by default and means there is something wrong with system I/O functions. + * + * Note: if the io_failbit is set in the return value of current_state(), then io_failure() will + * return the errno value corresponding to the failed operation. If it is clear, io_failure() will return 0. + * + * Exception handling + * ------------------ + * + * See the description for each function to know what exceptions can be thrown by functions. + * + * All exception-throwing functions have exception-free variants marked by the "_nx" suffix. Those + * functions guarantee not to throw any exceptions. + */ +class ostream : public io_base +{ + // io_error has different functionality, it will be set to errno which returned by system + // functions (such as read()). + int io_error = 0; + + // Internal function to appending into the buffer. + ssize_t put(const char *msg, size_t count) noexcept; + + // Throw exception based on current state from io_states + void throw_exception_on(const int states); + + public: + ostream() noexcept + { + buf = std::unique_ptr(new(std::nothrow) streambuf); + } + + ostream(std::unique_ptr passed_buf) noexcept + { + buf = std::move(passed_buf); + } + + ostream(const int tfd) noexcept + { + fd = tfd; + buf = std::unique_ptr(new(std::nothrow) streambuf); + } + + ostream(const int tfd, std::unique_ptr passed_buf) noexcept + { + fd = tfd; + buf = std::move(passed_buf); + } + + ostream(const ostream &) = delete; + + void operator=(const ostream &) = delete; + + ostream(const ostream &&) = delete; + + void operator=(const ostream &&) = delete; + + // Open file path which specified in path or as open(2) parameter with flags or modes. + // + // Note: O_WRONLY is hard-coded in internal call to POSIX open(2). + // + // Note: Opening an already opened stream is not allowed and caller must close the stream + // before open it again. + // + // Returns: true on success, false on failure. + // + // Non _nx variant may throw: + // dio::iostream_system_err on POSIX open(2) failure + bool open_nx(const char *path) noexcept; + bool open(const char *path); + + bool open_nx(const char *path, const int flags) noexcept; + bool open(const char *path, const int flags); + + bool open_nx(const char *path, const int flags, const mode_t mode) noexcept; + bool open(const char *path, const int flags, const mode_t mode); + + // Flush the buffer and close the current file descriptor. + // + // Returns: true on success, false on failure cases. + // + // Non _nx variant may throw: + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_system_err on system I/O failure + bool close_nx() noexcept; + bool close(); + + // Returns: current bits/vars in stream based on io_states enum. + int current_state() noexcept; + + // Returns: true when there is no set bits/vars otherwise false. + bool good() noexcept; + + // Returns: true when buffer_failbit is set otherwise false. + bool buffer_failure() noexcept; + + // Returns: 0 or errno based on io_error value. + int io_failure() noexcept; + + // Resets failure bits/vars to false (or 0 about io_error). + // Note: buffer_failbit will not be reset. + void clear() noexcept; + + // Flush buffer of stream. + // + // Returns: true on full-flushed buffer, false on failure or not full-flushed buffer. + // + // Non _nx variant may throw: + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_system_err on system I/O failure + bool flush_nx() noexcept; + bool flush(); + + // write() functions, Used to write many types. + // + // All calls to write() will be buffered and not necessarily written to the file descriptor except + // in these situations: + // 1. The size of the passed message is larger than the buffer size. + // 2. The size of the passed message is larger than the current buffer free space. + // 3. "flush()" (or its exception-free variant: flush_nx()) is called. + // 4. A "flush" object is passed to write() functions (equivalent to calling flush()). + // 5. An "endl" object is passed to write() functions (equivalent to writing \n and + // calling flush()). + // 6. The object is destructed (which calls flush_nx()). + // + // Multiple arguments can be passed in a single call as well (due to the templated overload), e.g: + // + // output_a.write("This is an example message.\n"); + // output_b.write("2 + 2 equals: ", 2 + 2, endl); + // + // Returns: true on success, false on failure cases. + // + // Note: write() does return false on partial write, Use write_buf() and check its return, if you + // want to handle partial write gracefully. + // + // Non _nx variants may throw: + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_system_err on system I/O failure + bool write_nx(const char *msg) noexcept; + bool write(const char *msg); + + bool write_nx(char msg) noexcept; + bool write(char msg); + + bool write_nx(const std::string &msg) noexcept; + bool write(const std::string &msg); + + bool write_nx(const int num) noexcept; + bool write(const int num); + + bool write_nx(const unsigned num) noexcept; + bool write(const unsigned num); + + bool write_nx(const long num) noexcept; + bool write(const long num); + + bool write_nx(const unsigned long num) noexcept; + bool write(const unsigned long num); + + bool write_nx(const endline &) noexcept; + bool write(const endline &); + + bool write_nx(const flushbuf &) noexcept; + bool write(const flushbuf &); + + template inline bool write_nx(const A &a, const B &b, const C & ...c) + noexcept + { + if (!good()) return false; + if (!write_nx(a)) return false; + if (!write_nx(b, c...)) return false; + return true; + } + + template inline bool write(const A &a, const B &b, const C & ...c) + { + if (!good()) return false; + if (!write(a)) return false; + if (!write(b, c...)) return false; + return true; + } + + // write_buf() functions, Used to write char* or std::string. + // + // All calls to write_buf() will be buffered and not necessarily written to the file descriptor + // except in situations that are mentioned about write() above. + // + // Note: On partial write, io_error will reflect the cause of partial write. + // + // Returns: number of written-or-buffered characters. + // + // Non _nx variants may throw: + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_system_err on system I/O failure + ssize_t write_buf_nx(const char *msg) noexcept; + ssize_t write_buf(const char *msg); + + ssize_t write_buf_nx(const std::string &msg) noexcept; + ssize_t write_buf(const std::string &msg); + + // This is an alias for good() function and could be used like this: + // + // if (output_a) { + // return; // Everything looks good + // } + // + // Returns: true when everything looks good otherwise false. + explicit operator bool() noexcept; + + ~ostream() noexcept; +}; + +// Standard output "cout" on STDOUT_FILENO file descriptor +extern ostream cout; +// Standard error output "cerr" on STDERR_FILENO file desclogriptor +extern ostream cerr; + +template ssize_t print(const arguments & ...args) noexcept +{ + return cout.write_nx(args...); +} + +template ssize_t print_err(const arguments & ...args) noexcept +{ + return cerr.write_nx(args...); +} + +/* + * istream + * =-=-=-= + * + * This class provides file input functionality. It holds a std::unique_ptr streambuf (a fixed-size + * circular buffer which that's size can be changed at compile-time) and controls its file descriptor + * and handle it gracefully when needed. + * + * Constructors + * ------------ + * + * The istream class has four constructors: + * + * // Simple construction + * dio::istream input_a; + * if (input_a.buffer_failure()) return -1; // Failure in buffer allocation + * + * // Construction with moving a pre-allocated unique_ptr of the buffer + * try { + * std::unique_ptr my_buffer(new streambuf); + * } + * catch(const std::bad_alloc &) { + * print_err("Cannot allocate memory for input buffer!\n"); + * return -1; + * } + * dio::istream input_b(std::move(my_buffer)); // Takes ownership of my_buffer + * + * // Construction with a file descriptor number + * dio::istream input_c(3); + * if (input_c.buffer_failure()) return -1; // Failure in buffer allocation + * + * // Construction with a combination of a file descriptor and moving a + * // pre-allocated unique_ptr of the buffer + * dio::istream input_d(3, std::move(buffer_2)); + * + * Also, setting (or changing) file descriptor after construction is available through set_fd() function + * (See note in top of set_fd()). opening and closing files is possible through open() function. + * + * Copy/move constructors/assignments and destructor + * ------------------------------------------------- + * + * istream objects cannot be moved, copied or assigned. + * + * The istream destructer will close its fd. + * + * Public member inherited from io_base: is_open() + * ------------------------------------------------------ + * + * istream through base io_base class provides is_open() function which return true when current + * file path is open or false when it's not. + * + * Error handling and Stream states + * -------------------------------- + * + * There are four possible states in an istream: EOF, buffer failure, string related operations failure and + * I/O failure. These are each represented by a bit in the value returned by the current_state() function. + * + * 1. eofbit is false by default and means there is nothing left in the file descriptor and the buffer + * of the stream. + * 2. buffer_failbit is false by default and means the class constructor failed to allocate a unique_ptr + * of the buffer at construction time. + * 3. string_failbit is false by default and means a std::string related operation was failed. + * 4. io_failbit is false by default and means there is something wrong from system I/O calls. + * + * Note: if the io_failbit is set in the return value of current_state(), then io_failure() will + * return the errno value corresponding to the failed operation. If it is clear, io_failure() will return 0. + * + * Exception handling + * ------------------ + * + * See the description for each function to know what exceptions can be thrown by functions. + * + * All of the exception-throwing functions have exception-free variants which are marked by the "_nx" + * suffix. Those functions guarantee not to throw any exceptions. + */ +class istream : public io_base +{ + // Variables to capture stream current status: + bool eof_state = false; + bool string_failed = false; + // io_error has different functionality, it will be set to errno which returned by system + // functions (such as read()). + int io_error = 0; + + // Internal function to load from file descriptor (a wrapper for buf->fill()) + int load_into_buf(unsigned len) noexcept; + + // Throw exception based on current state from io_states + void throw_exception_on(const int states); + + public: + istream() noexcept + { + buf = std::unique_ptr(new(std::nothrow) streambuf); + } + + istream(std::unique_ptr passed_buf) noexcept + { + buf = std::move(passed_buf); + } + + istream(const int tfd) noexcept + { + fd = tfd; + buf = std::unique_ptr(new(std::nothrow) streambuf); + } + + istream(const int tfd, std::unique_ptr passed_buf) noexcept + { + fd = tfd; + buf = std::move(passed_buf); + } + + istream(const istream &) = delete; + + void operator=(const istream &) = delete; + + istream(const istream &&) = delete; + + void operator=(const istream &&) = delete; + + // Open file path which specified in path or as open(2) parameter with flags or modes. + // + // Note: O_RDONLY is hard-coded in internal call to POSIX open(2). + // + // Note: Opening an already opened stream is not allowed and caller must close the stream + // before open it again. + // + // Returns: true on success, false on failure. + // + // Non _nx variant may throw: + // dio::iostream_system_err on POSIX open(2) failure + bool open_nx(const char *path) noexcept; + bool open(const char *path); + + bool open_nx(const char *path, const int flags) noexcept; + bool open(const char *path, const int flags); + + bool open_nx(const char *path, const int flags, const mode_t mode) noexcept; + bool open(const char *path, const int flags, const mode_t mode); + + // Close the current file descriptor. + // + // Returns: true on success, false on failure cases. + // + // Non _nx variant may throw: + // dio::iostream_system_err on system I/O failure + bool close_nx() noexcept; + bool close(); + + // Returns: current bits in stream based on io_states enum. + int current_state() noexcept; + + // Returns: true when there is no set bits/vars otherwise false. + bool good() noexcept; + + // Returns: true or false based on eofbit is set or not. + bool eof() noexcept; + + // Returns: true or false based on buffer_failbit is set or not. + bool buffer_failure() noexcept; + + // Returns: true or false based on string_failbit is set or not. + bool string_failure() noexcept; + + // Returns: 0 or errno based on io_error value. + int io_failure() noexcept; + + // Resets failure bits/vars to false (or 0 about io_error). + // Note: buffer_failbit will not be reset. + void clear() noexcept; + + // Reads and returns one character from file descriptor. + // + // Returns: character as an integer or -1 on eofbit, buffer_failure, and io_failure. + // + // Non _nx variant may throw: + // dio::iostream_eof on EOF + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_system_err on system I/O failure + int getc_nx() noexcept; + int getc(); + + // Reads and stores one line from fd into given std::string. + // + // istream provides the get_line() function to read from its file descriptor until the delimiter + // character is found (usually '\n') and write it to the given std::string. Also, the given + // std::string will be erased when there is something in the buffer on each call. + // + // Returns: the amount of characters in the string or 0 on EOF or -1 on failure cases. + // + // Non _nx variant may throw: + // dio::iostream_eof on EOF + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_internal_err on putting into given std::string failure + // dio::iostream_system_err on system I/O failure + ssize_t get_line_nx(std::string &dest, char delim = '\n') noexcept; + ssize_t get_line(std::string &dest, char delim = '\n'); + + // Same as get_line() but doesn't throw exception on EOF. + // + // Returns: Same as get_line(). + // + // May throw: + // dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) + // dio::iostream_internal_err on putting into given std::string failure + // dio::iostream_system_err on system I/O failure + ssize_t get_line_until_eof(std::string &dest, char delim = '\n'); + + // This is an alias for good() function and could be used like this: + // + // if (input_b) { + // return; // Everything looks good + // } + // + // Returns: true when everything looks good otherwise false. + explicit operator bool() noexcept; + + ~istream() noexcept; +}; + +// Standard input "cin" on STDIN_FILENO file descriptor +extern istream cin; + +// Reads and returns one character from file descriptor 1 (stdin) +inline int get_char() noexcept +{ + return cin.getc_nx(); +} + +// Reads and stores one line from given istream into given std::string +// Non _nx variants May throw: +// dio::iostream_eof on EOF +// dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) +// dio::iostream_internal_err on pushing into given std::string failure +// dio::iostream_system_err on system I/O failure +inline ssize_t get_line_nx(istream &stream, std::string &dest, char delim = '\n') noexcept +{ + return stream.get_line_nx(dest, delim); +} + +inline ssize_t get_line(istream &stream, std::string &dest, char delim = '\n') +{ + return stream.get_line(dest, delim); +} + +// Same as get_line() but doesn't throw exception on EOF +// May throw: +// dio::iostream_internal_err on Buffer failure (buffer did not allocated at construction time) +// dio::iostream_internal_err on putting into given std::string failure +// dio::iostream_system_err on system I/O failure +inline ssize_t get_line_until_eof(istream &stream, std::string &dest, char delim = '\n') +{ + return stream.get_line_until_eof(dest, delim); +} + +} /* dio namespace */ + +#endif /* DINIT_IOSTREAM_H_INCLUDED */ diff --git a/src/tests/Makefile b/src/tests/Makefile index 8782f994..51fd9805 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -3,12 +3,12 @@ ALL_TEST_CXXFLAGS=$(CPPFLAGS) $(TEST_CXXFLAGS) $(TEST_CXXFLAGS_EXTRA) ALL_TEST_LDFLAGS=$(TEST_LDFLAGS) $(TEST_LDFLAGS_EXTRA) -objects = tests.o test-dinit.o proctests.o loadtests.o envtests.o test-run-child-proc.o test-bpsys.o +objects = tests.o test-dinit.o proctests.o loadtests.o envtests.o iostreamtests.o test-run-child-proc.o test-bpsys.o parent_objs = service.o proc-service.o dinit-log.o load-service.o baseproc-service.o dinit-env.o control.o check: build-tests run-tests -build-tests: tests proctests loadtests envtests +build-tests: tests proctests loadtests envtests iostreamtests $(MAKE) -C cptests build-tests run-tests: build-tests @@ -16,6 +16,7 @@ run-tests: build-tests ./proctests ./loadtests ./envtests + ./iostreamtests $(MAKE) -C cptests run-tests tests: $(parent_objs) tests.o test-dinit.o test-bpsys.o test-run-child-proc.o @@ -23,13 +24,21 @@ tests: $(parent_objs) tests.o test-dinit.o test-bpsys.o test-run-child-proc.o proctests: $(parent_objs) proctests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(CXX) -o proctests $(parent_objs) proctests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(ALL_TEST_LDFLAGS) - + loadtests: $(parent_objs) loadtests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(CXX) -o loadtests $(parent_objs) loadtests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(ALL_TEST_LDFLAGS) envtests: $(parent_objs) envtests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(CXX) -o envtests $(parent_objs) envtests.o test-dinit.o test-bpsys.o test-run-child-proc.o $(ALL_TEST_LDFLAGS) +# dinit-iostream contains global objects which require special handling in test environments. +# Therefore, avoid linking it to tests by default (instead include it explicitly in relevant tests eg iostreamtests). +dinit-iostream.o: ../dinit-iostream.cc + $(CXX) $(ALL_TEST_CXXFLAGS) -MMD -MP -Itest-includes -I../../dasynq/include -I../../build/includes -I../includes -c $< -o $@ + +iostreamtests: iostreamtests.o dinit-iostream.o test-bpsys.o + $(CXX) -o iostreamtests iostreamtests.o dinit-iostream.o test-bpsys.o $(ALL_TEST_LDFLAGS) + $(objects): %.o: %.cc $(CXX) $(ALL_TEST_CXXFLAGS) -MMD -MP -Itest-includes -I../../dasynq/include -I../../build/includes -I../includes -c $< -o $@ @@ -38,7 +47,7 @@ $(parent_objs): %.o: ../%.cc clean: $(MAKE) -C cptests clean - rm -f *.o *.d tests proctests loadtests envtests + rm -f *.o *.d tests proctests loadtests envtests iostreamtests -include $(objects:.o=.d) -include $(parent_objs:.o=.d) diff --git a/src/tests/iostreamtests.cc b/src/tests/iostreamtests.cc new file mode 100644 index 00000000..bcecc52f --- /dev/null +++ b/src/tests/iostreamtests.cc @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "baseproc-sys.h" +#include "dinit-iostream.h" + +#ifdef NDEBUG +#error "This file must be built with assertions ENABLED!" +#endif + +void ostream_basic_test() +{ + int fd = bp_sys::allocfd(); + dio::ostream stream(fd); + dio::streambuf* buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + const char msg[] = "This is a test message!\n"; + + assert(stream.write(msg)); + assert(buf->get_length() == sizeof(msg) - 1); + + char* ptr = buf->get_ptr(0); + unsigned len = buf->get_contiguous_length(ptr); + assert(strncmp(ptr, msg, len) == 0); + + assert(stream.flush_nx()); + assert(buf->get_length() == 0); + + std::vector wdata; + bp_sys::extract_written_data(fd, wdata); + + assert(wdata.size() == sizeof(msg) - 1); + assert(strncmp(wdata.data(), msg, wdata.size()) == 0); +} + +void ostream_write_buf_test() +{ + int fd = bp_sys::allocfd(); + dio::ostream stream(fd); + dio::streambuf* buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + const char msg[] = "This is a test message!\n"; + + assert(stream.write_buf(msg) == sizeof(msg) - 1); + assert(buf->get_length() == sizeof(msg) - 1); + + char* ptr = buf->get_ptr(0); + unsigned len = buf->get_contiguous_length(ptr); + assert(strncmp(ptr, msg, len) == 0); + + assert(stream.flush_nx()); + assert(buf->get_length() == 0); + + std::vector wdata; + bp_sys::extract_written_data(fd, wdata); + + assert(wdata.size() == sizeof(msg) - 1); + assert(strncmp(wdata.data(), msg, wdata.size()) == 0); +} + +void ostream_int_conversion_test() +{ + int fd = bp_sys::allocfd(); + dio::ostream stream(fd); + dio::streambuf *buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + const char msg1[] = "The 2 + 2 equals to: "; + const int msg2 = 4; + const char msg_full[] = "The 2 + 2 equals to: 4"; + + assert(stream.write(msg1, msg2)); + assert(buf->get_length() == sizeof(msg_full) - 1); + + char* ptr = buf->get_ptr(0); + unsigned len = buf->get_contiguous_length(ptr); + assert(strncmp(ptr, msg_full, len) == 0); + + assert(stream.flush_nx()); + assert(buf->get_length() == 0); + + std::vector wdata; + bp_sys::extract_written_data(fd, wdata); + + assert(wdata.size() == sizeof(msg_full) - 1); + assert(strncmp(wdata.data(), msg_full, wdata.size()) == 0); +} + +void ostream_large_msg_test() +{ + int fd = bp_sys::allocfd(); + dio::ostream stream(fd); + dio::streambuf *buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + char msg[IOSTREAM_BUFSIZE + 2]; + std::fill_n(msg, IOSTREAM_BUFSIZE + 1, 'a'); + msg[IOSTREAM_BUFSIZE + 1] = '\0'; + + assert(stream.write(msg)); + assert(buf->get_length() == 1); + assert(stream.flush()); + + std::vector wdata; + bp_sys::extract_written_data(fd, wdata); + + assert(wdata.size() == sizeof(msg) - 1); + assert(strncmp(wdata.data(), msg, wdata.size()) == 0); +} + +void istream_basic_test() +{ + std::vector wdata = { 'L', '1', '\n', 'L','2', '\n', '\n', 'L', '3' }; + bp_sys::supply_file_content("file", std::move(wdata)); + + dio::istream stream; + dio::streambuf *buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + assert(stream.open_nx("file")); + assert(stream.is_open()); + assert(stream.get_fd() >= 0); + + std::string line; + + assert(dio::get_line(stream, line) == 2); + assert(line.compare("L1") == 0); + + assert(dio::get_line(stream, line) == 2); + assert(line.compare("L2") == 0); + + assert(dio::get_line(stream, line) == 1); + assert(line.size() == 0); + + assert(dio::get_line(stream, line) == 2); + assert(line.compare("L3") == 0); + + assert(dio::get_line_until_eof(stream, line) == 0); + assert(stream.eof()); + assert(line.compare("L3") == 0); + + assert(stream.close()); +} + +void istream_buffer_boundary_test() +{ + int fd = bp_sys::allocfd(); + dio::istream stream(fd); + dio::streambuf *buf = stream.get_buf(); + assert(buf != nullptr); + assert(stream.good()); + + std::array msg; + msg.fill('a'); + buf->append(msg.begin(), msg.size()); + + std::fill_n(msg.begin(), 100, 'b'); + buf->consume(100); + buf->append(msg.begin(), 100); + + std::string line; + assert(dio::get_line(stream, line) == IOSTREAM_BUFSIZE); + assert(strncmp(line.c_str(), msg.begin(), IOSTREAM_BUFSIZE)); +} + +#define RUN_TEST(name, spacing) \ + std::cout << #name "..." spacing << std::flush; \ + name(); \ + std::cout << "PASSED" << std::endl; + +int main(int argc, char **argv) +{ + RUN_TEST(ostream_basic_test, " "); + RUN_TEST(ostream_write_buf_test, " "); + RUN_TEST(ostream_int_conversion_test, " "); + RUN_TEST(ostream_large_msg_test, " "); + RUN_TEST(istream_basic_test, " "); + RUN_TEST(istream_buffer_boundary_test, " "); + + // The destructors of the cout, cerr, and cin global objects will attempt to close the associated + // file descriptors, if they are still open. The bp_sys test infrastructure may have been torn + // down by that time, however, leading to errors. So, close the (bp_sys-based) descriptors now + // to avoid that. + dio::cout.close_nx(); + dio::cerr.close_nx(); + dio::cin.close_nx(); + + return 0; +} diff --git a/src/tests/meson.build b/src/tests/meson.build index aa2f79d9..d40c0956 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -52,7 +52,15 @@ envtests_exec = executable( for_tests_dinit_sources, include_directories: for_tests_incdir ) +iostreamtests_exec = executable( + 'iostreamtests', + 'iostreamtests.cc', + '../dinit-iostream.cc', + 'test-bpsys.cc', + include_directories: for_tests_incdir +) test('tests', tests_exec, suite: 'unit_tests') test('proctests', proctests_exec, suite: 'unit_tests') test('loadtests', loadtests_exec, workdir: meson.current_source_dir(), suite: 'unit_tests') test('envtests', envtests_exec, suite: 'unit_tests') +test('iostreamtests', iostreamtests_exec, suite: 'unit_tests') diff --git a/src/tests/test-bpsys.cc b/src/tests/test-bpsys.cc index 21501869..d36f1a52 100644 --- a/src/tests/test-bpsys.cc +++ b/src/tests/test-bpsys.cc @@ -138,6 +138,12 @@ int open(const char *pathname, int flags) return nfd; } +// ToDo: Check for mode instead of partial impl +int open(const char *pathname, int flags, mode_t mode) +{ + return open(pathname, flags); +} + int pipe2(int fds[2], int flags) { fds[0] = allocfd(); diff --git a/src/tests/test-includes/baseproc-sys.h b/src/tests/test-includes/baseproc-sys.h index e9033607..05ee1596 100644 --- a/src/tests/test-includes/baseproc-sys.h +++ b/src/tests/test-includes/baseproc-sys.h @@ -50,6 +50,7 @@ void supply_file_content(const std::string &path, std::vector &&data); // implementations elsewhere: int open(const char *pathname, int flags); +int open(const char *pathname, int flags, mode_t mode); int pipe2(int pipefd[2], int flags); int close(int fd); int kill(pid_t pid, int sig);