From e98f3a558747a9cab208c7e6ce2900e5de5938d0 Mon Sep 17 00:00:00 2001 From: Nils Maier Date: Fri, 16 Dec 2016 20:16:37 +0100 Subject: [PATCH] Implement a somewhat compatible std::experimental::filesystem::space Also implement space_downwards which will looks downwards in the tree of the provided path for the first existing parent and return the disk space for that. --- src/Makefile.am | 1 + src/util.h | 19 ++++++++ src/util_fs.cc | 119 +++++++++++++++++++++++++++++++++++++++++++++ test/Makefile.am | 1 + test/UtilFsTest.cc | 116 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 src/util_fs.cc create mode 100644 test/UtilFsTest.cc diff --git a/src/Makefile.am b/src/Makefile.am index 9322112c..18ebe10a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -255,6 +255,7 @@ SRCS = \ uri_split.c uri_split.h\ usage_text.h\ util.cc util.h\ + util_fs.cc\ util_security.cc util_security.h\ ValueBase.cc ValueBase.h\ ValueBaseDiskWriter.h\ diff --git a/src/util.h b/src/util.h index 76ecb549..9e975551 100644 --- a/src/util.h +++ b/src/util.h @@ -53,6 +53,7 @@ #include #include #include +#include #include "a2time.h" #include "a2netcompat.h" @@ -875,6 +876,24 @@ void make_fd_cloexec(int fd); bool gainPrivilege(LPCTSTR privName); #endif // __MINGW32__ +// Basically std::experimental::filesystem, to be replaced later when the +// filesystem stdlib becomes stable and gains wide compiler support + +namespace filesystem { +struct space_info { + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +space_info space(const char* path, std::error_code& code); + +// Progress downwards in the provided path, yielding a result for the first +// directory that exists. +space_info space_downwards(const char* path, std::error_code& code); + +} // namespace filesystem + } // namespace util } // namespace aria2 diff --git a/src/util_fs.cc b/src/util_fs.cc new file mode 100644 index 00000000..612e75fe --- /dev/null +++ b/src/util_fs.cc @@ -0,0 +1,119 @@ +/* */ + +#include "a2io.h" +#include "util.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else // _WIN32 +#include +#endif + +namespace aria2 { +namespace util { +namespace filesystem { + +space_info space(const char* path, std::error_code& ec) +{ + space_info rv{static_cast(-1), static_cast(-1), + static_cast(-1)}; + if (!path || !*path) { + path = "."; + } +#ifdef _WIN32 + ULARGE_INTEGER sp_avail, sp_free, sp_cap; + auto wpath = utf8ToWChar(path); + if (GetDiskFreeSpaceExW(wpath.c_str(), &sp_avail, &sp_cap, &sp_free)) { + rv.capacity = static_cast(sp_cap.QuadPart); + rv.available = static_cast(sp_avail.QuadPart); + rv.free = static_cast(sp_free.QuadPart); + ec.clear(); + } + else { + ec.assign(GetLastError(), std::system_category()); + } +#else // _WIN32 + struct statvfs st; + if (!statvfs(path, &st)) { + rv.capacity = static_cast(st.f_blocks) * st.f_frsize; + rv.free = static_cast(st.f_bfree) * st.f_frsize; + rv.available = static_cast(st.f_bavail) * st.f_frsize; + ec.clear(); + } + else { + ec.assign(errno, std::system_category()); + } +#endif // _WIN32 + + return rv; +} + +space_info space_downwards(const char* path, std::error_code& ec) +{ + auto rv = space(path, ec); + if (!ec) { + return rv; + } + std::string spath(path); + for (;;) { + if (spath.empty()) { + break; + } +#if _WIN32 + if (spath == "\\\\") { + // raw UNC prefix + break; + } +#endif + auto pos = spath.find_last_of("/\\"); + if (pos == std::string::npos) { + spath = ""; + } + else { + spath = spath.substr(0, pos); + } + rv = space(spath.c_str(), ec); + if (!ec) { + break; + } + } + return rv; +} + +} // namespace filesystem +} // namespace util +} // namespace aria2 diff --git a/test/Makefile.am b/test/Makefile.am index c7114449..d39dcde3 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,6 +17,7 @@ aria2c_SOURCES = AllTest.cc\ RequestGroupTest.cc\ UtilTest1.cc\ UtilTest2.cc\ + UtilFsTest.cc\ UtilSecurityTest.cc\ UriListParserTest.cc\ HttpHeaderProcessorTest.cc\ diff --git a/test/UtilFsTest.cc b/test/UtilFsTest.cc new file mode 100644 index 00000000..e7e886d6 --- /dev/null +++ b/test/UtilFsTest.cc @@ -0,0 +1,116 @@ +#include "util.h" + +#include +#include + +#ifdef _WIN32 +static char* mkdtemp(char* tpl) +{ + char* dn = mktemp(tpl); + if (!dn) { + return dn; + } + if (mkdir(dn)) { + return nullptr; + } + return dn; +} +#endif // _WIN32 + +namespace aria2 { + +class UtilFsTest : public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(UtilFsTest); + CPPUNIT_TEST(testSpace); + CPPUNIT_TEST(testSpacePwd); + CPPUNIT_TEST(testSpaceDownwardsFile); + CPPUNIT_TEST(testSpaceDownwardsDir); + CPPUNIT_TEST_SUITE_END(); + +private: +public: + void setUp() {} + + void testSpace(); + void testSpacePwd(); + void testSpaceDownwardsFile(); + void testSpaceDownwardsDir(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(UtilFsTest); + +void UtilFsTest::testSpace() +{ + const char* tmpl = "aria2.test.tmp.XXXXXX"; + char* tpl = strdup(tmpl); // lets just leak this + CPPUNIT_ASSERT(tpl); + char* tmp = mkdtemp(tpl); + CPPUNIT_ASSERT(tmp); + std::error_code ec; + util::filesystem::space(tmp, ec); + CPPUNIT_ASSERT(!ec); + rmdir(tmp); + auto rv = util::filesystem::space(tmp, ec); + CPPUNIT_ASSERT(ec); + CPPUNIT_ASSERT_EQUAL(rv.available, static_cast(-1)); + CPPUNIT_ASSERT_EQUAL(rv.capacity, static_cast(-1)); + CPPUNIT_ASSERT_EQUAL(rv.free, static_cast(-1)); +} + +void UtilFsTest::testSpacePwd() +{ + std::error_code ec; + util::filesystem::space(nullptr, ec); + CPPUNIT_ASSERT(!ec); + util::filesystem::space("", ec); + CPPUNIT_ASSERT(!ec); + util::filesystem::space(".", ec); + CPPUNIT_ASSERT(!ec); + util::filesystem::space("doesnotexit", ec); + CPPUNIT_ASSERT(ec); + util::filesystem::space_downwards("doesnotexit", ec); + CPPUNIT_ASSERT(!ec); +} + +void UtilFsTest::testSpaceDownwardsFile() +{ + const char* tmpl = "aria2.test.tmp.XXXXXX"; + char* tpl = strdup(tmpl); // lets just leak this + CPPUNIT_ASSERT(tpl); + char* tmp = mkdtemp(tpl); + CPPUNIT_ASSERT(tmp); + std::string tn(tmp); + tn += "/aria2.tmp"; + { + std::ofstream s(tn); + std::error_code ec; + std::string tn2(tn); + tn2 += "/something.else.entirely"; + util::filesystem::space(tn2.c_str(), ec); + CPPUNIT_ASSERT_MESSAGE(tn2, ec); + util::filesystem::space_downwards(tn2.c_str(), ec); + CPPUNIT_ASSERT_MESSAGE(tn2, !ec); + } + unlink(tn.c_str()); + rmdir(tmp); +} + +void UtilFsTest::testSpaceDownwardsDir() +{ + const char* tmpl = "aria2.test.tmp.XXXXXX"; + char* tpl = strdup(tmpl); // lets just leak this + CPPUNIT_ASSERT(tpl); + char* tmp = mkdtemp(tpl); + CPPUNIT_ASSERT(tmp); + std::string tn(tmp); + tn += "/something.else.entirely"; + std::error_code ec; + auto rv = util::filesystem::space(tn.c_str(), ec); + CPPUNIT_ASSERT_MESSAGE(tn, ec); + rv = util::filesystem::space_downwards(tn.c_str(), ec); + rmdir(tmp); + CPPUNIT_ASSERT_MESSAGE(tn, !ec); +} + +} // namespace aria2