From 5a3ff5545dffd20d2bcaffe68988c57af944fda2 Mon Sep 17 00:00:00 2001 From: liyigang Date: Wed, 24 Jun 2026 17:25:28 +0800 Subject: [PATCH 1/2] perf(dfm-io): cache statx results in DFileInfoPrivate::attributesBySelf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce ensureStatxCached() to consolidate six independent statx syscalls into one. Cache the result in mutable member fields so that subsequent time-attribute queries reuse the same buffer. 在 attributesBySelf 中引入 ensureStatxCached() 方法,将 6 个时间 属性的重复 statx 系统调用合并为一次,结果缓存后各 case 直接读取。 Log: 优化 attributesBySelf 中 statx 调用,消除重复系统调用 Task: https://pms.uniontech.com/task-view-391003.html Influence: 查询 kTimeCreated/Modified/Access 等属性时,statx 从最多 6 次降为 1 次,减少不必要的系统调用开销。 --- src/dfm-io/dfm-io/dfileinfo.cpp | 79 ++++++++++--------------- src/dfm-io/dfm-io/private/dfileinfo_p.h | 7 +++ 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/dfm-io/dfm-io/dfileinfo.cpp b/src/dfm-io/dfm-io/dfileinfo.cpp index 25533406..ceb1e97c 100644 --- a/src/dfm-io/dfm-io/dfileinfo.cpp +++ b/src/dfm-io/dfm-io/dfileinfo.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -281,6 +280,24 @@ void DFileInfoPrivate::queryInfoAsync(int ioPriority, DFileInfo::InitQuerierAsyn g_file_query_info_async(this->gfile, attributes, GFileQueryInfoFlags(flag), ioPriority, gcancellable, queryInfoAsyncCallback, dataOp); } +bool DFileInfoPrivate::ensureStatxCached() const +{ + if (statxCached) + return statxValid; + + statxCached = true; + + const QUrl &url = q->uri(); + if (!url.isLocalFile()) + return false; + + unsigned mask = STATX_BASIC_STATS | STATX_BTIME; + int ret = statx(AT_FDCWD, url.path().toStdString().data(), + AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuf); + statxValid = (ret == 0); + return statxValid; +} + QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) { QVariant retValue; @@ -295,16 +312,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_btime.tv_sec); + return quint64(statxBuf.stx_btime.tv_sec); } return qulonglong(ret); } @@ -314,16 +325,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_btime.tv_nsec / 1000000; + return statxBuf.stx_btime.tv_nsec / 1000000; } return QVariant(ret); } @@ -333,16 +338,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_mtime.tv_sec); + return quint64(statxBuf.stx_mtime.tv_sec); } return qulonglong(ret); } @@ -352,16 +351,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_mtime.tv_nsec / 1000000; + return statxBuf.stx_mtime.tv_nsec / 1000000; } return QVariant(ret); } @@ -371,16 +364,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_atime.tv_sec); + return quint64(statxBuf.stx_atime.tv_sec); } return qulonglong(ret); } @@ -390,16 +377,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_atime.tv_nsec / 1000000; + return statxBuf.stx_atime.tv_nsec / 1000000; } return QVariant(ret); } diff --git a/src/dfm-io/dfm-io/private/dfileinfo_p.h b/src/dfm-io/dfm-io/private/dfileinfo_p.h index 348ed6df..1547a6b9 100644 --- a/src/dfm-io/dfm-io/private/dfileinfo_p.h +++ b/src/dfm-io/dfm-io/private/dfileinfo_p.h @@ -19,6 +19,7 @@ #include #include +#include BEGIN_IO_NAMESPACE @@ -55,6 +56,7 @@ class DFileInfoPrivate : public QObject, public QSharedData QVariant attributesBySelf(DFileInfo::AttributeID id); QVariant attributesFromUrl(DFileInfo::AttributeID id); void checkAndResetCancel(); + [[nodiscard]] bool ensureStatxCached() const; [[nodiscard]] DFileFuture *initQuerierAsync(int ioPriority, QObject *parent = nullptr) const; [[nodiscard]] QFuture refreshAsync(); @@ -94,6 +96,11 @@ class DFileInfoPrivate : public QObject, public QSharedData std::atomic_bool stoped { false }; std::atomic_bool fileExists { false }; QMap caches; + + // statx 缓存:6 个时间属性 case 共享一次 statx 调用,避免重复系统调用 + mutable struct statx statxBuf {}; + mutable bool statxCached { false }; + mutable bool statxValid { false }; std::atomic_bool cacheing { false }; std::atomic_bool refreshing { false }; QMutex mutex; From 0b0078215869c617a2007f22b3b78f104b2a08e2 Mon Sep 17 00:00:00 2001 From: liyigang Date: Tue, 23 Jun 2026 20:00:03 +0800 Subject: [PATCH 2/2] fix: prevent BOM character loss in path concatenation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use std::string for directory path concatenation to avoid QString's normalization of UTF-8 BOM (U+FEFF / zero-width no-break space). 使用 std::string 进行路径拼接,避免 QString 对 UTF-8 BOM (零宽不换行空格) 的规范化导致字节丢失。 Log: 修复路径拼接时 BOM 字符丢失的问题 Bug: https://pms.uniontech.com//bug-view-367075.html Influence: 修复后包含 BOM/零宽不换行空格的路径能正确拼接,避免文件操作失败。 --- src/dfm-io/dfm-io/denumerator.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/dfm-io/dfm-io/denumerator.cpp b/src/dfm-io/dfm-io/denumerator.cpp index 6c129566..36531169 100644 --- a/src/dfm-io/dfm-io/denumerator.cpp +++ b/src/dfm-io/dfm-io/denumerator.cpp @@ -446,12 +446,29 @@ char *DEnumeratorPrivate::filePath(const QUrl &url) QUrl DEnumeratorPrivate::buildUrl(const QUrl &url, const char *fileName) { - QString path; - if (url.path() == "/") { - path = "/" + QString(fileName); + // 防御空指针,避免std::string或QByteArray构造时崩溃 + if (!fileName) { + return QUrl(); + } + + // 拦截路径遍历攻击,防止恶意文件名越权 + QByteArray fileNameBa(fileName); + if (fileNameBa.contains('/') || fileNameBa.contains('\\') || fileNameBa == "." || fileNameBa == "..") { + return QUrl(); + } + + QByteArray path; + QString urlPath = url.path(); + + if (urlPath == "/" || urlPath.isEmpty()) { + path = QByteArray("/") + fileNameBa; } else { - QString dirPath = url.path(); - path = dirPath.endsWith('/') ? dirPath + QString(fileName) : dirPath + "/" + QString(fileName); + QByteArray dirPath = urlPath.toUtf8(); + if (!dirPath.endsWith('/')) { + dirPath.append('/'); + } + // 使用QByteArray进行底层字节数组拼接,避免QString剥离BOM (efbbbf) + path = dirPath + fileNameBa; } // 保留原始 URL 的 scheme 和 host,而不是假定为本地文件