diff --git a/configure.ac b/configure.ac index 330d0c85f..c7981dc9b 100644 --- a/configure.ac +++ b/configure.ac @@ -511,14 +511,26 @@ AM_COND_IF(INTERIX, ],[]) +dnl +dnl Linux AIO support +dnl + AM_COND_IF(LINUX, [ - AC_ARG_ENABLE(aio, - AC_HELP_STRING([--enable-aio], [Enable AIO support on Linux platforms]), + AC_ARG_ENABLE(aio, AC_HELP_STRING([--disable-aio], [Disable kernel AIO support]), [ - IRCD_DEFINE(USE_AIO, [1], [Define to 1 if you want AIO support on linux]) - ],[]) -],[]) + aio=$enableval + ], + [ + aio=yes + ]) +], []) + +if test "$aio" = "yes"; then + IRCD_DEFINE(USE_AIO, [1], [Linux AIO is supported and will be used]) +else + IRCD_DEFINE(USE_AIO, [0], [Define to 1 if you want AIO support (linux only)]) +fi dnl *************************************************************************** @@ -1364,6 +1376,7 @@ echo echo "Configured ........................ $PACKAGE_NAME $PACKAGE_VERSION" echo "Version ........................... $RB_VERSION" echo "Configuration time ................ $RB_DATESTR" +echo "Host OS ........................... $host_os" echo "Compiler .......................... $CXX" echo "Compiler flags (CXXFLAGS) ......... $CXXFLAGS" echo "Building boost .................... $with_included_boost" @@ -1375,6 +1388,7 @@ echo "SSL support........................ $SSL_TYPE" echo "Precompiled headers ............... $build_pch" echo "Developer debug ................... $debug" echo "IPv6 support ...................... $ipv6" +echo "Linux AIO support ................. $aio" echo "Installing into ................... $prefix" echo echo "* Ready to build $PACKAGE_NAME" diff --git a/include/ircd/fs/fs.h b/include/ircd/fs/fs.h index bc8b5c73d..f7917d5ad 100644 --- a/include/ircd/fs/fs.h +++ b/include/ircd/fs/fs.h @@ -1,23 +1,21 @@ -/* - * Copyright (C) 2016 Charybdis Development Team - * Copyright (C) 2016 Jason Volk - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice is present in all copies. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. #pragma once #define HAVE_IRCD_FS_H @@ -96,19 +94,18 @@ namespace ircd::fs bool mkdir(const std::string &path); // This suite of IO functions may yield your context. - std::string read(const std::string &name); - string_view read(const std::string &name, const mutable_raw_buffer &buf); - string_view read(const string_view &name, const mutable_raw_buffer &buf); bool write(const std::string &name, const const_raw_buffer &buf); bool append(const std::string &name, const const_raw_buffer &buf); bool overwrite(const std::string &name, const const_raw_buffer &buf); bool overwrite(const string_view &name, const const_raw_buffer &buf); + + extern aio *aioctx; } +#include "read.h" + struct ircd::fs::init { - std::unique_ptr aio; - init(); ~init() noexcept; }; diff --git a/include/ircd/fs/read.h b/include/ircd/fs/read.h new file mode 100644 index 000000000..dc1b8d663 --- /dev/null +++ b/include/ircd/fs/read.h @@ -0,0 +1,63 @@ +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#define HAVE_IRCD_FS_READ_H + +namespace ircd::fs +{ + struct read_opts extern const read_opts_default; + using read_callback = std::function; + + // Asynchronous callback-based read into buffer; callback views read portion. + void read(const string_view &path, const mutable_raw_buffer &, const read_opts &, read_callback); + void read(const string_view &path, const mutable_raw_buffer &, read_callback); + + // Yields ircd::ctx for read into buffer; returns view of read portion. + string_view read(const string_view &path, const mutable_raw_buffer &, const read_opts & = read_opts_default); + + // Yields ircd::ctx for read into allocated string; returns that string + std::string read(const string_view &path, const read_opts & = read_opts_default); +} + +/// Options for a read operation +struct ircd::fs::read_opts +{ + read_opts() = default; + read_opts(const off_t &); + + /// Offset in the file to start the read from. + off_t offset {0}; + + /// Request priority (this option may be improved, avoid for now) + int16_t priority {0}; +}; + +inline +ircd::fs::read_opts::read_opts(const off_t &offset) +:offset{offset} +{} + +inline void +ircd::fs::read(const string_view &path, + const mutable_raw_buffer &buf, + read_callback callback) +{ + return read(path, buf, read_opts_default, std::move(callback)); +} diff --git a/ircd/aio.h b/ircd/aio.h new file mode 100644 index 000000000..2726aaa84 --- /dev/null +++ b/ircd/aio.h @@ -0,0 +1,516 @@ +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#define HAVE_AIO_H + +#include + +/// AIO context instance. Right now this is a singleton with an extern +/// instance at fs::aioctx. +struct ircd::fs::aio +{ + struct request; + + /// Maximum number of events we can submit to kernel + static constexpr const size_t &MAX_EVENTS + { + 64 + }; + + /// The semaphore value for the eventfd which we keep here. + uint64_t semval{0}; + + /// An eventfd which will be notified by the kernel; we integrate this with + /// the ircd io_service core epoll() event loop. The EFD_SEMAPHORE flag is + /// not used to reduce the number of triggers. We can collect multiple AIO + /// completions after a single trigger to this fd. With EFD_SEMAPHORE, we + /// would collect all the completions on the first trigger and then + /// continue to get polled. Because EFD_SEMAPHORE is not set, the semval + /// which is kept above will reflect a hint for how many AIO's are done. + asio::posix::stream_descriptor resfd + { + *ircd::ios, int(syscall(::eventfd, semval, EFD_NONBLOCK)) + }; + + /// Handler to the io context we submit requests to the kernel with + aio_context_t idp + { + 0 + }; + + // Callback stack invoked when the sigfd is notified of completed events. + void handle_event(const io_event &) noexcept; + void handle_events() noexcept; + void handle(const error_code &, const size_t) noexcept; + + void set_handle(); + + aio() + { + syscall(MAX_EVENTS, &idp); + set_handle(); + } + + ~aio() noexcept + { + resfd.cancel(); + syscall(idp); + } +}; + +/// Generic request control block. +struct ircd::fs::aio::request +:iocb +{ + struct read; + struct write; + + ssize_t retval {0}; + ssize_t errcode {0}; + ctx::ctx *waiter {nullptr}; + bool close_fd {false}; + bool free_req {false}; + + /// called if close_fd is true + void close_fildes() noexcept; + + /// Overriden by types of requests + virtual void handle() = 0; + + public: + // Submit a request. ctx overload will properly yield + size_t operator()(ctx::ctx &waiter); + void operator()(); + + void cancel(); + + request(const int &fd); + virtual ~request() noexcept; +}; + +ircd::fs::aio::request::request(const int &fd) +:iocb{0} +{ + assert(aioctx); + aio_flags = IOCB_FLAG_RESFD; + aio_resfd = aioctx->resfd.native_handle(); + aio_fildes = fd; +} + +/// vtable base +ircd::fs::aio::request::~request() +noexcept +{ +} + +/// Cancel a request. The handler callstack is invoked directly from here +/// which means any callback will be invoked or ctx will be notified if +/// appropriate. +void +ircd::fs::aio::request::cancel() +{ + io_event result {0}; + const auto &cb{static_cast(this)}; + + assert(aioctx); + syscall_nointr(aioctx->idp, cb, &result); + aioctx->handle_event(result); +} + +/// Submit a request and properly yield the ircd::ctx. The ctx argument +/// must be the currently running ctx (or ctx::current) for now; there is +/// no other usage. When this returns the result will be available or +/// exception will be thrown. +size_t +ircd::fs::aio::request::operator()(ctx::ctx &waiter) +try +{ + // Cooperation strategy is to set this->waiter to the ctx and wait for + // notification with this->waiter set to null to ignore spurious notes. + assert(&waiter == ctx::current); + this->waiter = &waiter; + operator()(); do + { + ctx::wait(); + } + while(this->waiter); + + if(retval == -1) + throw_system_error(errcode); + + return size_t(retval); +} +catch(const ctx::interrupted &e) +{ + // When the ctx is interrupted we're obligated to cancel the request. + // The handler callstack is invoked directly from here by cancel() for + // what it's worth but we rethrow the interrupt anyway. + this->waiter = nullptr; + cancel(); + throw; +} + +/// Submit a request. +void +ircd::fs::aio::request::operator()() +{ + struct iocb *const cbs[] + { + static_cast(this) + }; + + assert(aioctx); + syscall(aioctx->idp, 1, &cbs); +/* + log::debug("AIO request(%p) fd:%u op:%d bytes:%lu off:%ld prio:%d ctx:%p submit", + this, + this->aio_fildes, + this->aio_lio_opcode, + this->aio_nbytes, + this->aio_offset, + this->aio_reqprio, + this->waiter); +*/ +} + +void +ircd::fs::aio::set_handle() +{ + semval = 0; + const asio::mutable_buffers_1 bufs(&semval, sizeof(semval)); + auto handler{std::bind(&aio::handle, this, ph::_1, ph::_2)}; + asio::async_read(resfd, bufs, std::move(handler)); +} + +/// Handle notifications that requests are complete. +void +ircd::fs::aio::handle(const error_code &ec, + const size_t bytes) +noexcept +{ + assert((bytes == 8 && !ec && semval >= 1) || (bytes == 0 && ec)); + assert(!ec || ec.category() == asio::error::get_system_category()); + + switch(ec.value()) + { + case boost::system::errc::success: + handle_events(); + break; + + case boost::system::errc::operation_canceled: + return; + + default: + throw boost::system::system_error(ec); + } + + set_handle(); +} + +void +ircd::fs::aio::handle_events() +noexcept try +{ + std::array event; + + // The number of completed requests available in events[]. This syscall + // is restarted on EINTR. After restart, it may or may not find any ready + // events but it never blocks to do so. + const auto count + { + syscall_nointr(idp, 0, event.size(), event.data(), nullptr) + }; + + // The count should be at least 1 event. The only reason to return 0 might + // be related to an INTR; this assert will find out and may be commented. + assert(count > 0); + + for(ssize_t i(0); i < count; ++i) + handle_event(event[i]); +} +catch(const std::exception &e) +{ + log::error("AIO(%p) handle_events: %s", + this, + e.what()); +} + +void +ircd::fs::aio::handle_event(const io_event &event) +noexcept try +{ + // Our extended control block is passed in event.data + auto *const request + { + reinterpret_cast(event.data) + }; + + // The relevant iocb is repeated back to us in the result; we assert + // some basic sanity here about the layout of the request conglomerate. + assert(reinterpret_cast(event.obj) == static_cast(request)); + + // error conventions are like so + assert(event.res >= -1); // unix syscall return value semantic + assert(event.res2 >= 0); // errno code semantic + assert(event.res == -1 || event.res2 == 0); + + // Set result indicators + request->retval = event.res; + request->errcode = event.res2; +/* + log::debug("AIO request(%p) fd:%d op:%d bytes:%lu off:%ld prio:%d ctx:%p result: bytes:%ld errno:%ld", + request, + request->aio_fildes, + request->aio_lio_opcode, + request->aio_nbytes, + request->aio_offset, + request->aio_reqprio, + request->waiter, + request->retval, + request->errcode); +*/ + const unwind cleanup{[&request] + { + // The user might want us to cleanup their fd after this event + if(request->close_fd) + request->close_fildes(); + + // The user might want us to free a dynamically allocated request struct + // to simplify their callback sequence. The noexcept guarantees h + if(request->free_req) + delete request; + }}; + + // virtual dispatch based on the request type. Alternatively, we could + // switch() on the iocb lio_opcode and downcast... but that's what vtable + // does for us right here. + request->handle(); +} +catch(const std::exception &e) +{ + log::critical("Unhandled request(%lu) event(%p) error: %s", + event.data, + &event, + e.what()); +} + +/// If requested, close the file descriptor. Errors here can be logged but +/// must be otherwise ignored. +void +ircd::fs::aio::request::close_fildes() +noexcept try +{ + syscall(::close, aio_fildes); +} +catch(const std::exception &e) +{ + log::error("Failed to close request(%p) fd:%d: %s", + this, + aio_fildes); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// fs/read.h +// + +namespace ircd::fs +{ + void read__aio(const string_view &path, const mutable_raw_buffer &, const read_opts &, read_callback); + string_view read__aio(const string_view &path, const mutable_raw_buffer &, const read_opts &); + std::string read__aio(const string_view &path, const read_opts &); +} + +/// Read request control block +struct ircd::fs::aio::request::read +:request +{ + read_callback callback; + + virtual void handle() final override; + + read(const int &fd, const mutable_raw_buffer &buf, const read_opts &opts, read_callback callback); +}; + +ircd::fs::aio::request::read::read(const int &fd, + const mutable_raw_buffer &buf, + const read_opts &opts, + read_callback callback) +:request{fd} +,callback{std::move(callback)} +{ + aio_data = uintptr_t(this); + aio_reqprio = opts.priority; + aio_lio_opcode = IOCB_CMD_PREAD; + + aio_buf = uintptr_t(buffer::data(buf)); + aio_nbytes = buffer::size(buf); + aio_offset = opts.offset; +} + +void +ircd::fs::aio::request::read::handle() +{ + if(waiter) + { + ircd::ctx::notify(*waiter); + waiter = nullptr; + } + + if(callback) + { + const size_t bytes + { + retval >= 0? size_t(retval) : 0 + }; + + const string_view view + { + reinterpret_cast(aio_buf), bytes + }; + + std::exception_ptr eptr + { + errcode != 0? make_system_error(errcode) : std::exception_ptr{} + }; + + callback(std::move(eptr), view); + } +} + +// +// Interface +// + +std::string +ircd::fs::read__aio(const string_view &path, + const read_opts &opts) +{ + // Path to open(2) must be null terminated; + static thread_local char pathstr[2048]; + strlcpy(pathstr, path, sizeof(pathstr)); + + const auto fd + { + syscall(::open, pathstr, O_CLOEXEC, O_RDONLY) + }; + + const unwind cfd{[&fd] + { + syscall(::close, fd); + }}; + + // This fstat may be defeating; to be sure, don't use this overload + // and progressively buffer chunks into your application. + struct stat stat; + syscall(::fstat, fd, &stat); + const auto &size + { + stat.st_size + }; + + std::string ret(size, char{}); + const mutable_buffer buf + { + const_cast(ret.data()), ret.size() + }; + + aio::request::read request + { + int(fd), buf, opts, nullptr + }; + + const size_t bytes + { + request(ctx::cur()) + }; + + ret.resize(bytes); + return ret; +} + +ircd::string_view +ircd::fs::read__aio(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts) +{ + // Path to open(2) must be null terminated; + static thread_local char pathstr[2048]; + strlcpy(pathstr, path, sizeof(pathstr)); + + const auto fd + { + syscall(::open, pathstr, O_CLOEXEC, O_RDONLY) + }; + + const unwind cfd{[&fd] + { + syscall(::close, fd); + }}; + + aio::request::read request + { + int(fd), buf, opts, nullptr + }; + + const size_t bytes + { + request(ctx::cur()) + }; + + const string_view view + { + reinterpret_cast(data(buf)), bytes + }; + + return view; +} + +void +ircd::fs::read__aio(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts, + read_callback callback) +{ + static thread_local char pathstr[2048]; + strlcpy(pathstr, path, sizeof(pathstr)); + + const auto fd + { + syscall(::open, pathstr, O_CLOEXEC, O_RDONLY) + }; + + const unwind::exceptional cfd{[&fd] + { + syscall(::close, fd); + }}; + + auto request + { + std::make_unique(int(fd), buf, opts, std::move(callback)) + }; + + request->close_fd = true; + request->free_req = true; + (*request)(); + request.release(); +} diff --git a/ircd/fs.cc b/ircd/fs.cc index 79b611938..1d2182d8d 100644 --- a/ircd/fs.cc +++ b/ircd/fs.cc @@ -1,50 +1,43 @@ -/* - * charybdis: A slightly useful ircd. - * ircd.c: Starts up and runs the ircd. - * - * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center - * Copyright (C) 1996-2002 Hybrid Development Team - * Copyright (C) 2002-2008 ircd-ratbox development team - * Copyright (C) 2005-2013 charybdis development team - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. -#undef linux -#include +#ifdef IRCD_USE_AIO + #include "aio.h" +#endif + namespace ircd::fs { using namespace boost::filesystem; - enum - { - NAME = 0, - PATH = 1, - }; - + enum { NAME, PATH }; using ent = std::pair; extern const std::array()> paths; } +/// Non-null when aio is available for use +decltype(ircd::fs::aioctx) +ircd::fs::aioctx +{}; + decltype(ircd::fs::paths) ircd::fs::paths {{ @@ -60,15 +53,147 @@ ircd::fs::paths }}; ircd::fs::init::init() -:aio{std::make_unique()} { + #ifdef IRCD_USE_AIO + assert(!aioctx); + aioctx = new aio{}; + #endif } ircd::fs::init::~init() noexcept { + #ifdef IRCD_USE_AIO + delete aioctx; + aioctx = nullptr; + #endif + + assert(!aioctx); } +/////////////////////////////////////////////////////////////////////////////// +// +// fs/read.h +// + +namespace ircd::fs +{ + void read__std(const string_view &path, const mutable_raw_buffer &, const read_opts &, read_callback); + string_view read__std(const string_view &path, const mutable_raw_buffer &, const read_opts &); + std::string read__std(const string_view &path, const read_opts &); +} + +ircd::fs::read_opts +const ircd::fs::read_opts_default +{}; + +// +// ircd::fs interface linkage +// + +std::string +ircd::fs::read(const string_view &path, + const read_opts &opts) +{ + #ifdef IRCD_USE_AIO + if(likely(aioctx)) + return read__aio(path, opts); + #endif + + return read__std(path, opts); +} + +ircd::string_view +ircd::fs::read(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts) +{ + #ifdef IRCD_USE_AIO + if(likely(aioctx)) + return read__aio(path, buf, opts); + #endif + + return read__std(path, buf, opts); +} + +void +ircd::fs::read(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts, + read_callback callback) +{ + #ifdef IRCD_USE_AIO + if(likely(aioctx)) + return read__aio(path, buf, opts, std::move(callback)); + #endif + + return read__std(path, buf, opts, std::move(callback)); +} + +// +// std read +// + +std::string +ircd::fs::read__std(const string_view &path, + const read_opts &opts) +{ + std::ifstream file{std::string{path}}; + std::noskipws(file); + std::istream_iterator b{file}; + std::istream_iterator e{}; + return std::string{b, e}; +} + +ircd::string_view +ircd::fs::read__std(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts) +{ + string_view ret; + std::exception_ptr reptr; + read(path, buf, opts, [&reptr, &ret] + (std::exception_ptr eptr, const string_view &view) + { + reptr = std::move(eptr); + ret = view; + }); + + if(reptr) + std::rethrow_exception(reptr); + + return ret; +} + +void +ircd::fs::read__std(const string_view &path, + const mutable_raw_buffer &buf, + const read_opts &opts, + read_callback callback) +try +{ + std::ifstream file{std::string{path}}; + file.exceptions(file.failbit | file.badbit); + file.seekg(opts.offset, file.beg); + file.read(reinterpret_cast(data(buf)), size(buf)); + const string_view view + { + reinterpret_cast(data(buf)), size_t(file.gcount()) + }; + + callback(std::exception_ptr{}, view); +} +catch(...) +{ + callback(std::make_exception_ptr(std::current_exception()), string_view{}); +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// fs/write.h +// + bool ircd::fs::write(const std::string &path, const const_raw_buffer &buf) @@ -104,35 +229,6 @@ ircd::fs::append(const std::string &path, return true; } -std::string -ircd::fs::read(const std::string &path) -{ - std::ifstream file{path}; - std::noskipws(file); - std::istream_iterator b{file}; - std::istream_iterator e{}; - return std::string{b, e}; -} - -ircd::string_view -ircd::fs::read(const string_view &path, - const mutable_raw_buffer &buf) -{ - return ircd::fs::read(std::string{path}, buf); -} - -ircd::string_view -ircd::fs::read(const std::string &path, - const mutable_raw_buffer &buf) -{ - std::ifstream file{path}; - if(!file.good()) - return {}; - - file.read(reinterpret_cast(data(buf)), size(buf)); - return { reinterpret_cast(data(buf)), size_t(file.gcount()) }; -} - void ircd::fs::chdir(const std::string &path) try @@ -284,142 +380,3 @@ catch(const std::out_of_range &e) { return nullptr; } - -/////////////////////////////////////////////////////////////////////////////// -// -// linux aio -// - -#ifndef IRCD_USE_AIO - -struct ircd::fs::aio -{ - aio() {} - ~aio() noexcept {} -}; - -#else - -struct ircd::fs::aio -{ - static constexpr const size_t &MAX_EVENTS - { - 64 - }; - - asio::posix::stream_descriptor resfd - { - *ircd::ios, int(syscall(::eventfd, 0, EFD_NONBLOCK)) - }; - - aio_context_t idp - { - 0 - }; - - void handle_ready(const error_code &ec) - noexcept - { - std::array events; - - const auto nr - { - syscall(idp, 0, events.size(), events.data(), nullptr) - }; - - for(ssize_t i(0); i < nr; ++i) - { - auto &event{events[i]}; - auto &ctx{*reinterpret_cast(event.data)}; - auto &cb{*reinterpret_cast(event.obj)}; - cb.aio_data = event.res; - ircd::ctx::notify(ctx); - } - } - - std::string read(const std::string &path); - - aio(); - ~aio() noexcept; -}; - -ircd::fs::aio::aio() -{ - syscall(MAX_EVENTS, &idp); - resfd.async_wait(resfd.wait_read, std::bind(&aio::handle_ready, this, ph::_1)); - - auto test{[this] - { - const auto got{this->read("/etc/passwd")}; - std::cout << "got: " << got << std::endl; - }}; - - ircd::context - { - "aiotest", context::POST | context::DETACH, test - }; -} - -ircd::fs::aio::~aio() -noexcept -{ - syscall(idp); -} - -/// Reads the file at path into a string; yields your ircd::ctx. -/// -std::string -ircd::fs::aio::read(const std::string &path) -{ - const auto fd - { - syscall(::open, path.c_str(), O_CLOEXEC, O_RDONLY) - }; - - const unwind close{[&fd] - { - syscall(::close, fd); - }}; - - struct stat stat; - syscall(::fstat, fd, &stat); - const auto &size{stat.st_size}; - - std::string ret(size, char{}); - const mutable_buffer buf - { - const_cast(ret.data()), ret.size() - }; - - struct iocb cb{0}; - cb.aio_flags = IOCB_FLAG_RESFD; - cb.aio_reqprio = 0; - cb.aio_resfd = resfd.native_handle(); - cb.aio_data = uintptr_t(ctx::current); - - cb.aio_lio_opcode = IOCB_CMD_PREAD; - cb.aio_fildes = fd; - cb.aio_buf = uintptr_t(buffer::data(buf)); - cb.aio_nbytes = buffer::size(buf); - cb.aio_offset = 0; - - struct iocb *cbs[] - { - &cb - }; - - syscall(idp, 1, &cbs); // there - ctx::wait(); // back - - const ssize_t &bytes - { - reinterpret_cast(cb.aio_data) - }; - - assert(bytes >= 0); - ret.resize(size_t(bytes)); - - return ret; -} - -#endif