refactor: remove varargs from libtransmission (#2876)

* refactor: do not use va_arg in peer-msgs.cc

* refactor: do not use va_arg in platform.cc

* build: enable cert-dcl50-cpp clang-tidy warning

* refactor: use some tr_pathbuf where sensible
This commit is contained in:
Charles Kerr 2022-04-04 20:37:12 -05:00 committed by GitHub
parent 738431169c
commit a71f0c762d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 239 additions and 277 deletions

View File

@ -7,7 +7,6 @@
#include <ctype.h> /* isxdigit() */
#include <errno.h>
#include <limits.h> /* INT_MAX */
#include <stdarg.h>
#include <giomm.h> /* g_file_trash() */
#include <glibmm/i18n.h>

View File

@ -7,7 +7,6 @@ Checks: >
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions,
cert-*,
-cert-dcl50-cpp,
-cert-err33-c,
-cert-err34-c,
-cert-err58-cpp,

View File

@ -5,7 +5,6 @@
#include <algorithm>
#include <cerrno>
#include <cstdarg>
#include <cstring>
#include <ctime>
#include <memory> // std::unique_ptr
@ -658,30 +657,25 @@ tr_peerMsgs* tr_peerMsgsNew(tr_torrent* torrent, peer_atom* atom, tr_peerIo* io,
***
**/
static void myDebug(char const* file, int line, tr_log_level level, tr_peerMsgsImpl const* msgs, char const* fmt, ...)
{
if (!tr_logLevelIsActive(level))
{
return;
}
#define myLogMacro(msgs, level, text) \
do \
{ \
if (tr_logGetLevel() >= (level)) \
{ \
tr_logAddMessage( \
__FILE__, \
__LINE__, \
(level), \
fmt::format(FMT_STRING("{:s} [{:s}]: {:s}"), (msgs)->io->addrStr(), (msgs)->client, text), \
(msgs)->torrent->name()); \
} \
} while (0)
va_list args;
char addrstr[TR_ADDRSTRLEN];
auto* const buf = evbuffer_new();
evbuffer_add_printf(buf, "%s [%s]: ", tr_peerIoGetAddrStr(msgs->io, addrstr, sizeof(addrstr)), msgs->client.c_str());
va_start(args, fmt);
evbuffer_add_vprintf(buf, fmt, args);
va_end(args);
auto const message = evbuffer_free_to_str(buf);
tr_logAddMessage(file, line, level, tr_torrentName(msgs->torrent), message);
}
#define logdbg(msgs, ...) myDebug(__FILE__, __LINE__, TR_LOG_DEBUG, msgs, __VA_ARGS__)
#define logtrace(msgs, ...) myDebug(__FILE__, __LINE__, TR_LOG_TRACE, msgs, __VA_ARGS__)
#define logdbg(msgs, text) myLogMacro(msgs, TR_LOG_DEBUG, text)
#define logtrace(msgs, text) myLogMacro(msgs, TR_LOG_TRACE, text)
/**
***
***j
**/
static void pokeBatchPeriod(tr_peerMsgsImpl* msgs, int interval)
@ -689,13 +683,13 @@ static void pokeBatchPeriod(tr_peerMsgsImpl* msgs, int interval)
if (msgs->outMessagesBatchPeriod > interval)
{
msgs->outMessagesBatchPeriod = interval;
logtrace(msgs, "lowering batch interval to %d seconds", interval);
logtrace(msgs, fmt::format(FMT_STRING("lowering batch interval to {:d} seconds"), interval));
}
}
static void dbgOutMessageLen(tr_peerMsgsImpl* msgs)
{
logtrace(msgs, "outMessage size is now %zu", evbuffer_get_length(msgs->outMessages));
logtrace(msgs, fmt::format(FMT_STRING("outMessage size is now {:d}"), evbuffer_get_length(msgs->outMessages)));
}
static void protocolSendReject(tr_peerMsgsImpl* msgs, struct peer_request const* req)
@ -710,7 +704,7 @@ static void protocolSendReject(tr_peerMsgsImpl* msgs, struct peer_request const*
evbuffer_add_uint32(out, req->offset);
evbuffer_add_uint32(out, req->length);
logtrace(msgs, "rejecting %u:%u->%u...", req->index, req->offset, req->length);
logtrace(msgs, fmt::format(FMT_STRING("rejecting {:d}:{:d}->{:d}..."), req->index, req->offset, req->length));
dbgOutMessageLen(msgs);
}
@ -724,7 +718,7 @@ static void protocolSendRequest(tr_peerMsgsImpl* msgs, struct peer_request const
evbuffer_add_uint32(out, req.offset);
evbuffer_add_uint32(out, req.length);
logtrace(msgs, "requesting %u:%u->%u...", req.index, req.offset, req.length);
logtrace(msgs, fmt::format(FMT_STRING("requesting {:d}:{:d}->{:d}..."), req.index, req.offset, req.length));
dbgOutMessageLen(msgs);
pokeBatchPeriod(msgs, ImmediatePriorityIntervalSecs);
}
@ -739,7 +733,7 @@ static void protocolSendCancel(tr_peerMsgsImpl* msgs, peer_request const& req)
evbuffer_add_uint32(out, req.offset);
evbuffer_add_uint32(out, req.length);
logtrace(msgs, "cancelling %u:%u->%u...", req.index, req.offset, req.length);
logtrace(msgs, fmt::format(FMT_STRING("cancelling {:d}:{:d}->{:d}..."), req.index, req.offset, req.length));
dbgOutMessageLen(msgs);
pokeBatchPeriod(msgs, ImmediatePriorityIntervalSecs);
}
@ -748,7 +742,7 @@ static void protocolSendPort(tr_peerMsgsImpl* msgs, uint16_t port)
{
struct evbuffer* out = msgs->outMessages;
logtrace(msgs, "sending Port %u", port);
logtrace(msgs, fmt::format(FMT_STRING("sending Port {:d}"), port));
evbuffer_add_uint32(out, 3);
evbuffer_add_uint8(out, BtPeerMsgs::Port);
evbuffer_add_uint16(out, port);
@ -762,7 +756,7 @@ static void protocolSendHave(tr_peerMsgsImpl* msgs, tr_piece_index_t index)
evbuffer_add_uint8(out, BtPeerMsgs::Have);
evbuffer_add_uint32(out, index);
logtrace(msgs, "sending Have %u", index);
logtrace(msgs, fmt::format(FMT_STRING("sending Have {:d}"), index));
dbgOutMessageLen(msgs);
pokeBatchPeriod(msgs, LowPriorityIntervalSecs);
}
@ -793,7 +787,7 @@ static void protocolSendChoke(tr_peerMsgsImpl* msgs, bool choke)
evbuffer_add_uint32(out, sizeof(uint8_t));
evbuffer_add_uint8(out, choke ? BtPeerMsgs::Choke : BtPeerMsgs::Unchoke);
logtrace(msgs, "sending %s...", choke ? "Choke" : "Unchoke");
logtrace(msgs, choke ? "sending choke" : "sending unchoked");
dbgOutMessageLen(msgs);
pokeBatchPeriod(msgs, ImmediatePriorityIntervalSecs);
}
@ -920,7 +914,7 @@ static void sendInterest(tr_peerMsgsImpl* msgs, bool b)
struct evbuffer* out = msgs->outMessages;
logtrace(msgs, "Sending %s", b ? "Interested" : "Not Interested");
logtrace(msgs, b ? "Sending Interested" : "Sending Not Interested");
evbuffer_add_uint32(out, sizeof(uint8_t));
evbuffer_add_uint8(out, b ? BtPeerMsgs::Interested : BtPeerMsgs::NotInterested);
@ -1118,11 +1112,11 @@ static void parseLtepHandshake(tr_peerMsgsImpl* msgs, uint32_t len, struct evbuf
/* arbitrary limit, should be more than enough */
if (len <= 4096)
{
logtrace(msgs, "here is the handshake: [%*.*s]", TR_ARG_TUPLE(int(len), int(len), tmp));
logtrace(msgs, fmt::format(FMT_STRING("here is the handshake: [{:{}.{}s}]"), tmp, len, len));
}
else
{
logtrace(msgs, "handshake length is too big (%" PRIu32 "), printing skipped", len);
logtrace(msgs, fmt::format(FMT_STRING("handshake length is too big ({:d}), printing skipped"), len));
}
/* does the peer prefer encrypted connections? */
@ -1148,14 +1142,14 @@ static void parseLtepHandshake(tr_peerMsgsImpl* msgs, uint32_t len, struct evbuf
{
msgs->peerSupportsPex = i != 0;
msgs->ut_pex_id = (uint8_t)i;
logtrace(msgs, "msgs->ut_pex is %d", int(msgs->ut_pex_id));
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_pex is {:d}"), static_cast<int>(msgs->ut_pex_id)));
}
if (tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &i))
{
msgs->peerSupportsMetadataXfer = i != 0;
msgs->ut_metadata_id = (uint8_t)i;
logtrace(msgs, "msgs->ut_metadata_id is %d", int(msgs->ut_metadata_id));
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_metadata_id is {:d}"), static_cast<int>(msgs->ut_metadata_id)));
}
if (tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &i))
@ -1183,7 +1177,7 @@ static void parseLtepHandshake(tr_peerMsgsImpl* msgs, uint32_t len, struct evbuf
{
pex.port = htons((uint16_t)i);
msgs->publishClientGotPort(pex.port);
logtrace(msgs, "peer's port is now %d", int(i));
logtrace(msgs, fmt::format(FMT_STRING("peer's port is now {:d}"), i));
}
uint8_t const* addr = nullptr;
@ -1232,7 +1226,9 @@ static void parseUtMetadata(tr_peerMsgsImpl* msgs, uint32_t msglen, struct evbuf
tr_variantFree(&dict);
}
logtrace(msgs, "got ut_metadata msg: type %d, piece %d, total_size %d", int(msg_type), int(piece), int(total_size));
logtrace(
msgs,
fmt::format(FMT_STRING("got ut_metadata msg: type {:d}, piece {:d}, total_size {:d}"), msg_type, piece, total_size));
if (msg_type == MetadataMsgType::Reject)
{
@ -1367,7 +1363,7 @@ static void parseLtep(tr_peerMsgsImpl* msgs, uint32_t msglen, struct evbuffer* i
}
else
{
logtrace(msgs, "skipping unknown ltep message (%d)", int(ltep_msgid));
logtrace(msgs, fmt::format(FMT_STRING("skipping unknown ltep message ({:d})"), static_cast<int>(ltep_msgid)));
evbuffer_drain(inbuf, msglen);
}
}
@ -1406,7 +1402,9 @@ static ReadState readBtId(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, size_t
auto id = uint8_t{};
tr_peerIoReadUint8(msgs->io, inbuf, &id);
msgs->incoming.id = id;
logtrace(msgs, "msgs->incoming.id is now %d; msgs->incoming.length is %zu", id, (size_t)msgs->incoming.length);
logtrace(
msgs,
fmt::format(FMT_STRING("msgs->incoming.id is now {:d}: msgs->incoming.length is {:d}"), id, msgs->incoming.length));
if (id == BtPeerMsgs::Piece)
{
@ -1565,7 +1563,9 @@ static ReadState readBtPiece(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, size
tr_peerIoReadUint32(msgs->io, inbuf, &req->index);
tr_peerIoReadUint32(msgs->io, inbuf, &req->offset);
req->length = msgs->incoming.length - 9;
logtrace(msgs, "got incoming block header %u:%u->%u", req->index, req->offset, req->length);
logtrace(
msgs,
fmt::format(FMT_STRING("got incoming block header {:d}:{:d}->{:d}"), req->index, req->offset, req->length));
return READ_NOW;
}
@ -1586,12 +1586,13 @@ static ReadState readBtPiece(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, size
*setme_piece_bytes_read += n;
logtrace(
msgs,
"got %zu bytes for block %u:%u->%u ... %d remain",
n,
req->index,
req->offset,
req->length,
int(req->length - evbuffer_get_length(block_buffer)));
fmt::format(
FMT_STRING("got {:d} bytes for block {:d}:{:d}->{:d} ... {:d} remain"),
n,
req->index,
req->offset,
req->length,
req->length - evbuffer_get_length(block_buffer)));
if (evbuffer_get_length(block_buffer) < req->length)
{
@ -1623,7 +1624,9 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
--msglen; /* id length */
logtrace(msgs, "got BT id %d, len %d, buffer size is %zu", int(id), int(msglen), inlen);
logtrace(
msgs,
fmt::format(FMT_STRING("got BT id {:d}, len {:d}, buffer size is {:d}"), static_cast<int>(id), msglen, inlen));
if (inlen < msglen)
{
@ -1632,7 +1635,9 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
if (!messageLengthIsCorrect(msgs, id, msglen + 1))
{
logdbg(msgs, "bad packet - BT message #%d with a length of %d", int(id), int(msglen));
logdbg(
msgs,
fmt::format(FMT_STRING("bad packet - BT message #{:d} with a length of {:d}"), static_cast<int>(id), msglen));
msgs->publishError(EMSGSIZE);
return READ_ERR;
}
@ -1672,7 +1677,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
case BtPeerMsgs::Have:
tr_peerIoReadUint32(msgs->io, inbuf, &ui32);
logtrace(msgs, "got Have: %u", ui32);
logtrace(msgs, fmt::format(FMT_STRING("got Have: {:d}"), ui32));
if (msgs->torrent->hasMetainfo() && ui32 >= msgs->torrent->pieceCount())
{
@ -1708,7 +1713,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
tr_peerIoReadUint32(msgs->io, inbuf, &r.index);
tr_peerIoReadUint32(msgs->io, inbuf, &r.offset);
tr_peerIoReadUint32(msgs->io, inbuf, &r.length);
logtrace(msgs, "got Request: %u:%u->%u", r.index, r.offset, r.length);
logtrace(msgs, fmt::format(FMT_STRING("got Request: {:d}:{:d}->{:d}"), r.index, r.offset, r.length));
peerMadeRequest(msgs, &r);
break;
}
@ -1720,7 +1725,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
tr_peerIoReadUint32(msgs->io, inbuf, &r.offset);
tr_peerIoReadUint32(msgs->io, inbuf, &r.length);
msgs->cancelsSentToClient.add(tr_time(), 1);
logtrace(msgs, "got a Cancel %u:%u->%u", r.index, r.offset, r.length);
logtrace(msgs, fmt::format(FMT_STRING("got a Cancel {:d}:{:d}->{:d}"), r.index, r.offset, r.length));
for (int i = 0; i < msgs->pendingReqsToClient; ++i)
{
@ -1850,7 +1855,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
break;
default:
logtrace(msgs, "peer sent us an UNKNOWN: %d", int(id));
logtrace(msgs, fmt::format(FMT_STRING("peer sent us an UNKNOWN: {:d}"), static_cast<int>(id)));
tr_peerIoDrain(msgs->io, inbuf, msglen);
break;
}
@ -1873,17 +1878,22 @@ static int clientGotBlock(tr_peerMsgsImpl* msgs, struct evbuffer* data, struct p
if (!requestIsValid(msgs, req))
{
logdbg(msgs, "dropping invalid block %u:%u->%u", req->index, req->offset, req->length);
logdbg(msgs, fmt::format(FMT_STRING("dropping invalid block {:d}:{:d}->{:d}"), req->index, req->offset, req->length));
return EBADMSG;
}
if (req->length != msgs->torrent->blockSize(block))
{
logdbg(msgs, "wrong block size -- expected %u, got %d", msgs->torrent->blockSize(block), req->length);
logdbg(
msgs,
fmt::format(
FMT_STRING("wrong block size -- expected {:d}, got {:d}"),
msgs->torrent->blockSize(block),
req->length));
return EMSGSIZE;
}
logtrace(msgs, "got block %u:%u->%u", req->index, req->offset, req->length);
logtrace(msgs, fmt::format(FMT_STRING("got block {:d}:{:d}->{:d}"), req->index, req->offset, req->length));
if (!tr_peerMgrDidPeerRequest(msgs->torrent, msgs, block))
{
@ -1934,7 +1944,9 @@ static ReadState canRead(tr_peerIo* io, void* vmsgs, size_t* piece)
evbuffer* const in = io->getReadBuffer();
size_t const inlen = evbuffer_get_length(in);
logtrace(msgs, "canRead: inlen is %zu, msgs->state is %d", inlen, int(msgs->state));
logtrace(
msgs,
fmt::format(FMT_STRING("canRead: inlen is {:d}, msgs->state is {:d}"), inlen, static_cast<int>(msgs->state)));
auto ret = ReadState{};
if (inlen == 0)
@ -1963,7 +1975,7 @@ static ReadState canRead(tr_peerIo* io, void* vmsgs, size_t* piece)
default:
#ifdef TR_ENABLE_ASSERTS
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unhandled peer messages state {:d}"), int(msgs->state)));
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unhandled peer messages state {:d}"), static_cast<int>(msgs->state)));
#else
ret = READ_ERR;
break;
@ -1971,7 +1983,7 @@ static ReadState canRead(tr_peerIo* io, void* vmsgs, size_t* piece)
}
}
logtrace(msgs, "canRead: ret is %d", int(ret));
logtrace(msgs, fmt::format(FMT_STRING("canRead: ret is {:d}"), static_cast<int>(ret)));
return ret;
}
@ -2032,7 +2044,7 @@ static void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now)
tr_variantDictAddInt(&tmp, TR_KEY_piece, piece);
auto* const payload = tr_variantToBuf(&tmp, TR_VARIANT_FMT_BENC);
logtrace(msgs, "requesting metadata piece #%d", piece);
logtrace(msgs, fmt::format(FMT_STRING("requesting metadata piece #{:d}"), piece));
/* write it out as a LTEP message to our outMessages buffer */
evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload));
@ -2094,14 +2106,16 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now)
if (haveMessages && msgs->outMessagesBatchedAt == 0) /* fresh batch */
{
logtrace(msgs, "started an outMessages batch (length is %zu)", evbuffer_get_length(msgs->outMessages));
logtrace(
msgs,
fmt::format(FMT_STRING("started an outMessages batch (length is {:d})"), evbuffer_get_length(msgs->outMessages)));
msgs->outMessagesBatchedAt = now;
}
else if (haveMessages && now - msgs->outMessagesBatchedAt >= msgs->outMessagesBatchPeriod)
{
size_t const len = evbuffer_get_length(msgs->outMessages);
/* flush the protocol messages */
logtrace(msgs, "flushing outMessages... to %p (length is %zu)", (void*)msgs->io, len);
logtrace(msgs, fmt::format(FMT_STRING("flushing outMessages... to {:p} (length is {:d})"), fmt::ptr(msgs->io), len));
tr_peerIoWriteBuf(msgs->io, msgs->outMessages, false);
msgs->clientSentAnythingAt = now;
msgs->outMessagesBatchedAt = 0;
@ -2224,7 +2238,7 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now)
else
{
size_t const n = evbuffer_get_length(out);
logtrace(msgs, "sending block %u:%u->%u", req.index, req.offset, req.length);
logtrace(msgs, fmt::format(FMT_STRING("sending block {:d}:{:d}->{:d}"), req.index, req.offset, req.length));
TR_ASSERT(n == msglen);
tr_peerIoWriteBuf(msgs->io, out, true);
bytesWritten += n;
@ -2292,12 +2306,14 @@ static void gotError(tr_peerIo* /*io*/, short what, void* vmsgs)
if ((what & BEV_EVENT_TIMEOUT) != 0)
{
logdbg(msgs, "libevent got a timeout, what=%hd", what);
logdbg(msgs, fmt::format(FMT_STRING("libevent got a timeout, what={:d}"), what));
}
if ((what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) != 0)
{
logdbg(msgs, "libevent got an error! what=%hd, errno=%d (%s)", what, errno, tr_strerror(errno));
logdbg(
msgs,
fmt::format(FMT_STRING("libevent got an error! what={:d}, errno={:d} ({:s})"), what, errno, tr_strerror(errno)));
}
msgs->publishError(ENOTCONN);
@ -2313,7 +2329,7 @@ static void sendBitfield(tr_peerMsgsImpl* msgs)
evbuffer_add_uint32(out, sizeof(uint8_t) + bytes.size());
evbuffer_add_uint8(out, BtPeerMsgs::Bitfield);
evbuffer_add(out, bytes.data(), std::size(bytes));
logtrace(msgs, "sending bitfield... outMessage size is now %zu", evbuffer_get_length(out));
logtrace(msgs, fmt::format(FMT_STRING("sending bitfield... outMessage size is now {:d}"), evbuffer_get_length(out)));
pokeBatchPeriod(msgs, ImmediatePriorityIntervalSecs);
}
@ -2502,15 +2518,16 @@ static void sendPex(tr_peerMsgsImpl* msgs)
&diffs6);
logtrace(
msgs,
"pex: old peer count %d+%d, new peer count %d+%d, added %d+%d, removed %d+%d",
msgs->pexCount,
msgs->pexCount6,
newCount,
newCount6,
diffs.addedCount,
diffs6.addedCount,
diffs.droppedCount,
diffs6.droppedCount);
fmt::format(
FMT_STRING("pex: old peer count {:d}+{:d}, new peer count {:d}+{:d}, added {:d}+{:d}, removed {:d}+{:d}"),
msgs->pexCount,
msgs->pexCount6,
newCount,
newCount6,
diffs.addedCount,
diffs6.addedCount,
diffs.droppedCount,
diffs6.droppedCount));
if (diffs.addedCount == 0 && diffs.droppedCount == 0 && diffs6.addedCount == 0 && diffs6.droppedCount == 0)
{
@ -2640,7 +2657,9 @@ static void sendPex(tr_peerMsgsImpl* msgs)
evbuffer_add_uint8(out, msgs->ut_pex_id);
evbuffer_add_buffer(out, payload);
pokeBatchPeriod(msgs, HighPriorityIntervalSecs);
logtrace(msgs, "sending a pex message; outMessage size is now %zu", evbuffer_get_length(out));
logtrace(
msgs,
fmt::format(FMT_STRING("sending a pex message; outMessage size is now {:d}"), evbuffer_get_length(out)));
dbgOutMessageLen(msgs);
evbuffer_free(payload);

View File

@ -4,7 +4,6 @@
// License text can be found in the licenses/ folder.
#include <algorithm>
#include <cstdarg>
#include <cstring>
#include <list>
#include <string>
@ -21,6 +20,7 @@
#include <windows.h>
#include <shlobj.h> /* SHGetKnownFolderPath(), FOLDERID_... */
#else
#include <pwd.h>
#include <unistd.h> /* getuid() */
#endif
@ -45,59 +45,10 @@
using namespace std::literals;
// TODO: use remove this and use tr_strvPath() instead
static char* tr_buildPath(char const* first_element, ...)
{
// pass 1: allocate enough space for the string
va_list vl;
va_start(vl, first_element);
auto bufLen = size_t{};
for (char const* element = first_element; element != nullptr;)
{
bufLen += strlen(element) + 1;
element = va_arg(vl, char const*);
}
va_end(vl);
auto* const buf = tr_new(char, bufLen);
if (buf == nullptr)
{
return nullptr;
}
/* pass 2: build the string piece by piece */
char* pch = buf;
va_start(vl, first_element);
for (char const* element = first_element; element != nullptr;)
{
size_t const elementLen = strlen(element);
pch = std::copy_n(element, elementLen, pch);
*pch++ = TR_PATH_DELIMITER;
element = va_arg(vl, char const*);
}
va_end(vl);
// if nonempty, eat the unwanted trailing slash
if (pch != buf)
{
--pch;
}
// zero-terminate the string
*pch++ = '\0';
/* sanity checks & return */
TR_ASSERT(pch - buf == (ptrdiff_t)bufLen);
return buf;
}
/***
**** PATHS
***/
#ifndef _WIN32
#include <pwd.h>
#endif
#ifdef _WIN32
static char* win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags)
@ -128,31 +79,34 @@ static char const* getHomeDir()
if (home == nullptr)
{
home = tr_env_get_string("HOME", nullptr);
}
if (home == nullptr)
{
#ifdef _WIN32
home = win32_get_known_folder(FOLDERID_Profile);
if (home == nullptr)
{
home = win32_get_known_folder(FOLDERID_Profile);
}
#else
struct passwd pwent;
struct passwd* pw = nullptr;
char buf[4096];
getpwuid_r(getuid(), &pwent, buf, sizeof buf, &pw);
if (pw != nullptr)
{
home = tr_strdup(pw->pw_dir);
}
if (home == nullptr)
{
struct passwd pwent;
struct passwd* pw = nullptr;
char buf[4096];
getpwuid_r(getuid(), &pwent, buf, sizeof buf, &pw);
if (pw != nullptr)
{
home = tr_strdup(pw->pw_dir);
}
}
#endif
}
if (home == nullptr)
{
home = tr_strdup("");
}
if (home == nullptr)
{
home = tr_strdup("");
}
return home;
@ -202,19 +156,19 @@ char const* tr_getDefaultConfigDir(char const* appname)
{
#ifdef __APPLE__
s = tr_buildPath(getHomeDir(), "Library", "Application Support", appname, nullptr);
s = tr_strvDup(tr_strvPath(getHomeDir(), "Library", "Application Support", appname));
#elif defined(_WIN32)
char* appdata = win32_get_known_folder(FOLDERID_LocalAppData);
s = tr_buildPath(appdata, appname, nullptr);
s = tr_strvDup(tr_strvPath(appdata, appname));
tr_free(appdata);
#elif defined(__HAIKU__)
char buf[PATH_MAX];
find_directory(B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof(buf));
s = tr_buildPath(buf, appname, nullptr);
s = tr_strvDup(tr_strvPath(buf, appname));
#else
@ -222,12 +176,12 @@ char const* tr_getDefaultConfigDir(char const* appname)
if (xdg_config_home != nullptr)
{
s = tr_buildPath(xdg_config_home, appname, nullptr);
s = tr_strvDup(tr_strvPath(xdg_config_home, appname));
tr_free(xdg_config_home);
}
else
{
s = tr_buildPath(getHomeDir(), ".config", appname, nullptr);
s = tr_strvDup(tr_strvPath(getHomeDir(), ".config", appname));
}
#endif
@ -301,9 +255,9 @@ char const* tr_getDefaultDownloadDir()
if (user_dir == nullptr)
{
#ifdef __HAIKU__
user_dir = tr_buildPath(getHomeDir(), "Desktop", nullptr);
user_dir = tr_strvDup(tr_strvPath(getHomeDir(), "Desktop"));
#else
user_dir = tr_buildPath(getHomeDir(), "Downloads", nullptr);
user_dir = tr_strvDup(tr_strvPath(getHomeDir(), "Downloads"));
#endif
}
}
@ -317,10 +271,10 @@ char const* tr_getDefaultDownloadDir()
static bool isWebClientDir(std::string_view path)
{
auto tmp = tr_strvPath(path, "index.html");
bool const ret = tr_sys_path_exists(tmp.c_str());
tr_logAddTrace(fmt::format("Searching for web interface file '{}'", tmp));
return ret;
auto const filename = tr_pathbuf{ path, '/', "index.html"sv };
bool const found = tr_sys_path_exists(filename);
tr_logAddTrace(fmt::format(FMT_STRING("Searching for web interface file '{:s}'"), filename));
return found;
}
char const* tr_getWebClientDir([[maybe_unused]] tr_session const* session)
@ -330,145 +284,136 @@ char const* tr_getWebClientDir([[maybe_unused]] tr_session const* session)
if (s == nullptr)
{
s = tr_env_get_string("CLUTCH_HOME", nullptr);
}
if (s == nullptr)
if (s == nullptr)
{
s = tr_env_get_string("TRANSMISSION_WEB_HOME", nullptr);
}
#ifdef BUILD_MAC_CLIENT
// look in the Application Support folder
if (s == nullptr)
{
if (auto path = tr_pathbuf{ session->config_dir, "/public_html"sv }; isWebClientDir(path))
{
s = tr_env_get_string("TRANSMISSION_WEB_HOME", nullptr);
s = tr_strvDup(path);
}
}
if (s == nullptr)
// look in the resource bundle
if (s == nullptr)
{
auto app_url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
auto app_ref = CFURLCopyFileSystemPath(app_url, kCFURLPOSIXPathStyle);
auto const buflen = CFStringGetMaximumSizeOfFileSystemRepresentation(app_ref);
auto buf = std::vector<char>(buflen, '\0');
bool const success = CFStringGetFileSystemRepresentation(app_ref, std::data(buf), std::size(buf));
TR_ASSERT(success);
CFRelease(app_url);
CFRelease(app_ref);
if (auto const path = tr_pathbuf{ std::string_view{ std::data(buf) }, "/Contents/Resources/public_html"sv };
isWebClientDir(path))
{
#ifdef BUILD_MAC_CLIENT /* on Mac, look in the Application Support folder first, then in the app bundle. */
/* Look in the Application Support folder */
s = tr_buildPath(tr_sessionGetConfigDir(session), "public_html", nullptr);
if (!isWebClientDir(s))
{
tr_free(const_cast<char*>(s));
CFURLRef appURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
CFStringRef appRef = CFURLCopyFileSystemPath(appURL, kCFURLPOSIXPathStyle);
CFIndex const appStringLength = CFStringGetMaximumSizeOfFileSystemRepresentation(appRef);
char* appString = static_cast<char*>(tr_malloc(appStringLength));
bool const success = CFStringGetFileSystemRepresentation(appRef, appString, appStringLength);
TR_ASSERT(success);
CFRelease(appURL);
CFRelease(appRef);
/* Fallback to the app bundle */
s = tr_buildPath(appString, "Contents", "Resources", "public_html", nullptr);
if (!isWebClientDir(s))
{
tr_free(const_cast<char*>(s));
s = nullptr;
}
tr_free(appString);
}
s = tr_strvDup(path);
}
}
#elif defined(_WIN32)
/* Generally, Web interface should be stored in a Web subdir of
* calling executable dir. */
if (s == nullptr)
{
/* Generally, Web interface should be stored in a Web subdir of
* calling executable dir. */
static KNOWNFOLDERID const* const known_folder_ids[] = {
&FOLDERID_LocalAppData,
&FOLDERID_RoamingAppData,
&FOLDERID_ProgramData,
};
static KNOWNFOLDERID const* const known_folder_ids[] = {
&FOLDERID_LocalAppData,
&FOLDERID_RoamingAppData,
&FOLDERID_ProgramData,
};
for (size_t i = 0; s == nullptr && i < TR_N_ELEMENTS(known_folder_ids); ++i)
for (size_t i = 0; s == nullptr && i < TR_N_ELEMENTS(known_folder_ids); ++i)
{
char* dir = win32_get_known_folder(*known_folder_ids[i]);
if (auto const path = tr_pathbuf{ std::string_view{ dir }, "/Transmission/Web"sv }; isWebClientDir(path))
{
char* dir = win32_get_known_folder(*known_folder_ids[i]);
char* path = tr_buildPath(dir, "Transmission", "Web", nullptr);
tr_free(dir);
if (isWebClientDir(path))
{
s = path;
}
else
{
tr_free(path);
}
s = tr_strvDup(path);
}
if (s == nullptr) /* check calling module place */
{
wchar_t wide_module_path[MAX_PATH];
GetModuleFileNameW(nullptr, wide_module_path, TR_N_ELEMENTS(wide_module_path));
char* module_path = tr_win32_native_to_utf8(wide_module_path, -1);
auto const dir = tr_sys_path_dirname(module_path);
tr_free(module_path);
if (!std::empty(dir))
{
char* path = tr_buildPath(dir.c_str(), "Web", nullptr);
if (isWebClientDir(path))
{
s = path;
}
else
{
tr_free(path);
}
}
}
#else /* everyone else, follow the XDG spec */
auto candidates = std::list<std::string>{};
/* XDG_DATA_HOME should be the first in the list of candidates */
char* tmp = tr_env_get_string("XDG_DATA_HOME", nullptr);
if (!tr_str_is_empty(tmp))
{
candidates.emplace_back(tmp);
}
else
{
candidates.emplace_back(tr_strvPath(getHomeDir(), ".local"sv, "share"sv));
}
tr_free(tmp);
/* XDG_DATA_DIRS are the backup directories */
{
char const* const pkg = PACKAGE_DATA_DIR;
auto* xdg = tr_env_get_string("XDG_DATA_DIRS", "");
auto const buf = tr_strvJoin(pkg, ":", xdg, ":/usr/local/share:/usr/share");
tr_free(xdg);
auto sv = std::string_view{ buf };
auto token = std::string_view{};
while (tr_strvSep(&sv, &token, ':'))
{
token = tr_strvStrip(token);
if (!std::empty(token))
{
candidates.emplace_back(token);
}
}
}
/* walk through the candidates & look for a match */
for (auto const& dir : candidates)
{
auto const path = tr_strvPath(dir, "transmission"sv, "public_html"sv);
if (isWebClientDir(path))
{
s = tr_strvDup(path);
break;
}
}
#endif
tr_free(dir);
}
}
if (s == nullptr) /* check calling module place */
{
wchar_t wide_module_path[MAX_PATH];
GetModuleFileNameW(nullptr, wide_module_path, TR_N_ELEMENTS(wide_module_path));
char* module_path = tr_win32_native_to_utf8(wide_module_path, -1);
if (auto const dir = tr_sys_path_dirname(module_path); !std::empty(dir))
{
if (auto const path = tr_pathbuf{ dir, "/Web"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
}
}
tr_free(module_path);
}
#else // everyone else, follow the XDG spec
if (s == nullptr)
{
auto candidates = std::list<std::string>{};
/* XDG_DATA_HOME should be the first in the list of candidates */
char* tmp = tr_env_get_string("XDG_DATA_HOME", nullptr);
if (!tr_str_is_empty(tmp))
{
candidates.emplace_back(tmp);
}
else
{
candidates.emplace_back(tr_strvPath(getHomeDir(), ".local"sv, "share"sv));
}
tr_free(tmp);
/* XDG_DATA_DIRS are the backup directories */
{
char const* const pkg = PACKAGE_DATA_DIR;
auto* xdg = tr_env_get_string("XDG_DATA_DIRS", "");
auto const buf = tr_strvJoin(pkg, ":", xdg, ":/usr/local/share:/usr/share");
tr_free(xdg);
auto sv = std::string_view{ buf };
auto token = std::string_view{};
while (tr_strvSep(&sv, &token, ':'))
{
token = tr_strvStrip(token);
if (!std::empty(token))
{
candidates.emplace_back(token);
}
}
}
/* walk through the candidates & look for a match */
for (auto const& dir : candidates)
{
if (auto const path = tr_pathbuf{ dir, "/transmission/public_html"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
break;
}
}
}
#endif
return s;
}