Files
free-file-sync-mirror/FreeFileSync/Source/afs/init_curl_libssh2.cpp
2025-12-10 14:38:26 -08:00

156 lines
4.9 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "init_curl_libssh2.h"
#include <zen/thread.h>
#include <libcurl/curl_wrap.h> //DON'T include <curl/curl.h> directly!
#include <libssh2/libssh2_wrap.h> //DON'T include <libssh2_sftp.h> directly!
using namespace zen;
namespace
{
int uniInitLevel = 0; //support interleaving initialization calls! (e.g. use for libssh2 and libcurl)
//zero-initialized POD => not subject to static initialization order fiasco
void libsshCurlUnifiedInit()
{
assert(runningOnMainThread());
assert(uniInitLevel >= 0);
if (++uniInitLevel != 1) //non-atomic => require call from main thread
return;
libcurlInit(); //includes WSAStartup() also needed by libssh2
[[maybe_unused]] const int rc = ::libssh2_init(0); //includes OpenSSL-related initialization which might be needed (and hopefully won't hurt...)
assert(rc == 0); //libssh2 unconditionally returns 0 => why then have a return value in first place???
}
void libsshCurlUnifiedTearDown()
{
assert(runningOnMainThread());
assert(uniInitLevel >= 1);
if (--uniInitLevel != 0)
return;
::libssh2_exit();
libcurlTearDown();
}
}
class zen::UniSessionCounter::Impl
{
public:
void inc() //throw SysError
{
{
std::unique_lock dummy(lockCount_);
assert(sessionCount_ >= 0);
if (!newSessionsAllowed_)
throw SysError(formatSystemError("UniSessionCounter::inc", L"", L"Function call not allowed during init/shutdown."));
++sessionCount_;
}
conditionCountChanged_.notify_all();
}
void dec() //noexcept
{
{
std::unique_lock dummy(lockCount_);
assert(sessionCount_ >= 1);
--sessionCount_;
}
conditionCountChanged_.notify_all();
}
void onInitCompleted() //noexcept
{
std::unique_lock dummy(lockCount_);
newSessionsAllowed_ = true;
}
void onBeforeTearDown() //noexcept
{
std::unique_lock dummy(lockCount_);
newSessionsAllowed_ = false;
conditionCountChanged_.wait(dummy, [this] { return sessionCount_ == 0; });
}
Impl() {}
~Impl()
{
}
private:
Impl (const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
std::mutex lockCount_;
int sessionCount_ = 0;
std::condition_variable conditionCountChanged_;
bool newSessionsAllowed_ = false;
};
UniSessionCounter::UniSessionCounter() : pimpl(std::make_unique<Impl>()) {}
UniSessionCounter::~UniSessionCounter() {}
std::unique_ptr<UniSessionCounter> zen::createUniSessionCounter()
{
return std::make_unique<UniSessionCounter>();
}
class zen::UniCounterCookie
{
public:
UniCounterCookie(const std::shared_ptr<UniSessionCounter>& sessionCounter) : sessionCounter_(sessionCounter) {}
~UniCounterCookie() { sessionCounter_->pimpl->dec(); }
private:
UniCounterCookie (const UniCounterCookie&) = delete;
UniCounterCookie& operator=(const UniCounterCookie&) = delete;
const std::shared_ptr<UniSessionCounter> sessionCounter_;
};
std::shared_ptr<UniCounterCookie> zen::getLibsshCurlUnifiedInitCookie(Global<UniSessionCounter>& globalSftpSessionCount) //throw SysError
{
std::shared_ptr<UniSessionCounter> sessionCounter = globalSftpSessionCount.get();
if (!sessionCounter)
throw SysError(formatSystemError("getLibsshCurlUnifiedInitCookie", L"", L"Function call not allowed during init/shutdown.")); //=> ~UniCounterCookie() *not* called!
sessionCounter->pimpl->inc(); //throw SysError //
//pass "ownership" of having to call UniSessionCounter::dec()
return std::make_shared<UniCounterCookie>(sessionCounter); //throw SysError
}
UniInitializer::UniInitializer(UniSessionCounter& sessionCount) : sessionCount_(sessionCount)
{
libsshCurlUnifiedInit();
sessionCount_.pimpl->onInitCompleted();
}
UniInitializer::~UniInitializer()
{
//wait until all (S)FTP sessions running on detached threads have ended! otherwise they'll crash during ::WSACleanup()!
sessionCount_.pimpl->onBeforeTearDown();
/* alternatively we could use a Global<UniInitializer> and have each session own a shared_ptr<UniInitializer>:
drawback 1: SFTP clean-up may happen on worker thread => probably not supported!!!
drawback 2: cleanup will not happen when the C++ runtime on Windows kills all worker threads during shutdown */
libsshCurlUnifiedTearDown();
}