diff --git a/src/Makefile.am b/src/Makefile.am index cb6e3b7c..df58792b 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/RpcMethodFactory.cc b/src/RpcMethodFactory.cc index bafbffd8..66eb5e8a 100644 --- a/src/RpcMethodFactory.cc +++ b/src/RpcMethodFactory.cc @@ -88,6 +88,7 @@ std::vector rpcMethodNames = { "aria2.forceShutdown", "aria2.getGlobalStat", "aria2.saveSession", + "aria2.getDiskspaceInformation", "system.multicall", "system.listMethods", "system.listNotifications", @@ -251,6 +252,10 @@ std::unique_ptr createMethod(const std::string& methodName) return make_unique(); } + if (methodName == GetDiskspaceInformationRpcMethod::getMethodName()) { + return make_unique(); + } + if (methodName == SystemMulticallRpcMethod::getMethodName()) { return make_unique(); } diff --git a/src/RpcMethodImpl.cc b/src/RpcMethodImpl.cc index c2249202..a75d0463 100644 --- a/src/RpcMethodImpl.cc +++ b/src/RpcMethodImpl.cc @@ -95,58 +95,62 @@ const char VLB_COMPLETE[] = "complete"; const char VLB_USED[] = "used"; const char VLB_ZERO[] = "0"; -const char KEY_GID[] = "gid"; +const char KEY_AM_CHOKING[] = "amChoking"; +const char KEY_ANNOUNCE_LIST[] = "announceList"; +const char KEY_BELONGS_TO[] = "belongsTo"; +const char KEY_BITFIELD[] = "bitfield"; +const char KEY_BITTORRENT[] = "bittorrent"; +const char KEY_COMMENT[] = "comment"; +const char KEY_COMPLETED_LENGTH[] = "completedLength"; +const char KEY_CONNECTIONS[] = "connections"; +const char KEY_CREATION_DATE[] = "creationDate"; +const char KEY_CURRENT_URI[] = "currentUri"; +const char KEY_DIR[] = "dir"; +const char KEY_DISKSPACE_AVAILABLE[] = "available"; +const char KEY_DISKSPACE_CAPACITY[] = "capacity"; +const char KEY_DISKSPACE_FREE[] = "free"; +const char KEY_DISKSPACE_PATH[] = "path"; +const char KEY_DOWNLOAD_SPEED[] = "downloadSpeed"; +const char KEY_ENABLED_FEATURES[] = "enabledFeatures"; const char KEY_ERROR_CODE[] = "errorCode"; const char KEY_ERROR_MESSAGE[] = "errorMessage"; -const char KEY_STATUS[] = "status"; -const char KEY_TOTAL_LENGTH[] = "totalLength"; -const char KEY_COMPLETED_LENGTH[] = "completedLength"; -const char KEY_DOWNLOAD_SPEED[] = "downloadSpeed"; -const char KEY_UPLOAD_SPEED[] = "uploadSpeed"; -const char KEY_UPLOAD_LENGTH[] = "uploadLength"; -const char KEY_CONNECTIONS[] = "connections"; -const char KEY_BITFIELD[] = "bitfield"; -const char KEY_PIECE_LENGTH[] = "pieceLength"; -const char KEY_NUM_PIECES[] = "numPieces"; +const char KEY_FILES[] = "files"; const char KEY_FOLLOWED_BY[] = "followedBy"; const char KEY_FOLLOWING[] = "following"; -const char KEY_BELONGS_TO[] = "belongsTo"; -const char KEY_INFO_HASH[] = "infoHash"; -const char KEY_NUM_SEEDERS[] = "numSeeders"; -const char KEY_PEER_ID[] = "peerId"; -const char KEY_IP[] = "ip"; -const char KEY_PORT[] = "port"; -const char KEY_AM_CHOKING[] = "amChoking"; -const char KEY_PEER_CHOKING[] = "peerChoking"; -const char KEY_SEEDER[] = "seeder"; +const char KEY_GID[] = "gid"; const char KEY_INDEX[] = "index"; -const char KEY_PATH[] = "path"; -const char KEY_SELECTED[] = "selected"; -const char KEY_LENGTH[] = "length"; -const char KEY_URI[] = "uri"; -const char KEY_CURRENT_URI[] = "currentUri"; -const char KEY_VERSION[] = "version"; -const char KEY_ENABLED_FEATURES[] = "enabledFeatures"; -const char KEY_METHOD_NAME[] = "methodName"; -const char KEY_PARAMS[] = "params"; -const char KEY_SESSION_ID[] = "sessionId"; -const char KEY_FILES[] = "files"; -const char KEY_DIR[] = "dir"; -const char KEY_URIS[] = "uris"; -const char KEY_BITTORRENT[] = "bittorrent"; const char KEY_INFO[] = "info"; -const char KEY_NAME[] = "name"; -const char KEY_ANNOUNCE_LIST[] = "announceList"; -const char KEY_COMMENT[] = "comment"; -const char KEY_CREATION_DATE[] = "creationDate"; +const char KEY_INFO_HASH[] = "infoHash"; +const char KEY_IP[] = "ip"; +const char KEY_LENGTH[] = "length"; +const char KEY_METHOD_NAME[] = "methodName"; const char KEY_MODE[] = "mode"; -const char KEY_SERVERS[] = "servers"; -const char KEY_NUM_WAITING[] = "numWaiting"; -const char KEY_NUM_STOPPED[] = "numStopped"; +const char KEY_NAME[] = "name"; const char KEY_NUM_ACTIVE[] = "numActive"; +const char KEY_NUM_PIECES[] = "numPieces"; +const char KEY_NUM_SEEDERS[] = "numSeeders"; +const char KEY_NUM_STOPPED[] = "numStopped"; const char KEY_NUM_STOPPED_TOTAL[] = "numStoppedTotal"; +const char KEY_NUM_WAITING[] = "numWaiting"; +const char KEY_PARAMS[] = "params"; +const char KEY_PATH[] = "path"; +const char KEY_PEER_CHOKING[] = "peerChoking"; +const char KEY_PEER_ID[] = "peerId"; +const char KEY_PIECE_LENGTH[] = "pieceLength"; +const char KEY_PORT[] = "port"; +const char KEY_SEEDER[] = "seeder"; +const char KEY_SELECTED[] = "selected"; +const char KEY_SERVERS[] = "servers"; +const char KEY_SESSION_ID[] = "sessionId"; +const char KEY_STATUS[] = "status"; +const char KEY_TOTAL_LENGTH[] = "totalLength"; +const char KEY_UPLOAD_LENGTH[] = "uploadLength"; +const char KEY_UPLOAD_SPEED[] = "uploadSpeed"; +const char KEY_URIS[] = "uris"; +const char KEY_URI[] = "uri"; const char KEY_VERIFIED_LENGTH[] = "verifiedLength"; const char KEY_VERIFY_PENDING[] = "verifyIntegrityPending"; +const char KEY_VERSION[] = "version"; } // namespace namespace { @@ -1410,6 +1414,72 @@ std::unique_ptr SaveSessionRpcMethod::process(const RpcRequest& req, fmt("Failed to serialize session to '%s'.", filename.c_str())); } +std::unique_ptr +GetDiskspaceInformationRpcMethod::process(const RpcRequest& req, + DownloadEngine* e) +{ + auto result = Dict::g(); + const List* gidsParam = checkParam(req, 0); + std::map known; + std::error_code ec; + + if (gidsParam) { + for (auto& elem : *gidsParam) { + const String* sgid = downcast(elem); + if (sgid) { + a2_gid_t gid = str2Gid(sgid); + auto group = e->getRequestGroupMan()->findGroup(gid); + if (!group) { + throw DL_ABORT_EX( + fmt("Diskspace information requested for Invalid GID#%s", + GroupId::toHex(gid).c_str())); + } + auto dctx = group->getDownloadContext(); + if (!dctx) { + throw DL_ABORT_EX( + fmt("Diskspace information requested for broken GID#%s", + GroupId::toHex(gid).c_str())); + } + auto path = dctx->getBasePath(); + if (path.empty()) { + auto option = group->getOption(); + if (!option->blank(PREF_OUT)) { + path = util::applyDir(option->get(PREF_DIR), option->get(PREF_OUT)); + } + else { + path = option->get(PREF_DIR); + } + } + auto ds = util::filesystem::space_downwards(path.c_str(), ec); + if (ec) { + throw DL_ABORT_EX( + fmt("Diskspace information returned failure for GID#%s", + GroupId::toHex(gid).c_str())); + } + auto sizes = Dict::g(); + sizes->put(KEY_DISKSPACE_PATH, String::g(path)); + sizes->put(KEY_DISKSPACE_AVAILABLE, Integer::g(ds.available)); + sizes->put(KEY_DISKSPACE_CAPACITY, Integer::g(ds.capacity)); + sizes->put(KEY_DISKSPACE_FREE, Integer::g(ds.free)); + result->put(sgid->s(), std::move(sizes)); + } + } + } + else { + auto ds = util::filesystem::space_downwards(nullptr, ec); + if (ec) { + throw DL_ABORT_EX("Diskspace information returned failure"); + } + auto sizes = Dict::g(); + sizes->put(KEY_DISKSPACE_PATH, String::g(".")); + sizes->put(KEY_DISKSPACE_AVAILABLE, Integer::g(ds.available)); + sizes->put(KEY_DISKSPACE_CAPACITY, Integer::g(ds.capacity)); + sizes->put(KEY_DISKSPACE_FREE, Integer::g(ds.free)); + result->put("aria2", std::move(sizes)); + } + return std::move(result); +} + std::unique_ptr SystemMulticallRpcMethod::process(const RpcRequest& req, DownloadEngine* e) { diff --git a/src/RpcMethodImpl.h b/src/RpcMethodImpl.h index de6d7685..13e9e91a 100644 --- a/src/RpcMethodImpl.h +++ b/src/RpcMethodImpl.h @@ -516,6 +516,15 @@ public: static const char* getMethodName() { return "aria2.saveSession"; } }; +class GetDiskspaceInformationRpcMethod : public RpcMethod { +protected: + virtual std::unique_ptr process(const RpcRequest& req, + DownloadEngine* e) CXX11_OVERRIDE; + +public: + static const char* getMethodName() { return "aria2.getDiskspaceInformation"; } +}; + class SystemMulticallRpcMethod : public RpcMethod { protected: virtual std::unique_ptr process(const RpcRequest& req, diff --git a/src/util.h b/src/util.h index bb7d8d3e..4a23f99d 100644 --- a/src/util.h +++ b/src/util.h @@ -53,6 +53,7 @@ #include #include #include +#include #include "a2time.h" #include "a2netcompat.h" @@ -879,6 +880,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 49c6211f..9a7e08a4 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