// 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 ());
}