refactor: use std::filesystem for more file utils (#8296)

* fix: warning: declaration shadows a variable in the global namespace [clang-diagnostic-shadow]

* fix: warning: use 'contains' to check for membership [readability-container-contains]

* fix: warning: variable gl_confdir can be made static or moved into an anonymous namespace to enforce internal linkage [misc-use-internal-linkage]

* warning: function 'TorrentFilter::match_mode' has a definition with different parameter names [readability-inconsistent-declaration-parameter-name]

* refactor: use std::filesystem for tr_sys_path_is_same()

* refactor: use std::filesystem for tr_sys_path_exists()

* refactor: use std::filesystem for tr_sys_path_is_relative()

* refactor: use std::filesystem for tr_sys_path_get_info()

* refactor: use std::filesystem for tr_sys_dir_create()

* refactor: add `maybe_set_error()` helper

* refactor: change behavior to match previous impl

* fix: tr_sys_path_is_same() checks

refactor: address code review feedback

* refactor: address code review feedback

* chore: fix readability-else-after-return clang-tidy warning

* fix: warning: Value stored to 'created' during its initialization is never read [clang-analyzer-deadcode.DeadStores]

fix: warning: parameter 'permissions' is unused [misc-unused-parameters]

* ci: work around a MSVC STL 14.44.35207 clang-tidy false warning

* refactor: simplify return logic of tr_sys_path_is_same()
This commit is contained in:
Charles Kerr
2026-02-01 15:37:37 -06:00
committed by GitHub
parent c1e4221a75
commit 2649e7acdd
8 changed files with 251 additions and 418 deletions

View File

@@ -1246,7 +1246,7 @@ void DetailsDialog::Impl::refreshPeerList(std::vector<tr_torrent*> const& torren
{
auto const key = make_key(tor, peer);
if (hash.find(key) == hash.end())
if (!hash.contains(key))
{
auto const iter = store->append();
initPeerRow(iter, key, tr_torrentName(tor), peer);
@@ -1314,7 +1314,7 @@ void DetailsDialog::Impl::refreshWebseedList(std::vector<tr_torrent*> const& tor
auto const* const url = tr_torrentWebseed(tor, j).url;
auto const key = make_key(tor, url);
if (hash.find(key) == hash.end())
if (!hash.contains(key))
{
auto const iter = store->append();
(*iter)[webseed_cols.url] = url;
@@ -1986,7 +1986,7 @@ void DetailsDialog::Impl::refreshTracker(std::vector<tr_torrent*> const& torrent
// build the key to find the row
gstr.str({});
gstr << torrent_id << '\t' << tracker.tier << '\t' << tracker.announce;
if (hash.find(gstr.str()) == hash.end())
if (!hash.contains(gstr.str()))
{
// if we didn't have that row, add it
auto const iter = store->append();

View File

@@ -23,15 +23,10 @@ using namespace std::literals;
using namespace tr::app;
using tr::serializer::to_variant;
std::string gl_confdir;
void gtr_pref_init(std::string_view config_dir)
{
gl_confdir = config_dir;
}
namespace
{
std::string gl_confdir;
[[nodiscard]] std::string get_default_download_dir()
{
if (auto dir = Glib::get_user_special_dir(TR_GLIB_USER_DIRECTORY(DOWNLOAD)); !std::empty(dir))
@@ -121,6 +116,11 @@ tr_variant& getPrefs()
}
} // namespace
void gtr_pref_init(std::string_view config_dir)
{
gl_confdir = config_dir;
}
tr_variant& gtr_pref_get_all()
{
return getPrefs();

View File

@@ -170,11 +170,11 @@ Glib::RefPtr<TorrentFilter> TorrentFilter::create()
return Glib::make_refptr_for_instance(new TorrentFilter());
}
bool TorrentFilter::match_mode(Torrent const& torrent, ShowMode const type)
bool TorrentFilter::match_mode(Torrent const& torrent, ShowMode const mode)
{
auto activity = tr_torrent_activity();
switch (type)
switch (mode)
{
case ShowMode::ShowAll:
return true;

View File

@@ -4,6 +4,10 @@ HeaderFilterRegex: .*/libtransmission/.*
# TODO: Enable `portability-template-virtual-member-function` after https://github.com/llvm/llvm-project/issues/139031 is fixed
# TODO: Enable `cppcoreguidelines-pro-bounds-pointer-arithmetic` after converting all pointers to std::span
# TODO: Enable `modernize-use-integer-sign-comparison` (P0586R2) after GCC >= 10.1, clang >= 13
# -clang-analyzer-optin.core.EnumCastOutOfRange: disabled due false <filesystem> warning in file.cc calling
# std::filesystem::last_write_time() on windows ci builds with MSVC STL 14.44.35207.
# Try re-enabling when the CI runner upgrades to a newer version.
#
# PRs welcome to fix & re-enable any of these explicitly-disabled checks
#
# -modernize-use-ranges: GCC 10.1, clang 15
@@ -20,6 +24,7 @@ Checks: >
-cert-err58-cpp,
-cert-int09-c,
clang-analyzer-*,
-clang-analyzer-optin.core.EnumCastOutOfRange,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,

View File

@@ -125,103 +125,6 @@ void set_file_for_single_pass(tr_sys_file_t handle)
}
} // namespace
bool tr_sys_path_exists(std::string_view const path, tr_error* error)
{
auto const sz_path = tr_pathbuf{ path };
bool const ret = access(sz_path.c_str(), F_OK) != -1;
if (error != nullptr && !ret && errno != ENOENT)
{
error->set_from_errno(errno);
}
return ret;
}
namespace
{
[[nodiscard]] auto stat_sv(std::string_view const path, struct stat* sb)
{
auto const sz_path = tr_pathbuf{ path };
return stat(sz_path.c_str(), sb);
}
[[nodiscard]] auto lstat_sv(std::string_view const path, struct stat* sb)
{
auto const sz_path = tr_pathbuf{ path };
return lstat(sz_path.c_str(), sb);
}
} // namespace
std::optional<tr_sys_path_info> tr_sys_path_get_info(std::string_view const path, int const flags, tr_error* error)
{
struct stat sb = {};
bool ok = false;
if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0)
{
ok = stat_sv(path, &sb) != -1;
}
else
{
ok = lstat_sv(path, &sb) != -1;
}
if (!ok)
{
if (error != nullptr)
{
error->set_from_errno(errno);
}
return {};
}
auto info = tr_sys_path_info{};
if (S_ISREG(sb.st_mode))
{
info.type = TR_SYS_PATH_IS_FILE;
}
else if (S_ISDIR(sb.st_mode))
{
info.type = TR_SYS_PATH_IS_DIRECTORY;
}
else
{
info.type = TR_SYS_PATH_IS_OTHER;
}
info.size = static_cast<uint64_t>(sb.st_size);
info.last_modified_at = sb.st_mtime;
return info;
}
bool tr_sys_path_is_relative(std::string_view path)
{
return std::empty(path) || path.front() != '/';
}
bool tr_sys_path_is_same(std::string_view const path1, std::string_view const path2, tr_error* error)
{
bool ret = false;
struct stat sb1 = {};
struct stat sb2 = {};
if (stat_sv(path1, &sb1) != -1 && stat_sv(path2, &sb2) != -1)
{
ret = sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
}
else if (error != nullptr && errno != ENOENT)
{
error->set_from_errno(errno);
}
return ret;
}
std::string_view tr_sys_path_basename(std::string_view path, tr_error* /*error*/)
{
// As per the basename() manpage:
@@ -1002,95 +905,6 @@ std::string tr_sys_dir_get_current(tr_error* error)
}
}
namespace
{
#ifndef HAVE_MKDIRP
[[nodiscard]] bool tr_mkdirp_(std::string_view path, int permissions, tr_error* error)
{
auto walk = path.find_first_not_of('/'); // walk past the root
auto subpath = tr_pathbuf{};
while (walk < std::size(path))
{
auto const end = path.find('/', walk);
subpath.assign(path.substr(0, end));
auto const info = tr_sys_path_get_info(subpath, 0);
if (info && !info->isFolder())
{
if (error != nullptr)
{
error->set(ENOTDIR, fmt::format("File is in the way: {:s}", path));
}
return false;
}
if (!info && mkdir(subpath, permissions) == -1)
{
if (error != nullptr)
{
error->set_from_errno(errno);
}
return false;
}
if (end == std::string_view::npos)
{
break;
}
walk = end + 1;
}
return true;
}
#endif
} // namespace
bool tr_sys_dir_create(std::string_view const path, int const flags, int const permissions, tr_error* error)
{
auto ret = false;
auto local_error = tr_error{};
auto const sz_path = tr_pathbuf{ path };
if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0)
{
#ifdef HAVE_MKDIRP
ret = mkdirp(sz_path.c_str(), permissions) != -1;
#else
ret = tr_mkdirp_(path, permissions, &local_error);
#endif
}
else
{
ret = mkdir(sz_path.c_str(), permissions) != -1;
}
if (!ret && errno == EEXIST)
{
if (auto const info = tr_sys_path_get_info(path); info && info->type == TR_SYS_PATH_IS_DIRECTORY)
{
local_error = {};
ret = true;
}
else
{
errno = EEXIST;
}
}
if (error != nullptr && !ret)
{
if (!local_error)
{
local_error.set_from_errno(errno);
}
*error = std::move(local_error);
}
return ret;
}
bool tr_sys_dir_create_temp(char* path_template, tr_error* error)
{
TR_ASSERT(path_template != nullptr);

View File

@@ -37,8 +37,6 @@ struct tr_sys_dir_win32
namespace
{
auto constexpr DeltaEpochInMicrosecs = UINT64_C(11644473600000000);
auto constexpr NativeLocalPathPrefix = L"\\\\?\\"sv;
auto constexpr NativeUncPathPrefix = L"\\\\?\\UNC\\"sv;
@@ -59,49 +57,11 @@ void set_system_error_if_file_found(tr_error* error, DWORD code)
}
}
constexpr time_t filetime_to_unix_time(FILETIME const& t)
{
uint64_t tmp = 0;
tmp |= t.dwHighDateTime;
tmp <<= 32;
tmp |= t.dwLowDateTime;
tmp /= 10; /* to microseconds */
tmp -= DeltaEpochInMicrosecs;
return tmp / 1000000UL;
}
constexpr bool to_bool(BOOL value) noexcept
{
return value != FALSE;
}
constexpr auto stat_to_sys_path_info(DWORD attributes, DWORD size_low, DWORD size_high, FILETIME const& mtime)
{
auto info = tr_sys_path_info{};
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
info.type = TR_SYS_PATH_IS_DIRECTORY;
}
else if ((attributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_VIRTUAL)) == 0)
{
info.type = TR_SYS_PATH_IS_FILE;
}
else
{
info.type = TR_SYS_PATH_IS_OTHER;
}
info.size = size_high;
info.size <<= 32;
info.size |= size_low;
info.last_modified_at = filetime_to_unix_time(mtime);
return info;
}
auto constexpr Slashes = R"(\/)"sv;
constexpr bool is_slash(char c)
@@ -331,163 +291,7 @@ void create_temp_path(char* path_template, CallbackT const& callback, tr_error*
}
}
std::optional<tr_sys_path_info> tr_sys_file_get_info_(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
auto attributes = BY_HANDLE_FILE_INFORMATION{};
if (to_bool(GetFileInformationByHandle(handle, &attributes)))
{
return stat_to_sys_path_info(
attributes.dwFileAttributes,
attributes.nFileSizeLow,
attributes.nFileSizeHigh,
attributes.ftLastWriteTime);
}
set_system_error(error, GetLastError());
return {};
}
[[nodiscard]] std::optional<BY_HANDLE_FILE_INFORMATION> get_file_info(std::string_view const path, tr_error* error)
{
auto const wpath = path_to_native_path(path);
if (std::empty(wpath))
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
auto const handle = CreateFileW(wpath.c_str(), 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
// TODO: Use GetFileInformationByHandleEx on >= Server 2012
auto info = BY_HANDLE_FILE_INFORMATION{};
if (!to_bool(GetFileInformationByHandle(handle, &info)))
{
set_system_error_if_file_found(error, GetLastError());
CloseHandle(handle);
return {};
}
CloseHandle(handle);
return info;
}
} // namespace
bool tr_sys_path_exists(std::string_view const path, tr_error* error)
{
bool ret = false;
HANDLE handle = INVALID_HANDLE_VALUE;
if (auto const wide_path = path_to_native_path(path); !std::empty(wide_path))
{
DWORD const attributes = GetFileAttributesW(wide_path.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES)
{
if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0)
{
handle = CreateFileW(wide_path.c_str(), 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
ret = handle != INVALID_HANDLE_VALUE;
}
else
{
ret = true;
}
}
}
if (!ret)
{
set_system_error_if_file_found(error, GetLastError());
}
if (handle != INVALID_HANDLE_VALUE)
{
CloseHandle(handle);
}
return ret;
}
std::optional<tr_sys_path_info> tr_sys_path_get_info(std::string_view path, int flags, tr_error* error)
{
if (auto const wide_path = path_to_native_path(path); std::empty(wide_path))
{
// do nothing
}
else if ((flags & TR_SYS_PATH_NO_FOLLOW) != 0)
{
auto attributes = WIN32_FILE_ATTRIBUTE_DATA{};
if (to_bool(GetFileAttributesExW(wide_path.c_str(), GetFileExInfoStandard, &attributes)))
{
return stat_to_sys_path_info(
attributes.dwFileAttributes,
attributes.nFileSizeLow,
attributes.nFileSizeHigh,
attributes.ftLastWriteTime);
}
}
else if (auto const
handle = CreateFileW(wide_path.c_str(), 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
handle != INVALID_HANDLE_VALUE)
{
auto ret = tr_sys_file_get_info_(handle, error);
CloseHandle(handle);
return ret;
}
set_system_error(error, GetLastError());
return {};
}
bool tr_sys_path_is_relative(std::string_view path)
{
/* UNC path: `\\...`. */
if (is_unc_path(path))
{
return false;
}
/* Local path: `X:` */
if (std::size(path) == 2 && isalpha(path[0]) != 0 && path[1] == ':')
{
return false;
}
/* Local path: `X:\...`. */
if (std::size(path) > 2 && isalpha(path[0]) != 0 && path[1] == ':' && is_slash(path[2]))
{
return false;
}
return true;
}
bool tr_sys_path_is_same(std::string_view const path1, std::string_view const path2, tr_error* error)
{
auto const fi1 = get_file_info(path1, error);
if (!fi1)
{
return false;
}
auto const fi2 = get_file_info(path2, error);
if (!fi2)
{
return false;
}
return fi1->dwVolumeSerialNumber == fi2->dwVolumeSerialNumber && fi1->nFileIndexHigh == fi2->nFileIndexHigh &&
fi1->nFileIndexLow == fi2->nFileIndexLow;
}
std::string_view tr_sys_path_basename(std::string_view path, tr_error* error)
{
if (std::empty(path))
@@ -1048,11 +852,6 @@ std::string tr_sys_dir_get_current(tr_error* error)
return {};
}
bool tr_sys_dir_create(std::string_view const path, int const flags, int const permissions, tr_error* error)
{
return create_dir(path, flags, permissions, true, error);
}
bool tr_sys_dir_create_temp(char* path_template, tr_error* error)
{
TR_ASSERT(path_template != nullptr);

View File

@@ -3,6 +3,7 @@
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <chrono>
#include <filesystem>
#include <system_error>
#include <string>
@@ -12,6 +13,19 @@
#include "libtransmission/error.h"
#include "libtransmission/file.h"
namespace
{
void maybe_set_error(tr_error* error, std::error_code const& ec)
{
if (error != nullptr && ec)
{
error->set(ec.value(), ec.message());
}
}
} // namespace
std::string tr_sys_path_resolve(std::string_view path, tr_error* error)
{
auto ec = std::error_code{};
@@ -19,11 +33,7 @@ std::string tr_sys_path_resolve(std::string_view path, tr_error* error)
if (ec)
{
if (error != nullptr)
{
error->set(ec.value(), ec.message());
}
maybe_set_error(error, ec);
return {};
}
@@ -31,6 +41,214 @@ std::string tr_sys_path_resolve(std::string_view path, tr_error* error)
return { std::begin(u8_path), std::end(u8_path) };
}
bool tr_sys_path_is_relative(std::string_view path)
{
#ifdef _WIN32
auto const is_slash = [](char ch)
{
return ch == '/' || ch == '\\';
};
if (std::size(path) >= 2 && is_slash(path[0]) && path[1] == path[0])
{
return false;
}
if (std::size(path) == 2 && std::isalpha(static_cast<unsigned char>(path[0])) != 0 && path[1] == ':')
{
return false;
}
if (std::size(path) > 2 && std::isalpha(static_cast<unsigned char>(path[0])) != 0 && path[1] == ':' && is_slash(path[2]))
{
return false;
}
return true;
#else
return std::empty(path) || path.front() != '/';
#endif
}
bool tr_sys_path_exists(std::string_view path, tr_error* error)
{
auto ec = std::error_code{};
auto const exists = std::filesystem::exists(tr_u8path(path), ec);
maybe_set_error(error, ec);
return exists;
}
std::optional<tr_sys_path_info> tr_sys_path_get_info(std::string_view path, int flags, tr_error* error)
{
auto const filesystem_path = tr_u8path(path);
auto ec = std::error_code{};
auto const status = (flags & TR_SYS_PATH_NO_FOLLOW) != 0 ? std::filesystem::symlink_status(filesystem_path, ec) :
std::filesystem::status(filesystem_path, ec);
if (ec || status.type() == std::filesystem::file_type::not_found)
{
maybe_set_error(error, ec ? ec : std::make_error_code(std::errc::no_such_file_or_directory));
return {};
}
auto info = tr_sys_path_info{};
if (std::filesystem::is_regular_file(status))
{
info.type = TR_SYS_PATH_IS_FILE;
info.size = std::filesystem::file_size(filesystem_path, ec);
if (ec)
{
maybe_set_error(error, ec);
return {};
}
}
else if (std::filesystem::is_directory(status))
{
info.type = TR_SYS_PATH_IS_DIRECTORY;
info.size = 0;
}
else
{
info.type = TR_SYS_PATH_IS_OTHER;
info.size = 0;
}
auto const ftime = std::filesystem::last_write_time(filesystem_path, ec);
if (ec)
{
maybe_set_error(error, ec);
return {};
}
// TODO: use std::chrono::clock_cast when available.
// https://github.com/llvm/llvm-project/issues/166050
auto const sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
info.last_modified_at = std::chrono::system_clock::to_time_t(sctp);
return info;
}
bool tr_sys_path_is_same(std::string_view path1, std::string_view path2, tr_error* error)
{
auto const u8path1 = tr_u8path(path1);
auto const u8path2 = tr_u8path(path2);
// std::filesystem::equivalent() returns an unspecified error
// when either path doesn't exist. libstdc++ and libc++ chose
// different errors. So let's check `exists` here for consistency.
auto ec = std::error_code{};
if (!std::filesystem::exists(u8path1, ec) || !std::filesystem::exists(u8path2, ec))
{
maybe_set_error(error, ec);
return false;
}
auto const same = std::filesystem::equivalent(u8path1, u8path2, ec);
maybe_set_error(error, ec);
return same;
}
bool tr_sys_dir_create(std::string_view path, int flags, [[maybe_unused]] int permissions, tr_error* error)
{
auto const filesystem_path = tr_u8path(path);
auto const parents = (flags & TR_SYS_DIR_CREATE_PARENTS) != 0;
#ifndef _WIN32
auto missing = std::vector<std::filesystem::path>{};
if (parents && permissions != 0)
{
auto current = std::filesystem::path{};
for (auto const& part : filesystem_path)
{
current /= part;
auto check_ec = std::error_code{};
if (std::filesystem::is_directory(current, check_ec))
{
continue;
}
if (check_ec && check_ec != std::errc::no_such_file_or_directory)
{
maybe_set_error(error, check_ec);
return false;
}
missing.emplace_back(current);
}
}
#endif
auto ec = std::error_code{};
if (std::filesystem::is_directory(filesystem_path, ec))
{
return true;
}
if (ec && ec != std::errc::no_such_file_or_directory)
{
maybe_set_error(error, ec);
return false;
}
ec = {};
if (parents)
{
std::filesystem::create_directories(filesystem_path, ec);
}
else
{
std::filesystem::create_directory(filesystem_path, ec);
}
if (ec)
{
maybe_set_error(error, ec);
return false;
}
#ifndef _WIN32
if (permissions != 0)
{
auto const apply_permissions = [&](std::filesystem::path const& target)
{
auto perm_ec = std::error_code{};
std::filesystem::permissions(
target,
static_cast<std::filesystem::perms>(permissions),
std::filesystem::perm_options::replace,
perm_ec);
if (perm_ec)
{
maybe_set_error(error, perm_ec);
return false;
}
return true;
};
if (parents)
{
for (auto const& created_path : missing)
{
if (!apply_permissions(created_path))
{
return false;
}
}
}
else if (!apply_permissions(filesystem_path))
{
return false;
}
}
#endif
return true;
}
std::vector<std::string> tr_sys_dir_get_files(
std::string_view folder,
std::function<bool(std::string_view)> const& test,
@@ -74,9 +292,6 @@ std::optional<std::filesystem::space_info> tr_sys_path_get_capacity(std::filesys
return space;
}
if (error != nullptr)
{
error->set(ec.value(), ec.message());
}
maybe_set_error(error, ec);
return {};
}

View File

@@ -118,32 +118,32 @@ int tr_main(int argc, char** argv)
QStringList filenames;
int opt = 0;
char const* optarg = nullptr;
char const* arg = nullptr;
int file_args_start_idx = -1;
int qt_args_start_idx = -1;
while (file_args_start_idx < 0 && qt_args_start_idx < 0 &&
(opt = tr_getopt(getUsage(), argc, static_cast<char const* const*>(argv), std::data(Opts), &optarg)) != TR_OPT_DONE)
(opt = tr_getopt(getUsage(), argc, static_cast<char const* const*>(argv), std::data(Opts), &arg)) != TR_OPT_DONE)
{
switch (opt)
{
case 'g':
config_dir = QString::fromUtf8(optarg);
config_dir = QString::fromUtf8(arg);
break;
case 'p':
port = QString::fromUtf8(optarg);
port = QString::fromUtf8(arg);
break;
case 'r':
host = QString::fromUtf8(optarg);
host = QString::fromUtf8(arg);
break;
case 'u':
username = QString::fromUtf8(optarg);
username = QString::fromUtf8(arg);
break;
case 'w':
password = QString::fromUtf8(optarg);
password = QString::fromUtf8(arg);
break;
case 'm':
@@ -160,17 +160,17 @@ int tr_main(int argc, char** argv)
return 1;
default:
if (optarg == FileArgsSeparator)
if (arg == FileArgsSeparator)
{
file_args_start_idx = tr_optind;
}
else if (optarg == QtArgsSeparator)
else if (arg == QtArgsSeparator)
{
qt_args_start_idx = tr_optind;
}
else
{
filenames.append(QString::fromUtf8(optarg));
filenames.append(QString::fromUtf8(arg));
}
break;