// ftpd is a server implementation based on the following: // - RFC 959 (https://tools.ietf.org/html/rfc959) // - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // // Copyright (C) 2024 Michael Theall // // 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 3 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, see . #include "fs.h" #include "IOAbstraction.h" #include "ioBuffer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__NDS__) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__) #define getline __getline #endif std::string fs::printSize (std::uint64_t const size_) { constexpr std::uint64_t const KiB = 1024; constexpr std::uint64_t const MiB = 1024 * KiB; constexpr std::uint64_t const GiB = 1024 * MiB; constexpr std::uint64_t const TiB = 1024 * GiB; constexpr std::uint64_t const PiB = 1024 * TiB; constexpr std::uint64_t const EiB = 1024 * PiB; std::array buffer{}; for (auto const &[name, bin] : { // clang-format off std::make_pair ("EiB", EiB), std::make_pair ("PiB", PiB), std::make_pair ("TiB", TiB), std::make_pair ("GiB", GiB), std::make_pair ("MiB", MiB), std::make_pair ("KiB", KiB) // clang-format on }) { // get the integral portion of the number auto const whole = size_ / bin; if (size_ >= 100 * bin) { // >= 100, print xxxXiB std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "%s", whole, name); return {buffer.data (), size}; } // get the fractional portion of the number auto const frac = size_ - (whole * bin); if (size_ >= 10 * bin) { // >= 10, print xx.xXiB std::size_t const size = std::sprintf ( buffer.data (), "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name); return {buffer.data (), size}; } if (size_ >= 1000 * (bin / KiB)) { // >= 1000 of lesser bin, print x.xxXiB std::size_t const size = std::sprintf ( buffer.data (), "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name); return {buffer.data (), size}; } } // < 1KiB, just print the number std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "B", size_); return {buffer.data (), size}; } /////////////////////////////////////////////////////////////////////////// fs::File::~File () { std::free (m_lineBuffer); } fs::File::File () = default; fs::File::File (File &&that_) = default; fs::File &fs::File::operator= (File &&that_) = default; fs::File::operator bool () const { return static_cast (m_fp); } fs::File::operator FILE * () const { return m_fp.get (); } void fs::File::setBufferSize (std::size_t const size_) { if (m_buffer.size () != size_) m_buffer.resize (size_); if (m_fp) (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ()); } bool fs::File::open (gsl::not_null const path_, gsl::not_null const mode_) { gsl::owner fp = IOAbstraction::fopen (path_, mode_); if (!fp) return false; m_fp = std::unique_ptr (fp, &std::fclose); if (!m_buffer.empty ()) (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ()); return true; } void fs::File::close () { m_fp.reset (); } std::make_signed_t fs::File::seek (std::make_signed_t const pos_, int const origin_) { return IOAbstraction::fseek (m_fp.get (), pos_, origin_); } std::make_signed_t fs::File::read (gsl::not_null const buffer_, std::size_t const size_) { assert (buffer_); assert (size_ > 0); auto const rc = IOAbstraction::fread (buffer_, 1, size_, m_fp.get ()); if (rc == 0) { if (std::feof (m_fp.get ())) return 0; return -1; } return gsl::narrow_cast> (rc); } std::make_signed_t fs::File::read (IOBuffer &buffer_) { assert (buffer_.freeSize () > 0); auto const rc = read (buffer_.freeArea (), buffer_.freeSize ()); if (rc > 0) buffer_.markUsed (rc); return rc; } std::string_view fs::File::readLine () { while (true) { auto rc = ::getline (&m_lineBuffer, &m_lineBufferSize, m_fp.get ()); if (rc < 0) return {}; while (rc > 0) { if (m_lineBuffer[rc - 1] != '\r' && m_lineBuffer[rc - 1] != '\n') break; m_lineBuffer[--rc] = 0; } if (rc > 0) return {m_lineBuffer, gsl::narrow_cast (rc)}; } } bool fs::File::readAll (gsl::not_null const buffer_, std::size_t const size_) { assert (buffer_); assert (size_ > 0); auto const p = static_cast (buffer_.get ()); std::size_t bytes = 0; while (bytes < size_) { auto const rc = read (p + bytes, size_ - bytes); if (rc <= 0) return false; bytes += rc; } return true; } std::make_signed_t fs::File::write (gsl::not_null const buffer_, std::size_t const size_) { assert (buffer_); assert (size_ > 0); auto const rc = IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ()); if (rc == 0) return -1; return gsl::narrow_cast> (rc); } std::make_signed_t fs::File::write (IOBuffer &buffer_) { assert (buffer_.usedSize () > 0); auto const rc = write (buffer_.usedArea (), buffer_.usedSize ()); if (rc > 0) buffer_.markFree (rc); return rc; } bool fs::File::writeAll (gsl::not_null const buffer_, std::size_t const size_) { assert (buffer_); assert (size_ > 0); auto const p = static_cast (buffer_.get ()); std::size_t bytes = 0; while (bytes < size_) { auto const rc = write (p + bytes, size_ - bytes); if (rc <= 0) return false; bytes += rc; } return true; } /////////////////////////////////////////////////////////////////////////// fs::Dir::~Dir () = default; fs::Dir::Dir () = default; fs::Dir::Dir (Dir &&that_) = default; fs::Dir &fs::Dir::operator= (Dir &&that_) = default; fs::Dir::operator bool () const { return static_cast (m_dp); } fs::Dir::operator DIR * () const { return m_dp.get (); } bool fs::Dir::open (gsl::not_null const path_) { auto const dp = IOAbstraction::opendir (path_); if (!dp) return false; m_dp = std::unique_ptr (dp, &IOAbstraction::closedir); return true; } void fs::Dir::close () { m_dp.reset (); } dirent *fs::Dir::read () { errno = 0; return IOAbstraction::readdir (m_dp.get ()); }