From f794e49bb6eadd70c52683e60a700a1d7e9cd17b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 6 Feb 2023 23:05:06 -0800 Subject: we want to make links! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.h | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f083f3c7..98f55f96 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -77,7 +77,9 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); -/// @brief Copies a directory and it's contents from src to dest +/** + * @brief Copies a directory and it's contents from src to dest + */ class copy : public QObject { Q_OBJECT public: @@ -122,6 +124,70 @@ class copy : public QObject { int m_copied; }; +/** + * @brief Copies a directory and it's contents from src to dest + */ +class create_link : public QObject { + Q_OBJECT + public: + create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) + { + m_src.setPath(src); + m_dst.setPath(dst); + } + create_link& useHardLinks(const bool useHard) + { + m_useHardLinks = useHard; + return *this; + } + create_link& matcher(const IPathMatcher* filter) + { + m_matcher = filter; + return *this; + } + create_link& whitelist(bool whitelist) + { + m_whitelist = whitelist; + return *this; + } + create_link& linkRecursively(bool recursive) + { + m_recursive = recursive; + return *this; + } + create_link& debug(bool d) + { + m_debug = d; + return *this; + } + + int getLastOSError() { + return m_last_os_err; + } + + bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + + int totalLinked() { return m_linked; } + + signals: + void fileLinked(const QString& relativeName); + void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); + + private: + bool operator()(const QString& offset, bool dryRun = false); + + private: + bool m_useHardLinks = false; + const IPathMatcher* m_matcher = nullptr; + bool m_whitelist = false; + bool m_recursive = true; + QDir m_src; + QDir m_dst; + int m_linked; + bool m_debug = false; + int m_last_os_err = 0; +}; + /** * @brief moves a file by renaming it * @param source source file path -- cgit From 6d160a7b7e31034c7a657f30003562c20f9b9c21 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 00:35:03 -0800 Subject: feat: successful process elevation and comunication! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 9 +- launcher/DesktopServices.cpp | 2 +- launcher/FileSystem.cpp | 140 +++++++++++++++++++++++++++---- launcher/FileSystem.h | 57 +++++++++++-- launcher/StringUtils.cpp | 15 ++++ launcher/StringUtils.h | 2 + launcher/filelink/FileLink.cpp | 106 ++++++++++++++++++++++-- launcher/filelink/FileLink.h | 15 ++++ tests/FileSystem_test.cpp | 181 +++++++++++++++++++++++++++-------------- 9 files changed, 437 insertions(+), 90 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18d4ce0b..dd62893c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -562,6 +562,13 @@ set(ATLAUNCHER_SOURCES set(LINKEXE_SOURCES filelink/FileLink.h filelink/FileLink.cpp + FileSystem.h + FileSystem.cpp + Exception.h + StringUtils.h + StringUtils.cpp + DesktopServices.h + DesktopServices.cpp ) ######## Logging categories ######## @@ -1126,8 +1133,6 @@ if(WIN32) Qt${QT_VERSION_MAJOR}::Xml Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Concurrent - Qt${QT_VERSION_MAJOR}::Gui - Qt${QT_VERSION_MAJOR}::Widgets ${Launcher_QT_LIBS} ) diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 302eaf96..69770e99 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -37,7 +37,7 @@ #include #include #include -#include "Application.h" +//#include "Application.h" /** * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index ec4af98c..9e51f932 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,6 +36,8 @@ #include "FileSystem.h" +#include "BuildConfig.h" + #include #include #include @@ -45,6 +47,7 @@ #include #include #include +#include #include "DesktopServices.h" #include "StringUtils.h" @@ -61,6 +64,11 @@ #include #include #include +//for ShellExecute +#include +//#include +#include +#include #else #include #endif @@ -218,19 +226,29 @@ bool copy::operator()(const QString& offset, bool dryRun) } +bool create_link::operator()(const QString& offset, bool dryRun) +{ + + for (auto pair : m_path_pairs) { + if (!make_link(pair.src, pair.dst, offset, dryRun)) { + return false; + } + } + return true; +} + + /** * @brief links a directory and it's contents from src to dest * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ -bool create_link::operator()(const QString& offset, bool dryRun) +bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun) { m_linked = 0; // reset counter - auto src = PathCombine(m_src.absolutePath(), offset); - auto dst = PathCombine(m_dst.absolutePath(), offset); - - std::error_code err; + auto src = PathCombine(QDir(srcPath).absolutePath(), offset); + auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); // you can't hard link a directory so make sure if we deal with a directory we do so recursively if (m_useHardLinks) @@ -248,26 +266,25 @@ bool create_link::operator()(const QString& offset, bool dryRun) if (m_useHardLinks) { if (m_debug) qDebug() << "making hard link:" << src_path << "to" << dst_path; - fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); } else if (fs::is_directory(StringUtils::toStdString(src_path))) { if (m_debug) qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; - fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); } else { if (m_debug) qDebug() << "making symlink:" << src_path << "to" << dst_path; - fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); } } - if (err) { - qWarning() << "Failed to link files:" << QString::fromStdString(err.message()); + if (m_os_err) { + qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; - qDebug() << "Error catagory:" << err.category().name(); - qDebug() << "Error code:" << err.value(); - m_last_os_err = err.value(); - emit linkFailed(src_path, dst_path, err); + qDebug() << "Error catagory:" << m_os_err.category().name(); + qDebug() << "Error code:" << m_os_err.value(); + emit linkFailed(src_path, dst_path, m_os_err); } else { m_linked++; emit fileLinked(relative_dst_path); @@ -290,10 +307,103 @@ bool create_link::operator()(const QString& offset, bool dryRun) auto relative_path = src_dir.relativeFilePath(src_path); link_file(src_path, relative_path); + if (m_os_err) return false; } } - return err.value() == 0; + return m_os_err.value() == 0; +} + +bool create_link::runPrivlaged(const QString& offset) +{ + + QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8); + + connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){ + + qDebug() << "Client connected, sending out pairs"; + // construct block of data to send + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_5_15); // choose correct version better? + + qint32 blocksize = quint32(sizeof(quint32)); + for (auto pair : m_path_pairs) { + blocksize += quint32(pair.src.size()); + blocksize += quint32(pair.dst.size()); + } + qDebug() << "About to write block of size:" << blocksize; + out << blocksize; + + out << quint32(m_path_pairs.length()); + for (auto pair : m_path_pairs) { + out << pair.src; + out << pair.dst; + } + + QLocalSocket *clientConnection = m_linkServer.nextPendingConnection(); + connect(clientConnection, &QLocalSocket::disconnected, + clientConnection, &QLocalSocket::deleteLater); + + qint64 byteswritten = clientConnection->write(block); + bool bytesflushed = clientConnection->flush(); + qDebug() << "block flushed" << byteswritten << bytesflushed; + //clientConnection->disconnectFromServer(); + }); + + qDebug() << "Listening on pipe" << serverName; + if (!m_linkServer.listen(serverName)) { + qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString(); + return false; + } + + ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){ + emit finishedPrivlaged(); + }); + connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); + + linkFileProcess->start(); + + // linkFileProcess->wait(); + + return true; +} + + +void ExternalLinkFileProcess::runLinkFile() { + QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); + QString params = "-s " + m_server; + +#if defined Q_OS_WIN32 + SHELLEXECUTEINFO ShExecInfo; + HRESULT hr; + + fileLinkExe = fileLinkExe + ".exe"; + + qDebug() << "Running: runas" << fileLinkExe << params; + + LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16(); + LPCWSTR paramsWin = (const wchar_t*) params.utf16(); + + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa + ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); + ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function. + ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC + ShExecInfo.lpFile = programNameWin; + ShExecInfo.lpParameters = paramsWin; + ShExecInfo.lpDirectory = NULL; + ShExecInfo.nShow = SW_NORMAL; + ShExecInfo.hInstApp = NULL; + + ShellExecuteEx(&ShExecInfo); + + WaitForSingleObject(ShExecInfo.hProcess, INFINITE); + CloseHandle(ShExecInfo.hProcess); +#endif + + qDebug() << "Process exited"; } bool move(const QString& source, const QString& dest) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 98f55f96..b15d1685 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -39,9 +39,13 @@ #include "Exception.h" #include "pathmatcher/IPathMatcher.h" +#include + #include #include #include +#include +#include namespace FS { @@ -124,16 +128,45 @@ class copy : public QObject { int m_copied; }; +struct LinkPair { + QString src; + QString dst; +}; + +class ExternalLinkFileProcess : public QThread +{ + Q_OBJECT + public: + ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {} + + void run() override { + runLinkFile(); + emit processExited(); + } + + signals: + void processExited(); + + private: + void runLinkFile(); + + QString m_server; +}; + /** - * @brief Copies a directory and it's contents from src to dest + * @brief links (a file / a directory and it's contents) from src to dest */ class create_link : public QObject { Q_OBJECT public: + create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) + { + m_path_pairs.append(path_pairs); + } create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) { - m_src.setPath(src); - m_dst.setPath(dst); + LinkPair pair = {src, dst}; + m_path_pairs.append(pair); } create_link& useHardLinks(const bool useHard) { @@ -161,31 +194,39 @@ class create_link : public QObject { return *this; } - int getLastOSError() { - return m_last_os_err; + std::error_code getOSError() { + return m_os_err; } bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + bool runPrivlaged() { return runPrivlaged(QString()); } + bool runPrivlaged(const QString& offset); + int totalLinked() { return m_linked; } signals: void fileLinked(const QString& relativeName); void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); + void finishedPrivlaged(); private: bool operator()(const QString& offset, bool dryRun = false); + bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun); private: bool m_useHardLinks = false; const IPathMatcher* m_matcher = nullptr; bool m_whitelist = false; bool m_recursive = true; - QDir m_src; - QDir m_dst; + + QList m_path_pairs; + int m_linked; bool m_debug = false; - int m_last_os_err = 0; + std::error_code m_os_err; + + QLocalServer m_linkServer; }; /** diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 0f3c3669..93a44d4c 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -1,5 +1,7 @@ #include "StringUtils.h" +#include + /// If you're wondering where these came from exactly, then know you're not the only one =D /// TAKEN FROM Qt, because it doesn't expose it intelligently @@ -74,3 +76,16 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe // The two strings are the same (02 == 2) so fall back to the normal sort return QString::compare(s1, s2, cs); } + +QString StringUtils::getRandomAlphaNumeric(const int length) +{ + const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + QString randomString; + for(int i=0; i < length; ++i) + { + int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length()); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + return randomString; +} diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 1799605b..1ba19555 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -29,4 +29,6 @@ inline QString fromStdString(string s) #endif int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); + +QString getRandomAlphaNumeric(const int length); } // namespace StringUtils diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index 9b5589ab..78486507 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -31,8 +31,6 @@ #include - -#include #include #include @@ -48,7 +46,7 @@ -FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) +FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { #if defined Q_OS_WIN32 // attach the parent console @@ -81,18 +79,116 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) // Commandline parsing QCommandLineParser parser; - parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher")); + parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher")); parser.addOptions({ - + {{"s", "server"}, "Join the specified server on launch", "pipe name"} }); parser.addHelpOption(); parser.addVersionOption(); parser.process(arguments()); + QString serverToJoin = parser.value("server"); + qDebug() << "link program launched"; + if (!serverToJoin.isEmpty()) { + qDebug() << "joining server" << serverToJoin; + joinServer(serverToJoin); + } else { + qDebug() << "no server to join"; + exit(); + } + +} + +void FileLinkApp::joinServer(QString server) +{ + + blockSize = 0; + + in.setDevice(&socket); + in.setVersion(QDataStream::Qt_5_15); + + connect(&socket, &QLocalSocket::connected, this, [&](){ + qDebug() << "connected to server"; + }); + + connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); + + connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ + switch (socketError) { + case QLocalSocket::ServerNotFoundError: + qDebug() << tr("The host was not found. Please make sure " + "that the server is running and that the " + "server name is correct."); + break; + case QLocalSocket::ConnectionRefusedError: + qDebug() << tr("The connection was refused by the peer. " + "Make sure the server is running, " + "and check that the server name " + "is correct."); + break; + case QLocalSocket::PeerClosedError: + break; + default: + qDebug() << tr("The following error occurred: %1.").arg(socket.errorString()); + } + }); + + connect(&socket, &QLocalSocket::disconnected, this, [&](){ + qDebug() << "dissconnected from server"; + }); + + socket.connectToServer(server); + + +} + +void FileLinkApp::runLink() +{ + qDebug() << "creating link"; + FS::create_link lnk(m_path_pairs); + lnk.debug(true); + if (!lnk()) { + qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); + } + //exit(); + qDebug() << "done, should exit"; +} + +void FileLinkApp::readPathPairs() +{ + m_path_pairs.clear(); + qDebug() << "Reading path pairs from server"; + qDebug() << "bytes avalible" << socket.bytesAvailable(); + if (blockSize == 0) { + // Relies on the fact that QDataStream serializes a quint32 into + // sizeof(quint32) bytes + if (socket.bytesAvailable() < (int)sizeof(quint32)) + return; + qDebug() << "reading block size"; + in >> blockSize; + } + qDebug() << "blocksize is" << blockSize; + qDebug() << "bytes avalible" << socket.bytesAvailable(); + if (socket.bytesAvailable() < blockSize || in.atEnd()) + return; + + quint32 numPairs; + in >> numPairs; + qDebug() << "numPairs" << numPairs; + + for(int i = 0; i < numPairs; i++) { + FS::LinkPair pair; + in >> pair.src; + in >> pair.dst; + qDebug() << "link" << pair.src << "to" << pair.dst; + m_path_pairs.append(pair); + } + + runLink(); } diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index 253d1394..5d0ba123 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -32,6 +32,11 @@ #include #include #include +#include +#include + +#define PRISM_EXTERNAL_EXE +#include "FileSystem.h" class FileLinkApp : public QCoreApplication { @@ -43,7 +48,17 @@ public: virtual ~FileLinkApp(); private: + + void joinServer(QString server); + void readPathPairs(); + void runLink(); + QDateTime m_startTime; + QLocalSocket socket; + QDataStream in; + quint32 blockSize; + + QList m_path_pairs; #if defined Q_OS_WIN32 // used on Windows to attach the standard IO streams diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 395ca5c0..be0a4be0 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include #include @@ -26,6 +28,66 @@ namespace fs = ghc::filesystem; #include + + +class LinkTask : public Task { + Q_OBJECT + + friend class FileSystemTest; + + LinkTask(QString src, QString dst) + { + m_lnk = new FS::create_link(src, dst, this); + m_lnk->debug(true); + } + + void matcher(const IPathMatcher *filter) + { + m_lnk->matcher(filter); + } + + void linkRecursively(bool recursive) + { + m_lnk->linkRecursively(recursive); + m_linkRecursive = recursive; + } + + void whitelist(bool b) + { + m_lnk->whitelist(b); + } + + private: + void executeTask() override + { + if(!(*m_lnk)()){ +#if defined Q_OS_WIN32 + if (!m_useHard) { + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + + qDebug() << "atempting to run with privelage"; + connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](){ + emitSucceeded(); + }); + m_lnk->runPrivlaged(); + } else { + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); + } +#else + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); +#endif + } else { + emitSucceeded(); + } + + }; + + FS::create_link *m_lnk; + bool m_useHard = false; + bool m_linkRecursive = true; +}; + + class FileSystemTest : public QObject { Q_OBJECT @@ -273,7 +335,7 @@ slots: void test_link() { QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); - auto f = [&folder]() + auto f = [&folder, this]() { QTemporaryDir tempDir; tempDir.setAutoRemove(true); @@ -282,17 +344,17 @@ slots: QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); qDebug() << tempDir.path(); qDebug() << target_dir.path(); - FS::create_link lnk(folder, target_dir.path()); - lnk.linkRecursively(false); - lnk.debug(true); - if(!lnk()){ -#if defined Q_OS_WIN32 - qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - QVERIFY(lnk.getLastOSError() == 1314); - return; -#endif - qDebug() << "Link Failed!" << lnk.getLastOSError(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(false); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); for(auto entry: target_dir.entryList()) { @@ -337,7 +399,7 @@ slots: lnk.useHardLinks(true); lnk.debug(true); if(!lnk()){ - qDebug() << "Link Failed!" << lnk.getLastOSError(); + qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); } for(auto entry: target_dir.entryList()) @@ -385,18 +447,19 @@ slots: QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); qDebug() << tempDir.path(); qDebug() << target_dir.path(); - FS::create_link lnk(folder, target_dir.path()); - lnk.matcher(new RegexpMatcher("[.]?mcmeta")); - lnk.linkRecursively(true); - lnk.debug(true); - if(!lnk()){ -#if defined Q_OS_WIN32 - qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - QVERIFY(lnk.getLastOSError() == 1314); - return; -#endif - qDebug() << "Link Failed!" << lnk.getLastOSError(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk_tsk.linkRecursively(true); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); + for(auto entry: target_dir.entryList()) { @@ -435,19 +498,19 @@ slots: QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); qDebug() << tempDir.path(); qDebug() << target_dir.path(); - FS::create_link lnk(folder, target_dir.path()); - lnk.matcher(new RegexpMatcher("[.]?mcmeta")); - lnk.whitelist(true); - lnk.linkRecursively(true); - lnk.debug(true); - if(!lnk()){ -#if defined Q_OS_WIN32 - qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - QVERIFY(lnk.getLastOSError() == 1314); - return; -#endif - qDebug() << "Link Failed!" << lnk.getLastOSError(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk_tsk.linkRecursively(true); + lnk_tsk.whitelist(true); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); for(auto entry: target_dir.entryList()) { @@ -486,17 +549,17 @@ slots: QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); qDebug() << tempDir.path(); qDebug() << target_dir.path(); - FS::create_link lnk(folder, target_dir.path()); - lnk.linkRecursively(true); - lnk.debug(true); - if(!lnk()){ -#if defined Q_OS_WIN32 - qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - QVERIFY(lnk.getLastOSError() == 1314); - return; -#endif - qDebug() << "Link Failed!" << lnk.getLastOSError(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; @@ -538,16 +601,16 @@ slots: QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta")); qDebug() << tempDir.path(); qDebug() << target_dir.path(); - FS::create_link lnk(file, target_dir.filePath("pack.mcmeta")); - lnk.debug(true); - if(!lnk()){ -#if defined Q_OS_WIN32 - qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - QVERIFY(lnk.getLastOSError() == 1314); - return; -#endif - qDebug() << "Link Failed!" << lnk.getLastOSError(); - } + + LinkTask lnk_tsk(file, target_dir.filePath("pack.mcmeta")); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); auto filter = QDir::Filter::Files; -- cgit From 8ba51c790098ec9ebe3d2ef686f823b61c8a3645 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:36:15 -0800 Subject: refactor: make complete list of links to make and send that. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 221 +++++++++++++++++++++++++++-------------- launcher/FileSystem.h | 35 +++++-- launcher/filelink/FileLink.cpp | 134 ++++++++++++++++++++----- launcher/filelink/FileLink.h | 6 +- tests/FileSystem_test.cpp | 11 +- 5 files changed, 293 insertions(+), 114 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 9e51f932..c48a3bba 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -66,7 +66,6 @@ #include //for ShellExecute #include -//#include #include #include #else @@ -228,94 +227,120 @@ bool copy::operator()(const QString& offset, bool dryRun) bool create_link::operator()(const QString& offset, bool dryRun) { + m_linked = 0; // reset counter + m_path_results.clear(); + m_links_to_make.clear(); + + m_path_results.clear(); + + make_link_list(offset); + + if (!dryRun) + return make_links(); - for (auto pair : m_path_pairs) { - if (!make_link(pair.src, pair.dst, offset, dryRun)) { - return false; - } - } return true; } /** - * @brief links a directory and it's contents from src to dest + * @brief make a list off all the links ot make * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ -bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun) +void create_link::make_link_list( const QString& offset) { - m_linked = 0; // reset counter + for (auto pair : m_path_pairs) { + const QString& srcPath = pair.src; + const QString& dstPath = pair.dst; - auto src = PathCombine(QDir(srcPath).absolutePath(), offset); - auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); + auto src = PathCombine(QDir(srcPath).absolutePath(), offset); + auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); - // you can't hard link a directory so make sure if we deal with a directory we do so recursively - if (m_useHardLinks) - m_recursive = true; + // you can't hard link a directory so make sure if we deal with a directory we do so recursively + if (m_useHardLinks) + m_recursive = true; - // Function that'll do the actual linking - auto link_file = [&](QString src_path, QString relative_dst_path) { - if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) - return; + // Function that'll do the actual linking + auto link_file = [&](QString src_path, QString relative_dst_path) { + if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) { + qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; + return; + } + - auto dst_path = PathCombine(dst, relative_dst_path); - if (!dryRun) { - - ensureFilePathExists(dst_path); - if (m_useHardLinks) { - if (m_debug) - qDebug() << "making hard link:" << src_path << "to" << dst_path; - fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); - } else if (fs::is_directory(StringUtils::toStdString(src_path))) { - if (m_debug) - qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; - fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); - } else { - if (m_debug) - qDebug() << "making symlink:" << src_path << "to" << dst_path; - fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + auto dst_path = PathCombine(dst, relative_dst_path); + LinkPair link = {src_path, dst_path}; + m_links_to_make.append(link); + }; + + if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) { + if (m_debug) + qDebug() << "linking single file or dir:" << src << "to" << dst; + link_file(src, ""); + } else { + if (m_debug) + qDebug() << "linking recursivly:" << src << "to" << dst; + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + link_file(src_path, relative_path); } - } + } +} + +bool create_link::make_links() +{ + for (auto link : m_links_to_make) { + + QString src_path = link.src; + QString dst_path = link.dst; + + ensureFilePathExists(dst_path); + if (m_useHardLinks) { + if (m_debug) + qDebug() << "making hard link:" << src_path << "to" << dst_path; + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + if (m_debug) + qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + } else { + if (m_debug) + qDebug() << "making symlink:" << src_path << "to" << dst_path; + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + } + + if (m_os_err) { qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; qDebug() << "Error catagory:" << m_os_err.category().name(); qDebug() << "Error code:" << m_os_err.value(); - emit linkFailed(src_path, dst_path, m_os_err); + emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value()); } else { m_linked++; - emit fileLinked(relative_dst_path); - } - - }; - - if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) { - if (m_debug) - qDebug() << "linking single file or dir:" << src << "to" << dst; - link_file(src, ""); - } else { - if (m_debug) - qDebug() << "linking recursivly:" << src << "to" << dst; - QDir src_dir(src); - QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); - - while (source_it.hasNext()) { - auto src_path = source_it.next(); - auto relative_path = src_dir.relativeFilePath(src_path); - - link_file(src_path, relative_path); - if (m_os_err) return false; + emit fileLinked(src_path, dst_path); } + if (m_os_err) return false; } - - return m_os_err.value() == 0; + return true; } -bool create_link::runPrivlaged(const QString& offset) +void create_link::runPrivlaged(const QString& offset) { + m_linked = 0; // reset counter + m_path_results.clear(); + m_links_to_make.clear(); + + bool gotResults = false; + + make_link_list(offset); QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8); @@ -325,25 +350,72 @@ bool create_link::runPrivlaged(const QString& offset) // construct block of data to send QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_15); // choose correct version better? + out.setVersion(QDataStream::Qt_5_0); // choose correct version better? qint32 blocksize = quint32(sizeof(quint32)); - for (auto pair : m_path_pairs) { - blocksize += quint32(pair.src.size()); - blocksize += quint32(pair.dst.size()); + for (auto link : m_links_to_make) { + blocksize += quint32(link.src.size()); + blocksize += quint32(link.dst.size()); } qDebug() << "About to write block of size:" << blocksize; out << blocksize; - out << quint32(m_path_pairs.length()); - for (auto pair : m_path_pairs) { - out << pair.src; - out << pair.dst; + out << quint32(m_links_to_make.length()); + for (auto link : m_links_to_make) { + out << link.src; + out << link.dst; } QLocalSocket *clientConnection = m_linkServer.nextPendingConnection(); connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater); + + connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){ + QDataStream in; + quint32 blockSize = 0; + in.setDevice(clientConnection); + in.setVersion(QDataStream::Qt_5_0); + qDebug() << "Reading path results from client"; + qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + + // Relies on the fact that QDataStream serializes a quint32 into + // sizeof(quint32) bytes + if (clientConnection->bytesAvailable() < (int)sizeof(quint32)) + return; + qDebug() << "reading block size"; + in >> blockSize; + + qDebug() << "blocksize is" << blockSize; + qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + if (clientConnection->bytesAvailable() < blockSize || in.atEnd()) + return; + + quint32 numResults; + in >> numResults; + qDebug() << "numResults" << numResults; + + for(int i = 0; i < numResults; i++) { + FS::LinkResult result; + in >> result.src; + in >> result.dst; + in >> result.err_msg; + qint32 err_value; + in >> err_value; + result.err_value = err_value; + if (result.err_value) { + qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; + emit linkFailed(result.src, result.dst, result.err_msg, result.err_value); + } else { + qDebug() << "privlaged link success" << result.src << "to" << result.dst; + m_linked++; + emit fileLinked(result.src, result.dst); + } + m_path_results.append(result); + } + gotResults = true; + qDebug() << "results recieved, closing connection"; + clientConnection->close(); + }); qint64 byteswritten = clientConnection->write(block); bool bytesflushed = clientConnection->flush(); @@ -354,20 +426,15 @@ bool create_link::runPrivlaged(const QString& offset) qDebug() << "Listening on pipe" << serverName; if (!m_linkServer.listen(serverName)) { qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString(); - return false; + return; } - ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){ - emit finishedPrivlaged(); - }); + ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); - // linkFileProcess->wait(); - - return true; } @@ -375,6 +442,8 @@ void ExternalLinkFileProcess::runLinkFile() { QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); QString params = "-s " + m_server; + params += " -H " + QVariant(m_useHardLinks).toString(); + #if defined Q_OS_WIN32 SHELLEXECUTEINFO ShExecInfo; HRESULT hr; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b15d1685..2e739298 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -133,13 +133,22 @@ struct LinkPair { QString dst; }; -class ExternalLinkFileProcess : public QThread -{ +struct LinkResult { + QString src; + QString dst; + QString err_msg; + int err_value; +}; + +class ExternalLinkFileProcess : public QThread { Q_OBJECT public: - ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {} + ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr) + : QThread(parent), m_server(server), m_useHardLinks(useHardLinks) + {} - void run() override { + void run() override + { runLinkFile(); emit processExited(); } @@ -150,6 +159,8 @@ class ExternalLinkFileProcess : public QThread private: void runLinkFile(); + bool m_useHardLinks = false; + QString m_server; }; @@ -200,19 +211,21 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } - bool runPrivlaged() { return runPrivlaged(QString()); } - bool runPrivlaged(const QString& offset); + void runPrivlaged() { runPrivlaged(QString()); } + void runPrivlaged(const QString& offset); int totalLinked() { return m_linked; } signals: - void fileLinked(const QString& relativeName); - void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); - void finishedPrivlaged(); + void fileLinked(const QString& srcName, const QString& dstName); + void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); + void finishedPrivlaged(bool gotResults); + void finished(); private: bool operator()(const QString& offset, bool dryRun = false); - bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun); + void make_link_list(const QString& offset); + bool make_links(); private: bool m_useHardLinks = false; @@ -221,6 +234,8 @@ class create_link : public QObject { bool m_recursive = true; QList m_path_pairs; + QList m_path_results; + QList m_links_to_make; int m_linked; bool m_debug = false; diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index 78486507..a731ecdb 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -23,6 +23,8 @@ #include "FileLink.h" #include "BuildConfig.h" +#include "StringUtils.h" + #include @@ -43,6 +45,24 @@ #include #endif +// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header + +#ifdef __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif // __APPLE__ + +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs = std::filesystem; +#endif // MacOS min version check +#endif // Other OSes version check + +#ifndef GHC_USE_STD_FS +#include +namespace fs = ghc::filesystem; +#endif @@ -82,7 +102,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher")); parser.addOptions({ - {{"s", "server"}, "Join the specified server on launch", "pipe name"} + {{"s", "server"}, "Join the specified server on launch", "pipe name"}, + {{"H", "hard"}, "use hard links insted of symbolic", "true/false"} }); parser.addHelpOption(); parser.addVersionOption(); @@ -90,6 +111,7 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), parser.process(arguments()); QString serverToJoin = parser.value("server"); + m_useHardLinks = QVariant(parser.value("hard")).toBool(); qDebug() << "link program launched"; @@ -109,7 +131,7 @@ void FileLinkApp::joinServer(QString server) blockSize = 0; in.setDevice(&socket); - in.setVersion(QDataStream::Qt_5_15); + in.setVersion(QDataStream::Qt_5_0); connect(&socket, &QLocalSocket::connected, this, [&](){ qDebug() << "connected to server"; @@ -120,25 +142,27 @@ void FileLinkApp::joinServer(QString server) connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ switch (socketError) { case QLocalSocket::ServerNotFoundError: - qDebug() << tr("The host was not found. Please make sure " - "that the server is running and that the " - "server name is correct."); + qDebug() << ("The host was not found. Please make sure " + "that the server is running and that the " + "server name is correct."); break; case QLocalSocket::ConnectionRefusedError: - qDebug() << tr("The connection was refused by the peer. " - "Make sure the server is running, " - "and check that the server name " - "is correct."); + qDebug() << ("The connection was refused by the peer. " + "Make sure the server is running, " + "and check that the server name " + "is correct."); break; case QLocalSocket::PeerClosedError: + qDebug() << ("The connection was closed by the peer. "); break; default: - qDebug() << tr("The following error occurred: %1.").arg(socket.errorString()); + qDebug() << "The following error occurred: " << socket.errorString(); } }); connect(&socket, &QLocalSocket::disconnected, this, [&](){ - qDebug() << "dissconnected from server"; + qDebug() << "dissconnected from server, should exit"; + exit(); }); socket.connectToServer(server); @@ -147,20 +171,82 @@ void FileLinkApp::joinServer(QString server) } void FileLinkApp::runLink() +{ + + std::error_code os_err; + + qDebug() << "creating links"; + + for (auto link : m_links_to_make) { + + QString src_path = link.src; + QString dst_path = link.dst; + + FS::ensureFilePathExists(dst_path); + if (m_useHardLinks) { + qDebug() << "making hard link:" << src_path << "to" << dst_path; + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); + } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); + } else { + qDebug() << "making symlink:" << src_path << "to" << dst_path; + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); + } + + if (os_err) { + qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + qDebug() << "Error catagory:" << os_err.category().name(); + qDebug() << "Error code:" << os_err.value(); + + FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()}; + m_path_results.append(result); + } else { + FS::LinkResult result = {src_path, dst_path}; + m_path_results.append(result); + } + } + + sendResults(); + qDebug() << "done, should exit soon"; + +} + +void FileLinkApp::sendResults() { - qDebug() << "creating link"; - FS::create_link lnk(m_path_pairs); - lnk.debug(true); - if (!lnk()) { - qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); + // construct block of data to send + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_5_0); + + qint32 blocksize = quint32(sizeof(quint32)); + for (auto result : m_path_results) { + blocksize += quint32(result.src.size()); + blocksize += quint32(result.dst.size()); + blocksize += quint32(result.err_msg.size()); + blocksize += quint32(sizeof(quint32)); } - //exit(); - qDebug() << "done, should exit"; + qDebug() << "About to write block of size:" << blocksize; + out << blocksize; + + out << quint32(m_path_results.length()); + for (auto result : m_path_results) { + out << result.src; + out << result.dst; + out << result.err_msg; + out << quint32(result.err_value); + } + + qint64 byteswritten = socket.write(block); + bool bytesflushed = socket.flush(); + qDebug() << "block flushed" << byteswritten << bytesflushed; } void FileLinkApp::readPathPairs() { - m_path_pairs.clear(); + m_links_to_make.clear(); qDebug() << "Reading path pairs from server"; qDebug() << "bytes avalible" << socket.bytesAvailable(); if (blockSize == 0) { @@ -176,16 +262,16 @@ void FileLinkApp::readPathPairs() if (socket.bytesAvailable() < blockSize || in.atEnd()) return; - quint32 numPairs; - in >> numPairs; - qDebug() << "numPairs" << numPairs; + quint32 numLinks; + in >> numLinks; + qDebug() << "numLinks" << numLinks; - for(int i = 0; i < numPairs; i++) { + for(int i = 0; i < numLinks; i++) { FS::LinkPair pair; in >> pair.src; in >> pair.dst; qDebug() << "link" << pair.src << "to" << pair.dst; - m_path_pairs.append(pair); + m_links_to_make.append(pair); } runLink(); diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index 5d0ba123..d146b8d9 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -52,13 +52,17 @@ private: void joinServer(QString server); void readPathPairs(); void runLink(); + void sendResults(); + + bool m_useHardLinks = false; QDateTime m_startTime; QLocalSocket socket; QDataStream in; quint32 blockSize; - QList m_path_pairs; + QList m_links_to_make; + QList m_path_results; #if defined Q_OS_WIN32 // used on Windows to attach the standard IO streams diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index be0a4be0..4ccc4003 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -66,12 +66,17 @@ class LinkTask : public Task { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "atempting to run with privelage"; - connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](){ - emitSucceeded(); + connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + if (gotResults) { + emitSucceeded(); + } else { + qDebug() << "Privlaged run exited without results!"; + emitFailed(); + } }); m_lnk->runPrivlaged(); } else { - qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); } #else qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); -- cgit From 59788823785c186af78d8100fce3bdedbed85c80 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 14:30:45 -0800 Subject: feat(symlinks&hardlinks): linkup copy dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 5 +- launcher/FileSystem.h | 9 +++- launcher/InstanceCopyPrefs.cpp | 8 ++-- launcher/InstanceCopyPrefs.h | 6 +-- launcher/InstanceCopyTask.cpp | 73 ++++++++++++++++++++++++++++-- launcher/InstanceCopyTask.h | 3 ++ launcher/ui/dialogs/CopyInstanceDialog.cpp | 7 +-- launcher/ui/dialogs/CopyInstanceDialog.h | 2 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 9 ++-- 9 files changed, 96 insertions(+), 26 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c48a3bba..c94770ee 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -420,7 +420,7 @@ void create_link::runPrivlaged(const QString& offset) qint64 byteswritten = clientConnection->write(block); bool bytesflushed = clientConnection->flush(); qDebug() << "block flushed" << byteswritten << bytesflushed; - //clientConnection->disconnectFromServer(); + }); qDebug() << "Listening on pipe" << serverName; @@ -437,7 +437,6 @@ void create_link::runPrivlaged(const QString& offset) } - void ExternalLinkFileProcess::runLinkFile() { QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); QString params = "-s " + m_server; @@ -463,7 +462,7 @@ void ExternalLinkFileProcess::runLinkFile() { ShExecInfo.lpFile = programNameWin; ShExecInfo.lpParameters = paramsWin; ShExecInfo.lpDirectory = NULL; - ShExecInfo.nShow = SW_NORMAL; + ShExecInfo.nShow = SW_HIDE; ShExecInfo.hInstApp = NULL; ShellExecuteEx(&ShExecInfo); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 2e739298..d79096e6 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -211,16 +211,21 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + int totalLinked() { return m_linked; } + + void runPrivlaged() { runPrivlaged(QString()); } void runPrivlaged(const QString& offset); - int totalLinked() { return m_linked; } + QList getResults() { return m_path_results; } + signals: void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); - void finishedPrivlaged(bool gotResults); void finished(); + void finishedPrivlaged(bool gotResults); + private: bool operator()(const QString& offset, bool dryRun = false); diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 18a6d704..e363d4c6 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -103,9 +103,9 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const return useHardLinks; } -bool InstanceCopyPrefs::isLinkWorldsEnabled() const +bool InstanceCopyPrefs::isDontLinkSavesEnabled() const { - return linkWorlds; + return dontLinkSaves; } // ======= Setters ======= @@ -159,7 +159,7 @@ void InstanceCopyPrefs::enableUseHardLinks(bool b) useHardLinks = b; } -void InstanceCopyPrefs::enableLinkWorlds(bool b) +void InstanceCopyPrefs::enableDontLinkSaves(bool b) { - linkWorlds = b; + dontLinkSaves = b; } diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 25c0f3fc..61719a06 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -21,7 +21,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyScreenshotsEnabled() const; [[nodiscard]] bool isLinkFilesEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; - [[nodiscard]] bool isLinkWorldsEnabled() const; + [[nodiscard]] bool isDontLinkSavesEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -33,7 +33,7 @@ struct InstanceCopyPrefs { void enableCopyScreenshots(bool b); void enableLinkFiles(bool b); void enableUseHardLinks(bool b); - void enableLinkWorlds(bool b); + void enableDontLinkSaves(bool b); protected: // data bool copySaves = true; @@ -46,5 +46,5 @@ struct InstanceCopyPrefs { bool copyScreenshots = true; bool linkFiles = false; bool useHardLinks = false; - bool linkWorlds = true; + bool dontLinkSaves = false; }; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 188d163b..31c6bdca 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -11,6 +11,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); QString filters = prefs.getSelectedFiltersAsRegex(); + + m_useLinks = prefs.isLinkFilesEnabled(); + m_useHardLinks = prefs.isUseHardLinksEnabled(); + m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); + if (!filters.isEmpty()) { // Set regex filter: @@ -25,11 +30,71 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{ - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); + auto copySaves = [&](){ + FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves")); + savesCopy.followSymlinks(false); + + return savesCopy(); + }; + + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ + if (m_useLinks) { + FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); + folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + + bool there_were_errors = false; + + if(!folderLink()){ +#if defined Q_OS_WIN32 + if (!m_useHardLinks) { + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + + qDebug() << "atempting to run with privelage"; + + QEventLoop loop; + bool got_priv_results = false; + + connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + if (!gotResults) { + qDebug() << "Privlaged run exited without results!"; + } + got_priv_results = gotResults; + loop.quit(); + }); + folderLink.runPrivlaged(); + + loop.exec(); // wait for the finished signal + + for (auto result : folderLink.getResults()) { + if (result.err_value != 0) { + there_were_errors = true; + } + } + + if (m_copySaves) { + there_were_errors |= !copySaves(); + } + + return got_priv_results && !there_were_errors; + } else { + qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); + } +#else + qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); +#endif return false; + } + + if (m_copySaves) { + there_were_errors |= !copySaves(); + } + + return !there_were_errors; + } else { + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(false).matcher(m_matcher.get()); - return folderCopy(); + return folderCopy(); + } }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 1f29b854..d9651b07 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -30,4 +30,7 @@ private: QFutureWatcher m_copyFutureWatcher; std::unique_ptr m_matcher; bool m_keepPlaytime; + bool m_useLinks = false; + bool m_useHardLinks = false; + bool m_copySaves = true; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 981352ae..e477b4b3 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -88,7 +88,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); - ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled()); + ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -179,6 +179,7 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { m_selectedOptions.enableCopySaves(state == Qt::Checked); + ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked()); updateSelectAllCheckbox(); } @@ -235,7 +236,7 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) m_selectedOptions.enableUseHardLinks(state == Qt::Checked); } -void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state) +void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { - m_selectedOptions.enableLinkWorlds(state == Qt::Checked); + m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index a80faab9..57775925 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -57,7 +57,7 @@ slots: void on_copyScreenshotsCheckbox_stateChanged(int state); void on_linkFilesGroup_toggled(bool checked); void on_hardLinksCheckbox_stateChanged(int state); - void on_linkWorldsCheckbox_stateChanged(int state); + void on_dontLinkSavesCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index e41ad526..d8eb96eb 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -240,17 +240,14 @@ - + - World save data will be linked and thus shared between instances. + If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. - Link worlds + Don't link saves - true - - false -- cgit From c9105e525e175ee8181ab0a6998d0e21526f116d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:52:50 -0700 Subject: fix: follow symlinks when exporting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.h | 2 +- launcher/MMCZip.cpp | 9 ++++++--- launcher/MMCZip.h | 6 ++++-- launcher/ui/dialogs/ExportInstanceDialog.cpp | 5 ++++- 4 files changed, 15 insertions(+), 7 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index d79096e6..782a2f40 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -144,7 +144,7 @@ class ExternalLinkFileProcess : public QThread { Q_OBJECT public: ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr) - : QThread(parent), m_server(server), m_useHardLinks(useHardLinks) + : QThread(parent), m_useHardLinks(useHardLinks), m_server(server) {} void run() override diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1eda43fe..b4b663c1 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -94,20 +94,23 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet &containe return true; } -bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files) +bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks) { QDir directory(dir); if (!directory.exists()) return false; for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); - if( !JlCompress::compressFile(zip, e.absoluteFilePath(), filePath)) return false; + auto srcPath = e.absoluteFilePath(); + if (followSymlinks) + srcPath = e.canonicalFilePath(); + if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; } return true; } -bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files) +bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) { QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 81f9cb90..2a78f830 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -59,18 +59,20 @@ namespace MMCZip * \param zip target archive * \param dir directory that will be compressed (to compress with relative paths) * \param files list of files to compress + * \param followSymlinks should follow symlinks when compressing file data * \return true for success or false for failure */ - bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files); + bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false); /** * Compress directory, by providing a list of files to compress * \param fileCompressed target archive file * \param dir directory that will be compressed (to compress with relative paths) * \param files list of files to compress + * \param followSymlinks should follow symlinks when compressing file data * \return true for success or false for failure */ - bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files); + bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); /** * take a source jar, add mods to it, resulting in target jar diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index f13e36e8..07ec3c70 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -45,6 +45,8 @@ #include #include #include +#include + #include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" @@ -429,7 +431,8 @@ bool ExportInstanceDialog::doExport() QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; } - if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files)) + + if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true)) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; -- cgit From c5bbe42b57075a4b428d0be1c1ca9f51701a1a7c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 23:42:13 -0700 Subject: feat: reflink / Clone support! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ launcher/FileSystem.h | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c94770ee..7b7fc80b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +36,9 @@ */ #include "FileSystem.h" +#include +#include +#include #include "BuildConfig.h" @@ -48,6 +52,7 @@ #include #include #include +#include #include "DesktopServices.h" #include "StringUtils.h" @@ -91,6 +96,18 @@ namespace fs = std::filesystem; namespace fs = ghc::filesystem; #endif + +// clone +#if defined(Q_OS_LINUX) +#include +#include /* Definition of FICLONE* constants */ +#include +#include +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +#include +#include +#endif + namespace FS { void ensureExists(const QDir& dir) @@ -831,4 +848,193 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } + +/** + * @brief colect information about the filesystem under a file + * + */ +FilesystemInfo statFS(QString path) +{ + + FilesystemInfo info; + + QStorageInfo storage_info(path); + + QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + + for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { + auto fs_type_name = fs_type_pair.first; + auto fs_type = fs_type_pair.second; + + if(fsTypeName.contains(fs_type_name.toLower())) { + info.fsType = fs_type; + break; + } + } + + info.blockSize = storage_info.blockSize(); + info.bytesAvailable = storage_info.bytesAvailable(); + info.bytesFree = storage_info.bytesFree(); + info.bytesTotal = storage_info.bytesTotal(); + + info.name = storage_info.name(); + info.rootPath = storage_info.rootPath(); + + return info; +} + +/** + * @brief if the Filesystem is reflink/clone capable + * + */ +bool canCloneOnFS(const QString& path) +{ + FilesystemInfo info = statFS(path); + return canCloneOnFS(info); +} +bool canCloneOnFS(const FilesystemInfo& info) +{ + return canCloneOnFS(info.fsType); +} +bool canCloneOnFS(FilesystemType type) +{ + return s_clone_filesystems.contains(type); +} + +/** + * @brief if the Filesystem is reflink/clone capable and both paths are on the same device + * + */ +bool canClone(const QString& src, const QString& dst) +{ + auto srcVInfo = statFS(src); + auto dstVInfo = statFS(dst); + + bool sameDevice = srcVInfo.rootPath == dstVInfo.rootPath; + + return sameDevice && canCloneOnFS(srcVInfo) && canCloneOnFS(dstVInfo); +} + +/** + * @brief reflink/clones a directory and it's contents from src to dest + * @param offset subdirectory form src to copy to dest + * @return if there was an error during the filecopy + */ +bool clone::operator()(const QString& offset, bool dryRun) +{ + + if (!canClone(m_src.absolutePath(), m_dst.absolutePath())) { + qWarning() << "Can not clone: not same device or not clone/reflink filesystem"; + qDebug() << "Source path:" << m_src.absolutePath(); + qDebug() << "Destination path:" << m_dst.absolutePath(); + emit cloneFailed(m_src.absolutePath(), m_dst.absolutePath()); + return false; + } + + m_cloned = 0; // reset counter + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + std::error_code err; + + // Function that'll do the actual cloneing + auto cloneFile = [&](QString src_path, QString relative_dst_path) { + if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) + return; + + auto dst_path = PathCombine(dst, relative_dst_path); + if (!dryRun) { + ensureFilePathExists(dst_path); + clone_file(src_path, dst_path, err); + } + if (err) { + qWarning() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + } + m_cloned++; + emit fileCloned(src_path, dst_path); + }; + + // We can't use copy_opts::recursive because we need to take into account the + // blacklisted paths, so we iterate over the source directory, and if there's no blacklist + // match, we copy the file. + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + cloneFile(src_path, relative_path); + } + + // If the root src is not a directory, the previous iterator won't run. + if (!fs::is_directory(StringUtils::toStdString(src))) + cloneFile(src, ""); + + return err.value() == 0; +} + +/** + * @brief clone/reflink file from src to dst + * + */ +bool clone_file(const QString& src, const QString& dst, std::error_code& ec) +{ + auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath()); + auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath()); + +#if defined(Q_OS_WIN) + qWarning("clone/reflink not supported on windows!"); + ec = std::make_error_code(std::errc::not_supported); + return false; +#elif defined(Q_OS_LINUX) + + // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html + + int src_fd = open(src_path.c_str(), O_RDONLY); + if(!src_fd) { + qWarning() << "Failed to open file:" << src_path.c_str(); + qDebug() << "Error:" << strerror(errno); + ec = std::make_error_code(static_cast(errno)); + return false; + } + int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC); + if(!dst_fd) { + qWarning() << "Failed to open file:" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + ec = std::make_error_code(static_cast(errno)); + return false; + } + // attempt to clone + if(!ioctl(dst_fd, FICLONE, src_fd)){ + qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + ec = std::make_error_code(static_cast(errno)); + return false; + } + +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + // TODO: use clonefile + // clonefile(const char * src, const char * dst, int flags); + // https://www.manpagez.com/man/2/clonefile/ + + if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) { + qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + ec = std::make_error_code(static_cast(errno)); + return false; + } + +#else + qWarning("clone/reflink not supported! unknown OS"); + ec = std::make_error_code(std::errc::not_supported); + return false; +#endif + + return true; +} + } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 782a2f40..531036dd 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -313,4 +314,144 @@ bool overrideFolder(QString overwritten_path, QString override_path); * Creates a shortcut to the specified target file at the specified destination path. */ bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon); + +enum class FilesystemType { + FAT, + NTFS, + EXT, + EXT_2_OLD, + EXT_2_3_4, + XFS, + BTRFS, + NFS, + ZFS, + APFS, + HFS, + HFSPLUS, + HFSX, + UNKNOWN +}; + +static const QMap s_filesystem_type_names = { + {FilesystemType::FAT, QString("FAT")}, + {FilesystemType::NTFS, QString("NTFS")}, + {FilesystemType::EXT, QString("EXT")}, + {FilesystemType::EXT_2_OLD, QString("EXT2_OLD")}, + {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, + {FilesystemType::XFS, QString("XFS")}, + {FilesystemType::BTRFS, QString("BTRFS")}, + {FilesystemType::NFS, QString("NFS")}, + {FilesystemType::ZFS, QString("ZFS")}, + {FilesystemType::APFS, QString("APFS")}, + {FilesystemType::HFS, QString("HFS")}, + {FilesystemType::HFSPLUS, QString("HFSPLUS")}, + {FilesystemType::HFSX, QString("HFSX")}, + {FilesystemType::UNKNOWN, QString("UNKNOWN")} +}; + +static const QMap s_filesystem_type_names_inverse = { + {QString("FAT"), FilesystemType::FAT}, + {QString("NTFS"), FilesystemType::NTFS}, + {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD}, + {QString("EXT2"), FilesystemType::EXT_2_3_4}, + {QString("EXT3"), FilesystemType::EXT_2_3_4}, + {QString("EXT4"), FilesystemType::EXT_2_3_4}, + {QString("EXT"), FilesystemType::EXT}, + {QString("XFS"), FilesystemType::XFS}, + {QString("BTRFS"), FilesystemType::BTRFS}, + {QString("NFS"), FilesystemType::NFS}, + {QString("ZFS"), FilesystemType::ZFS}, + {QString("APFS"), FilesystemType::APFS}, + {QString("HFSPLUS"), FilesystemType::HFSPLUS}, + {QString("HFSX"), FilesystemType::HFSX}, + {QString("HFS"), FilesystemType::HFS}, + {QString("UNKNOWN"), FilesystemType::UNKNOWN} +}; + +inline QString getFilesystemTypeName(FilesystemType type) { + return s_filesystem_type_names.constFind(type).value(); +} + +struct FilesystemInfo { + FilesystemType fsType = FilesystemType::UNKNOWN; + int blockSize; + qint64 bytesAvailable; + qint64 bytesFree; + qint64 bytesTotal; + QString name; + QString rootPath; +}; + +/** + * @brief colect information about the filesystem under a file + * + */ +FilesystemInfo statFS(QString path); + + +static const QList s_clone_filesystems = { + FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS +}; + +/** + * @brief if the Filesystem is reflink/clone capable + * + */ +bool canCloneOnFS(const QString& path); +bool canCloneOnFS(const FilesystemInfo& info); +bool canCloneOnFS(FilesystemType type); + +/** + * @brief if the Filesystem is reflink/clone capable and both are on the same device + * + */ +bool canClone(const QString& src, const QString& dst); + +/** + * @brief Copies a directory and it's contents from src to dest + */ +class clone : public QObject { + Q_OBJECT + public: + clone(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) + { + m_src.setPath(src); + m_dst.setPath(dst); + } + clone& matcher(const IPathMatcher* filter) + { + m_matcher = filter; + return *this; + } + clone& whitelist(bool whitelist) + { + m_whitelist = whitelist; + return *this; + } + + bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + + int totalCloned() { return m_cloned; } + + signals: + void fileCloned(const QString& src, const QString& dst); + void cloneFailed(const QString& src, const QString& dst); + + private: + bool operator()(const QString& offset, bool dryRun = false); + + private: + const IPathMatcher* m_matcher = nullptr; + bool m_whitelist = false; + QDir m_src; + QDir m_dst; + int m_cloned; +}; + +/** + * @brief clone/reflink file from src to dst + * + */ +bool clone_file(const QString& src, const QString& dst, std::error_code& ec); + } -- cgit From 397e7f036339b09569317300423261f2b37d6119 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 02:02:40 -0700 Subject: feat(reflink): hook up relink / clone on the copy dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 26 ++++++++++++++---- launcher/FileSystem.h | 3 +++ launcher/InstanceCopyPrefs.cpp | 10 +++++++ launcher/InstanceCopyPrefs.h | 3 +++ launcher/InstanceCopyTask.cpp | 11 ++++++-- launcher/InstanceCopyTask.h | 1 + launcher/ui/dialogs/CopyInstanceDialog.cpp | 28 ++++++++++++++++++- launcher/ui/dialogs/CopyInstanceDialog.h | 5 ++++ launcher/ui/dialogs/CopyInstanceDialog.ui | 43 +++++++++++++++++++++++++++--- 9 files changed, 119 insertions(+), 11 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7b7fc80b..fd09842f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -103,6 +103,7 @@ namespace fs = ghc::filesystem; #include /* Definition of FICLONE* constants */ #include #include +#include #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #include #include @@ -880,6 +881,9 @@ FilesystemInfo statFS(QString path) info.name = storage_info.name(); info.rootPath = storage_info.rootPath(); + qDebug() << "Pulling filesystem info for" << info.rootPath; + qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType); + return info; } @@ -995,33 +999,45 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html int src_fd = open(src_path.c_str(), O_RDONLY); - if(!src_fd) { + if(src_fd == -1) { qWarning() << "Failed to open file:" << src_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); return false; } - int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC); - if(!dst_fd) { + int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if(dst_fd == -1) { qWarning() << "Failed to open file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); + close(src_fd); return false; } // attempt to clone - if(!ioctl(dst_fd, FICLONE, src_fd)){ + if(ioctl(dst_fd, FICLONE, src_fd) == -1){ qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); + close(src_fd); + close(dst_fd); return false; } + if(close(src_fd)) { + qWarning() << "Failed to close file:" << src_path.c_str(); + qDebug() << "Error:" << strerror(errno); + } + if(close(dst_fd)) { + qWarning() << "Failed to close file:" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + } #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) // TODO: use clonefile // clonefile(const char * src, const char * dst, int flags); // https://www.manpagez.com/man/2/clonefile/ - if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) { + qDebug() << "attempting file clone via clonefile" << src << "to" << dst; + if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) { qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 531036dd..aa28de93 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -329,6 +329,7 @@ enum class FilesystemType { HFS, HFSPLUS, HFSX, + FUSEBLK, UNKNOWN }; @@ -346,6 +347,7 @@ static const QMap s_filesystem_type_names = { {FilesystemType::HFS, QString("HFS")}, {FilesystemType::HFSPLUS, QString("HFSPLUS")}, {FilesystemType::HFSX, QString("HFSX")}, + {FilesystemType::FUSEBLK, QString("FUSEBLK")}, {FilesystemType::UNKNOWN, QString("UNKNOWN")} }; @@ -365,6 +367,7 @@ static const QMap s_filesystem_type_names_inverse = { {QString("HFSPLUS"), FilesystemType::HFSPLUS}, {QString("HFSX"), FilesystemType::HFSX}, {QString("HFS"), FilesystemType::HFS}, + {QString("FUSEBLK"), FilesystemType::FUSEBLK}, {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 59825ced..f2aa5e69 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -113,6 +113,11 @@ bool InstanceCopyPrefs::isDontLinkSavesEnabled() const return dontLinkSaves; } +bool InstanceCopyPrefs::isUseCloneEnabled() const +{ + return useClone; +} + // ======= Setters ======= void InstanceCopyPrefs::enableCopySaves(bool b) { @@ -173,3 +178,8 @@ void InstanceCopyPrefs::enableDontLinkSaves(bool b) { dontLinkSaves = b; } + +void InstanceCopyPrefs::enableUseClone(bool b) +{ + useClone = b; +} \ No newline at end of file diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 9fc9dcab..583fdd4c 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -23,6 +23,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isLinkRecursivelyEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; [[nodiscard]] bool isDontLinkSavesEnabled() const; + [[nodiscard]] bool isUseCloneEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -36,6 +37,7 @@ struct InstanceCopyPrefs { void enableLinkRecursively(bool b); void enableUseHardLinks(bool b); void enableDontLinkSaves(bool b); + void enableUseClone(bool b); protected: // data bool copySaves = true; @@ -50,4 +52,5 @@ struct InstanceCopyPrefs { bool linkRecursively = false; bool useHardLinks = false; bool dontLinkSaves = false; + bool useClone = false; }; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 81502d89..b3ea54b0 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -17,6 +17,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_linkRecursively = prefs.isLinkRecursivelyEnabled(); m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); + m_useClone = prefs.isUseCloneEnabled(); if (!filters.isEmpty()) { @@ -40,7 +41,12 @@ void InstanceCopyTask::executeTask() }; m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ - if (m_useLinks) { + if (m_useClone) { + FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); + folderClone.matcher(m_matcher.get()); + + return folderClone(); + } else if (m_useLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); @@ -83,7 +89,8 @@ void InstanceCopyTask::executeTask() } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); -#endif return false; +#endif + return false; } if (m_copySaves) { diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 3dce1662..aea9d99a 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -34,4 +34,5 @@ private: bool m_useHardLinks = false; bool m_copySaves = false; bool m_linkRecursively = false; + bool m_useClone = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 55962c5a..c51bc067 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -46,6 +46,7 @@ #include "icons/IconList.h" #include "BaseInstance.h" #include "InstanceList.h" +#include "FileSystem.h" CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) @@ -85,11 +86,22 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); - + ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); + + auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType; + m_cloneSupported = FS::canCloneOnFS(detectedOS); + + if (m_cloneSupported) { + ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + } else { + ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + } + + updateUseCloneCheckbox(); } CopyInstanceDialog::~CopyInstanceDialog() @@ -152,6 +164,12 @@ void CopyInstanceDialog::updateSelectAllCheckbox() ui->selectAllCheckbox->blockSignals(false); } +void CopyInstanceDialog::updateUseCloneCheckbox() +{ + ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked()); + ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -230,6 +248,7 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) { m_selectedOptions.enableLinkFiles(checked); + updateUseCloneCheckbox(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) @@ -254,3 +273,10 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); } + +void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked)); + ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled()); + updateUseCloneCheckbox(); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 2fc6f38a..859c643c 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -16,6 +16,7 @@ #pragma once #include +#include "BaseInstance.h" #include "BaseVersion.h" #include "InstanceCopyPrefs.h" @@ -59,13 +60,17 @@ slots: void on_recursiveLinkCheckbox_stateChanged(int state); void on_hardLinksCheckbox_stateChanged(int state); void on_dontLinkSavesCheckbox_stateChanged(int state); + void on_useCloneCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); + void updateUseCloneCheckbox(); + /* data */ Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; + bool m_cloneSupported = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 8df0d3db..ce8657f6 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 525 - 581 + 531 + 640 @@ -275,6 +275,44 @@ + + + + Clone / Reflink (Copy On Write) Options + + + + + + false + + + Use Clone / Reflink + + + + + + + + 1 + 0 + + + + Clone / Reflink not supported on this filesystem + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4 + + + + + + @@ -302,7 +340,6 @@ copyServersCheckbox copyResPacksCheckbox copyModsCheckbox - linkFilesGroup recursiveLinkCheckbox hardLinksCheckbox dontLinkSavesCheckbox -- cgit From bc8336a4b115fd190e068f57159d925683ba3930 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:19:38 -0700 Subject: fix: cleanup UI, detect FAT and turn off links Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/DesktopServices.cpp | 1 - launcher/FileSystem.cpp | 28 ++++++++ launcher/FileSystem.h | 24 ++++++- launcher/InstanceCopyPrefs.cpp | 8 +-- launcher/InstanceCopyPrefs.h | 6 +- launcher/InstanceCopyTask.cpp | 2 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 50 +++++++++---- launcher/ui/dialogs/CopyInstanceDialog.h | 6 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 111 ++++++++++++++++++++--------- 9 files changed, 175 insertions(+), 61 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 69770e99..2984a1b4 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -37,7 +37,6 @@ #include #include #include -//#include "Application.h" /** * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index fd09842f..c363f6ea 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1053,4 +1053,32 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return true; } + +/** + * @brief if the Filesystem is symlink capable + * + */ +bool canLinkOnFS(const QString& path) +{ + FilesystemInfo info = statFS(path); + return canLinkOnFS(info); +} +bool canLinkOnFS(const FilesystemInfo& info) +{ + return canLinkOnFS(info.fsType); +} +bool canLinkOnFS(FilesystemType type) +{ + return !s_non_link_filesystems.contains(type); +} +/** + * @brief if the Filesystem is symlink capable on both ends + * + */ +bool canLink(const QString& src, const QString& dst) +{ + return canLinkOnFS(src) && canLinkOnFS(dst); +} + + } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index aa28de93..83ff99a4 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -330,6 +330,7 @@ enum class FilesystemType { HFSPLUS, HFSX, FUSEBLK, + F2FS, UNKNOWN }; @@ -348,6 +349,7 @@ static const QMap s_filesystem_type_names = { {FilesystemType::HFSPLUS, QString("HFSPLUS")}, {FilesystemType::HFSX, QString("HFSX")}, {FilesystemType::FUSEBLK, QString("FUSEBLK")}, + {FilesystemType::F2FS, QString("F2FS")}, {FilesystemType::UNKNOWN, QString("UNKNOWN")} }; @@ -368,6 +370,7 @@ static const QMap s_filesystem_type_names_inverse = { {QString("HFSX"), FilesystemType::HFSX}, {QString("HFS"), FilesystemType::HFS}, {QString("FUSEBLK"), FilesystemType::FUSEBLK}, + {QString("F2FS"), FilesystemType::F2FS}, {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; @@ -405,7 +408,7 @@ bool canCloneOnFS(const FilesystemInfo& info); bool canCloneOnFS(FilesystemType type); /** - * @brief if the Filesystem is reflink/clone capable and both are on the same device + * @brief if the Filesystems are reflink/clone capable and both are on the same device * */ bool canClone(const QString& src, const QString& dst); @@ -457,4 +460,23 @@ class clone : public QObject { */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); + +static const QList s_non_link_filesystems = { + FilesystemType::FAT, +}; + +/** + * @brief if the Filesystem is symlink capable + * + */ +bool canLinkOnFS(const QString& path); +bool canLinkOnFS(const FilesystemInfo& info); +bool canLinkOnFS(FilesystemType type); + +/** + * @brief if the Filesystem is symlink capable on both ends + * + */ +bool canLink(const QString& src, const QString& dst); + } diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index f2aa5e69..c03d0aae 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -93,9 +93,9 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const return copyScreenshots; } -bool InstanceCopyPrefs::isLinkFilesEnabled() const +bool InstanceCopyPrefs::isUseSymLinksEnabled() const { - return linkFiles; + return useSymLinks; } bool InstanceCopyPrefs::isUseHardLinksEnabled() const @@ -159,9 +159,9 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b) copyScreenshots = b; } -void InstanceCopyPrefs::enableLinkFiles(bool b) +void InstanceCopyPrefs::enableUseSymLinks(bool b) { - linkFiles = b; + useSymLinks = b; } void InstanceCopyPrefs::enableLinkRecursively(bool b) diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 583fdd4c..14ae9551 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -19,7 +19,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const; - [[nodiscard]] bool isLinkFilesEnabled() const; + [[nodiscard]] bool isUseSymLinksEnabled() const; [[nodiscard]] bool isLinkRecursivelyEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; [[nodiscard]] bool isDontLinkSavesEnabled() const; @@ -33,7 +33,7 @@ struct InstanceCopyPrefs { void enableCopyServers(bool b); void enableCopyMods(bool b); void enableCopyScreenshots(bool b); - void enableLinkFiles(bool b); + void enableUseSymLinks(bool b); void enableLinkRecursively(bool b); void enableUseHardLinks(bool b); void enableDontLinkSaves(bool b); @@ -48,7 +48,7 @@ struct InstanceCopyPrefs { bool copyServers = true; bool copyMods = true; bool copyScreenshots = true; - bool linkFiles = false; + bool useSymLinks = false; bool linkRecursively = false; bool useHardLinks = false; bool dontLinkSaves = false; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index b3ea54b0..ba0052fa 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -13,7 +13,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP QString filters = prefs.getSelectedFiltersAsRegex(); - m_useLinks = prefs.isLinkFilesEnabled(); + m_useLinks = prefs.isUseSymLinksEnabled(); m_linkRecursively = prefs.isLinkRecursivelyEnabled(); m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index c51bc067..c6cbefcf 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -87,21 +87,26 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); - ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); - ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); + ui->symbolicLinksCheckbox->setChecked(m_selectedOptions.isUseSymLinksEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); + + ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType; + m_cloneSupported = FS::canCloneOnFS(detectedOS); + m_linkSupported = FS::canLinkOnFS(detectedOS); if (m_cloneSupported) { - ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); } else { - ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); } + updateLinkOptions(); updateUseCloneCheckbox(); + } CopyInstanceDialog::~CopyInstanceDialog() @@ -170,6 +175,21 @@ void CopyInstanceDialog::updateUseCloneCheckbox() ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); } +void CopyInstanceDialog::updateLinkOptions() +{ + ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked()); + ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked()); + + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled()); + ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled()); + + bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); + ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked()); + ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse); + ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled()); + ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled()); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -245,10 +265,20 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) updateSelectAllCheckbox(); } -void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) +void CopyInstanceDialog::on_symbolicLinksCheckbox_stateChanged(int state) { - m_selectedOptions.enableLinkFiles(checked); + m_selectedOptions.enableUseSymLinks(state == Qt::Checked); updateUseCloneCheckbox(); + updateLinkOptions(); +} + +void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseHardLinks(state == Qt::Checked); + if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { + ui->recursiveLinkCheckbox->setChecked(true); + } + updateLinkOptions(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) @@ -261,14 +291,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) } -void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) -{ - m_selectedOptions.enableUseHardLinks(state == Qt::Checked); - if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { - ui->recursiveLinkCheckbox->setChecked(true); - } -} - void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 859c643c..2dea3795 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -56,9 +56,9 @@ slots: void on_copyServersCheckbox_stateChanged(int state); void on_copyModsCheckbox_stateChanged(int state); void on_copyScreenshotsCheckbox_stateChanged(int state); - void on_linkFilesGroup_toggled(bool checked); - void on_recursiveLinkCheckbox_stateChanged(int state); + void on_symbolicLinksCheckbox_stateChanged(int state); void on_hardLinksCheckbox_stateChanged(int state); + void on_recursiveLinkCheckbox_stateChanged(int state); void on_dontLinkSavesCheckbox_stateChanged(int state); void on_useCloneCheckbox_stateChanged(int state); @@ -66,6 +66,7 @@ private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); void updateUseCloneCheckbox(); + void updateLinkOptions(); /* data */ Ui::CopyInstanceDialog *ui; @@ -73,4 +74,5 @@ private: InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; bool m_cloneSupported = false; + bool m_linkSupported = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index ce8657f6..7bf75c2d 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 531 - 640 + 527 + 699 @@ -138,7 +138,7 @@ - Instance copy options + Instance Copy Options @@ -224,53 +224,92 @@ - Use symbolic links instead of copying files. + Use symbolic or hard links instead of copying files. - Link files instead of copying them + Symbolic and Hard Link Options false - true + false false - + - Link files recursively - - - - - - - false + Links are supported on most filesystems except FAT - - Use hard links instead of symbolic links - - - Use hard links + + Qt::AlignCenter - - - If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. + + + 6 - - Don't link saves + + 6 - - false + + 6 - + + 6 + + + + + true + + + Use hard links instead of symbolic links + + + Use hard links + + + + + + + false + + + Link files recursively + + + + + + + false + + + If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. + + + Don't link saves + + + false + + + + + + + Use symbloic links + + + + @@ -278,7 +317,7 @@ - Clone / Reflink (Copy On Write) Options + CoW (Copy-on-Write) Options @@ -287,7 +326,7 @@ false - Use Clone / Reflink + Clone instead of copying @@ -300,7 +339,7 @@ - Clone / Reflink not supported on this filesystem + Your filesystem and/or OS doesn't support reflinks Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -340,9 +379,11 @@ copyServersCheckbox copyResPacksCheckbox copyModsCheckbox + symbolicLinksCheckbox recursiveLinkCheckbox hardLinksCheckbox dontLinkSavesCheckbox + useCloneCheckbox @@ -353,8 +394,8 @@ accept() - 263 - 571 + 269 + 692 157 @@ -369,8 +410,8 @@ reject() - 331 - 571 + 337 + 692 286 -- cgit From 2837236d81b882f041a1cefadc86ca9d5f09ceeb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:48:40 -0700 Subject: fix: intelegent recursive links & symlink follow on export Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 66 ++++++++++++- launcher/FileSystem.h | 28 +++++- launcher/InstanceCopyPrefs.cpp | 9 ++ launcher/InstanceCopyPrefs.h | 1 + launcher/InstanceCopyTask.cpp | 16 +++- launcher/MMCZip.cpp | 11 ++- launcher/ui/dialogs/CopyInstanceDialog.cpp | 21 ++-- tests/FileSystem_test.cpp | 148 +++++++++++++++++++++++++++++ 8 files changed, 278 insertions(+), 22 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c363f6ea..4ee3899b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -242,6 +242,14 @@ bool copy::operator()(const QString& offset, bool dryRun) return err.value() == 0; } +/// qDebug print support for the LinkPair struct +QDebug operator<<(QDebug debug, const LinkPair& lp) +{ + QDebugStateSaver saver(debug); + + debug.nospace() << "LinkPair{ src: " << lp.src << " , dst: " << lp.dst << " }"; + return debug; +} bool create_link::operator()(const QString& offset, bool dryRun) { @@ -265,7 +273,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ -void create_link::make_link_list( const QString& offset) +void create_link::make_link_list(const QString& offset) { for (auto pair : m_path_pairs) { const QString& srcPath = pair.src; @@ -297,14 +305,26 @@ void create_link::make_link_list( const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug() << "linking recursivly:" << src << "to" << dst; + qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); + QStringList linkedPaths; + while (source_it.hasNext()) { auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); + if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth){ + relative_path = PathTruncate(relative_path, m_max_depth); + src_path = src_dir.filePath(relative_path); + if (linkedPaths.contains(src_path)) { + continue; + } + } + + linkedPaths.append(src_path); + link_file(src_path, relative_path); } } @@ -312,7 +332,7 @@ void create_link::make_link_list( const QString& offset) } bool create_link::make_links() -{ +{ for (auto link : m_links_to_make) { QString src_path = link.src; @@ -556,11 +576,49 @@ QString PathCombine(const QString& path1, const QString& path2, const QString& p return PathCombine(PathCombine(path1, path2, path3), path4); } -QString AbsolutePath(QString path) +QString AbsolutePath(const QString& path) { return QFileInfo(path).absolutePath(); } +int PathDepth(const QString& path) +{ + if (path.isEmpty()) return 0; + + QFileInfo info(path); + + auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts); + + int numParts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts).length(); + numParts -= parts.count("."); + numParts -= parts.count("..") * 2; + + return numParts; +} + +QString PathTruncate(const QString& path, int depth) +{ + if (path.isEmpty() || (depth < 0) ) return ""; + + QString trunc = QFileInfo(path).path(); + + if (PathDepth(trunc) > depth ) { + return PathTruncate(trunc, depth); + } + + auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts); + if (parts.startsWith(".") && !path.startsWith(".")) { + parts.removeFirst(); + } + if (path.startsWith(QDir::separator())) { + parts.prepend(""); + } + + trunc = parts.join(QDir::separator()); + + return trunc; +} + QString ResolveExecutable(QString path) { if (path.isEmpty()) { diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 83ff99a4..7485206a 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -200,6 +200,11 @@ class create_link : public QObject { m_recursive = recursive; return *this; } + create_link& setMaxDepth(int depth) + { + m_max_depth = depth; + return *this; + } create_link& debug(bool d) { m_debug = d; @@ -239,6 +244,9 @@ class create_link : public QObject { bool m_whitelist = false; bool m_recursive = true; + /// @brief >= -1 = infinite, 0 = link files at src/* to dest/*, 1 = link files at src/*/* to dest/*/*, etc. + int m_max_depth = -1; + QList m_path_pairs; QList m_path_results; QList m_links_to_make; @@ -272,7 +280,25 @@ QString PathCombine(const QString& path1, const QString& path2); QString PathCombine(const QString& path1, const QString& path2, const QString& path3); QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4); -QString AbsolutePath(QString path); +QString AbsolutePath(const QString& path); + +/** + * @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc. + * + * @param path path to measure + * @return int number of componants before base path + */ +int PathDepth(const QString& path); + + +/** + * @brief cut off segments of path untill it is a max of length depth + * + * @param path path to truncate + * @param depth max depth of new path + * @return QString truncated path + */ +QString PathTruncate(const QString& path, int depth); /** * Resolve an executable diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index c03d0aae..0650002b 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -16,8 +16,13 @@ bool InstanceCopyPrefs::allTrue() const copyScreenshots; } + // Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat") QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const +{ + return getSelectedFiltersAsRegex({}); +} +QString InstanceCopyPrefs::getSelectedFiltersAsRegex(const QStringList& additionalFilters) const { QStringList filters; @@ -42,6 +47,10 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const if(!copyScreenshots) filters << "screenshots"; + for (auto filter : additionalFilters) { + filters << filter; + } + // If we have any filters to add, join them as a single regex string to return: if (!filters.isEmpty()) { const QString MC_ROOT = "[.]?minecraft/"; diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 14ae9551..c7bde068 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -10,6 +10,7 @@ struct InstanceCopyPrefs { public: [[nodiscard]] bool allTrue() const; [[nodiscard]] QString getSelectedFiltersAsRegex() const; + [[nodiscard]] QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) const; // Getters [[nodiscard]] bool isCopySavesEnabled() const; [[nodiscard]] bool isKeepPlaytimeEnabled() const; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index ba0052fa..40babd0f 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -4,13 +4,14 @@ #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" #include +#include InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); - QString filters = prefs.getSelectedFiltersAsRegex(); + m_useLinks = prefs.isUseSymLinksEnabled(); @@ -18,6 +19,14 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); m_useClone = prefs.isUseCloneEnabled(); + + QString filters = prefs.getSelectedFiltersAsRegex(); + if (m_useLinks || m_useHardLinks) { + if (!filters.isEmpty()) filters += "|"; + filters += "instance.cfg"; + } + + qDebug() << "CopyFilters:" << filters; if (!filters.isEmpty()) { @@ -46,9 +55,10 @@ void InstanceCopyTask::executeTask() folderClone.matcher(m_matcher.get()); return folderClone(); - } else if (m_useLinks) { + } else if (m_useLinks || m_useHardLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); - folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder + folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); bool there_were_errors = false; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b4b663c1..1a336375 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -102,8 +102,13 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); auto srcPath = e.absoluteFilePath(); - if (followSymlinks) - srcPath = e.canonicalFilePath(); + if (followSymlinks) { + if (e.isSymLink()) { + srcPath = e.symLinkTarget(); + } else { + srcPath = e.canonicalFilePath(); + } + } if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; } @@ -119,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList return false; } - auto result = compressDirFiles(&zip, dir, files); + auto result = compressDirFiles(&zip, dir, files, followSymlinks); zip.close(); if(zip.getZipError()!=0) { diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index c6cbefcf..9fe129f1 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -171,17 +171,18 @@ void CopyInstanceDialog::updateSelectAllCheckbox() void CopyInstanceDialog::updateUseCloneCheckbox() { - ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked()); - ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); + ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->hardLinksCheckbox->isChecked()); + ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled() && !ui->symbolicLinksCheckbox->isChecked() && + !ui->hardLinksCheckbox->isChecked()); } void CopyInstanceDialog::updateLinkOptions() { - ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked()); - ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked()); + ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); + ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); - ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled()); - ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled()); + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked()); + ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked()); bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked()); @@ -278,16 +279,14 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { ui->recursiveLinkCheckbox->setChecked(true); } + updateUseCloneCheckbox(); updateLinkOptions(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) { m_selectedOptions.enableLinkRecursively(state == Qt::Checked); - if (state != Qt::Checked) { - ui->hardLinksCheckbox->setChecked(false); - ui->dontLinkSavesCheckbox->setChecked(false); - } + updateLinkOptions(); } @@ -299,6 +298,6 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state) { m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked)); - ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled()); updateUseCloneCheckbox(); + updateLinkOptions(); } \ No newline at end of file diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 4ccc4003..4418dd62 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -57,6 +57,11 @@ class LinkTask : public Task { m_lnk->whitelist(b); } + void setMaxDepth(int depth) + { + m_lnk->setMaxDepth(depth); + } + private: void executeTask() override { @@ -630,6 +635,149 @@ slots: QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); } } + + void test_link_with_max_depth() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder, this]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + lnk_tsk.setMaxDepth(0); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); + + QVERIFY(!QFileInfo(target_dir.path()).isSymLink()); + + auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; + for(auto entry: target_dir.entryList(filter)) + { + qDebug() << entry; + if (entry == "." || entry == "..") continue; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(entry_lnk_info.isSymLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(!lnk_info.isSymLink()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + + + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_with_no_max_depth() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + lnk_tsk.setMaxDepth(-1); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); + + + std::function verify_check = [&](QString check_path) { + QDir check_dir(check_path); + auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; + for(auto entry: check_dir.entryList(filter)) + { + QFileInfo entry_lnk_info(check_dir.filePath(entry)); + qDebug() << entry << check_dir.filePath(entry); + if (!entry_lnk_info.isDir()){ + QVERIFY(entry_lnk_info.isSymLink()); + } else if (entry != "." && entry != "..") { + qDebug() << "Decending tree to verify symlinks:" << check_dir.filePath(entry); + verify_check(entry_lnk_info.filePath()); + } + } + }; + + verify_check(target_dir.path()); + + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_path_depth() { + QCOMPARE_EQ(FS::PathDepth(""), 0); + QCOMPARE_EQ(FS::PathDepth("."), 0); + QCOMPARE_EQ(FS::PathDepth("foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("./foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("./bar/foo.txt"), 1); + QCOMPARE_EQ(FS::PathDepth("../bar/foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("/bar/foo.txt"), 1); + QCOMPARE_EQ(FS::PathDepth("baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("/baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("./baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("/baz/../bar/foo.txt"), 1); + } + + void test_path_trunc() { + QCOMPARE_EQ(FS::PathTruncate("", 0), ""); + QCOMPARE_EQ(FS::PathTruncate("foo.txt", 0), ""); + QCOMPARE_EQ(FS::PathTruncate("foo.txt", 1), ""); + QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 0), "./bar"); + QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 1), "./bar"); + QCOMPARE_EQ(FS::PathTruncate("/bar/foo.txt", 1), "/bar"); + QCOMPARE_EQ(FS::PathTruncate("bar/foo.txt", 1), "bar"); + QCOMPARE_EQ(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar"); + } }; QTEST_GUILESS_MAIN(FileSystemTest) -- cgit From 2e8d04aad0d4fe3e742e4b29f4e23dc91b8ef838 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 05:34:48 -0800 Subject: feat: support reflink on windows via winbtrfs! https://github.com/maharmstone/btrfs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 198 +++++++++++++++++++++++++++++++++++++++++----- launcher/FileSystem.h | 21 ++++- launcher/InstanceList.cpp | 2 +- 3 files changed, 199 insertions(+), 22 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c3a0c273..4037d346 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -108,6 +108,13 @@ namespace fs = ghc::filesystem; #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #include #include +#elif defined(Q_OS_WIN) +// winbtrfs clone vs rundll32 shellbtrfs.dll,ReflinkCopy +#include +#include +#include +// refs +#include #endif namespace FS { @@ -916,25 +923,44 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } +/** + * @brief path to the near ancestor that exsists + * + */ +QString NearestExistentAncestor(const QString& path) +{ + if(QFileInfo::exists(path)) return path; + + QDir dir(path); + if(!dir.makeAbsolute()) return {}; + do + { + dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral("..")))); + } + while(!dir.exists() && !dir.isRoot()); + + return dir.exists() ? dir.path() : QString(); +} /** * @brief colect information about the filesystem under a file * */ -FilesystemInfo statFS(QString path) +FilesystemInfo statFS(const QString& path) { FilesystemInfo info; - QStorageInfo storage_info(path); + QStorageInfo storage_info(NearestExistentAncestor(path)); QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName; for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { auto fs_type_name = fs_type_pair.first; auto fs_type = fs_type_pair.second; - if(fsTypeName.contains(fs_type_name.toLower())) { + if(fsTypeName.toLower().contains(fs_type_name.toLower())) { info.fsType = fs_type; break; } @@ -948,9 +974,6 @@ FilesystemInfo statFS(QString path) info.name = storage_info.name(); info.rootPath = storage_info.rootPath(); - qDebug() << "Pulling filesystem info for" << info.rootPath; - qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType); - return info; } @@ -1054,15 +1077,156 @@ bool clone::operator()(const QString& offset, bool dryRun) */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) { - auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath()); - auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath()); + auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath())); + auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath())); #if defined(Q_OS_WIN) - qWarning("clone/reflink not supported on windows!"); + FilesystemInfo srcinfo = statFS(src); + if (srcinfo.fsType == FilesystemType::BTRFS) { + FilesystemInfo dstinfo = statFS(dst); + if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ + qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; + qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe"; + + if (!winbtrfs_clone(src_path, dst_path, ec)) + return false; + + // There is no return value from rundll32.exe so we must check if the file exsists ourselves + + QFileInfo dstInfo(dst); + if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { + // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556 + // lets try a little workaround + // find the misnamed file + qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround."; + QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName(); + qDebug() << "trying" << badDst; + QFileInfo badDstInfo(badDst); + if (badDstInfo.exists() && badDstInfo.isFile()) { + qDebug() << badDst << "exists! moving it to the correct location."; + if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) { + qDebug() << "move from" << badDst << "to" << dst << "failed"; + ec = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + } else { + // oof, clone failure? + qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist"; + ec = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + } + + } else if (srcinfo.fsType == FilesystemType::REFS) { + qWarning() << "clone/reflink not yet supported on windows ReFS!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } else { + qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; + qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } +#elif defined(Q_OS_LINUX) + + if(!linux_ficlone(src_path, dst_path, ec)) + return false; + +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + + if(!macos_bsd_clonefile(src_path, dst_path, ec)) + return false; + +#else + qWarning() << "clone/reflink not supported! unknown OS"; ec = std::make_error_code(std::errc::not_supported); return false; -#elif defined(Q_OS_LINUX) +#endif + return true; +} + +#if defined(Q_OS_WIN) +typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); + +bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + // https://github.com/maharmstone/btrfs + QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path); + + std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast + + LPWSTR cmdLineWin = (wchar_t*)wstr.c_str(); + + // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609 + HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll"); + + if (shellbtrfsDLL == NULL) { + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported"; + return false; + } + + f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW"); + + if (!ReflinkCopyW) { + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported"; + return false; + } + + qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine; + + ReflinkCopyW(0, 0, cmdLineWin, 1); + + FreeLibrary(shellbtrfsDLL); + + return true; +} + +bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file + //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 + std::wstring existingFile = src_path.c_str(); + std::wstring newLink = dst_path.c_str(); + + + HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hExistingFile == INVALID_HANDLE_VALUE) + { + return false; + } + + HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + if (hNewFile == INVALID_HANDLE_VALUE) + { + CloseHandle(hExistingFile); + return false; + } + + DWORD bytesReturned; + + // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary. + // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks + BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL); + + CloseHandle(hNewFile); + CloseHandle(hExistingFile); + + return (result != 0); + +} + + +#elif defined(Q_OS_LINUX) +bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec) +{ // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html int src_fd = open(src_path.c_str(), O_RDONLY); @@ -1097,9 +1261,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) qWarning() << "Failed to close file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); } + return true; +} #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - // TODO: use clonefile +bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) +{ // clonefile(const char * src, const char * dst, int flags); // https://www.manpagez.com/man/2/clonefile/ @@ -1110,16 +1277,9 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) ec = std::make_error_code(static_cast(errno)); return false; } - -#else - qWarning("clone/reflink not supported! unknown OS"); - ec = std::make_error_code(std::errc::not_supported); - return false; -#endif - return true; } - +#endif /** * @brief if the Filesystem is symlink capable diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 7485206a..3f6b78e5 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -344,6 +344,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri enum class FilesystemType { FAT, NTFS, + REFS, EXT, EXT_2_OLD, EXT_2_3_4, @@ -363,6 +364,7 @@ enum class FilesystemType { static const QMap s_filesystem_type_names = { {FilesystemType::FAT, QString("FAT")}, {FilesystemType::NTFS, QString("NTFS")}, + {FilesystemType::REFS, QString("REFS")}, {FilesystemType::EXT, QString("EXT")}, {FilesystemType::EXT_2_OLD, QString("EXT2_OLD")}, {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, @@ -382,6 +384,7 @@ static const QMap s_filesystem_type_names = { static const QMap s_filesystem_type_names_inverse = { {QString("FAT"), FilesystemType::FAT}, {QString("NTFS"), FilesystemType::NTFS}, + {QString("REFS"), FilesystemType::REFS}, {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD}, {QString("EXT2"), FilesystemType::EXT_2_3_4}, {QString("EXT3"), FilesystemType::EXT_2_3_4}, @@ -414,15 +417,21 @@ struct FilesystemInfo { QString rootPath; }; +/** + * @brief path to the near ancestor that exsists + * + */ +QString NearestExistentAncestor(const QString& path); + /** * @brief colect information about the filesystem under a file * */ -FilesystemInfo statFS(QString path); +FilesystemInfo statFS(const QString& path); static const QList s_clone_filesystems = { - FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS + FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS, FilesystemType::REFS }; /** @@ -486,6 +495,14 @@ class clone : public QObject { */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); +#if defined(Q_OS_WIN) +bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +#elif defined(Q_OS_LINUX) +bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec); +#endif static const QList s_non_link_filesystems = { FilesystemType::FAT, diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 68e3e92c..1ca16ae9 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -865,7 +865,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task) QString InstanceList::getStagedInstancePath() { - QString key = QUuid::createUuid().toString(); + QString key = QUuid::createUuid().toString().remove("{").remove("}"); QString tempDir = ".LAUNCHER_TEMP/"; QString relPath = FS::PathCombine(tempDir, key); QDir rootPath(m_instDir); -- cgit From 9939367db7568249efd47701105323a4801a9413 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:41:48 -0800 Subject: feat(reflink): ioctl_clone for winbtrfs & ReFS Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 356 +++++++++++++++++++++++++++++++++++++++++++----- launcher/FileSystem.h | 10 ++ 2 files changed, 334 insertions(+), 32 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 69e2d36a..948ec55e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -59,6 +59,7 @@ #include "StringUtils.h" #if defined Q_OS_WIN32 +#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include @@ -113,6 +114,7 @@ namespace fs = ghc::filesystem; #include #include #include +#include // refs #include #endif @@ -953,14 +955,13 @@ FilesystemInfo statFS(const QString& path) QStorageInfo storage_info(NearestExistentAncestor(path)); - QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); - qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName; + info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { auto fs_type_name = fs_type_pair.first; auto fs_type = fs_type_pair.second; - if(fsTypeName.toLower().contains(fs_type_name.toLower())) { + if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) { info.fsType = fs_type; break; } @@ -1043,7 +1044,7 @@ bool clone::operator()(const QString& offset, bool dryRun) clone_file(src_path, dst_path, err); } if (err) { - qWarning() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Failed to clone files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; } @@ -1082,8 +1083,23 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) #if defined(Q_OS_WIN) FilesystemInfo srcinfo = statFS(src); - if (srcinfo.fsType == FilesystemType::BTRFS) { - FilesystemInfo dstinfo = statFS(dst); + FilesystemInfo dstinfo = statFS(dst); + + if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) || + (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) && + USE_IOCTL_CLONE) + { + if (srcinfo.rootPath != dstinfo.rootPath) { + qWarning() << "clones must be to the same device! src and dst root paths do not match."; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qDebug() << "ioctl clone" << src << "to" << dst; + if (!ioctl_clone(src_path, dst_path, ec)) + return false; + + } else if (srcinfo.fsType == FilesystemType::BTRFS) { if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; @@ -1091,12 +1107,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return false; } - qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe"; + qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW"; if (!winbtrfs_clone(src_path, dst_path, ec)) return false; - // There is no return value from rundll32.exe so we must check if the file exsists ourselves + // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves QFileInfo dstInfo(dst); if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { @@ -1116,16 +1132,24 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) } } else { // oof, clone failure? - qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist"; + qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist"; ec = std::make_error_code(std::errc::no_such_file_or_directory); return false; } } } else if (srcinfo.fsType == FilesystemType::REFS) { - qWarning() << "clone/reflink not yet supported on windows ReFS!"; - ec = std::make_error_code(std::errc::not_supported); - return false; + if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){ + qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match."; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qDebug() << "clone/reflink of ReFS on windows!"; + + if (!refs_clone(src_path, dst_path, ec)) + return false; + } else { qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; @@ -1154,6 +1178,14 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) #if defined(Q_OS_WIN) typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); +const long WinMaxChunkSize = 1L << 31; // 2GB + +static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) +{ + long mask = roundingMultiplePowerOf2 - 1; + return (originalValue + mask) & ~mask; +} + bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { // https://github.com/maharmstone/btrfs @@ -1194,33 +1226,118 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: #if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE) //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 - std::wstring existingFile = src_path.c_str(); - std::wstring newLink = dst_path.c_str(); + QString qSourcePath = StringUtils::fromStdString(src_path); + QString sourceVolumePath = statFS(qSourcePath).rootPath; + std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath); + + unsigned long sectorsPerCluster; + unsigned long bytesPerSector; + unsigned long numberOfFreeClusters; + unsigned long totalNumberOfClusters; + + if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){ + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get disk info for source volume" << sourceVolumePath; + return false; + } + long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector); - HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (hExistingFile == INVALID_HANDLE_VALUE) + HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hSourceFile == INVALID_HANDLE_VALUE) { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open source file" << src_path.c_str(); return false; } - HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); - if (hNewFile == INVALID_HANDLE_VALUE) + HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + if (hDestFile == INVALID_HANDLE_VALUE) { - CloseHandle(hExistingFile); + ec = std::error_code(GetLastError(), std::system_category()); + CloseHandle(hSourceFile); + qDebug() << "Failed to open dest file" << dst_path.c_str(); return false; } - DWORD bytesReturned; + DWORD bytesReturned = 0; - // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary. - // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks - BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL); + // Set the destination to be sparse while we clone. + // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle() + // below which will just be released when cloning file extents. + + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) { + qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str(); + ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + + LARGE_INTEGER sourceFileLengthStruct; + if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get file info for source file" << src_path.c_str(); + return false; + } + + long sourceFileLength = sourceFileLengthStruct.QuadPart; + + // Set the destination on-disk size the same as the source. + FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; + if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo, + &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set end of file on destination file" << dst_path.c_str(); + return false; + } + + DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile }; + + long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize); + long sourceOffset = 0; + while(sourceOffset < sourceFileLength) + { + duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset; + duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset; + long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize); + duplicateExtentsData.ByteCount.QuadPart = thisChunkSize; + + DWORD numBytesReturned = 0; + bool ioctlResult = DeviceIoControl( + hDestFile, + FSCTL_DUPLICATE_EXTENTS_TO_FILE, + &duplicateExtentsData, + sizeof(DUPLICATE_EXTENTS_DATA), + nullptr, + 0, + &numBytesReturned, + nullptr); + if (!ioctlResult) + { + DWORD err = GetLastError(); + ec = std::error_code(err, std::system_category()); + QString additionalMessage; + if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) + { + static const int MaxClonesPerFile = 8175; + additionalMessage = QString( + " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " + "allowed %1 references for a single file. " + "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" + ).arg(MaxClonesPerFile); + + } + qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage; + return false; + } - CloseHandle(hNewFile); - CloseHandle(hExistingFile); + sourceOffset += thisChunkSize; + } + + CloseHandle(hDestFile); + CloseHandle(hSourceFile); - return (result != 0); + return true; #else ec = std::make_error_code(std::errc::not_supported); qWarning() << "not built with refs support"; @@ -1228,6 +1345,181 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: #endif } +bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + /** + * This algorithm inspired from https://github.com/0xbadfca11/reflink + * LICENSE MIT + * + */ + + HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hSourceFile == INVALID_HANDLE_VALUE) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open source file" << src_path.c_str(); + return false; + } + + ULONG fs_flags; + if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get Filesystem information for " << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) + { + SetLastError(ERROR_NOT_CAPABLE); + ec = std::error_code(GetLastError(), std::system_category()); + qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink"; + CloseHandle(hSourceFile); + return false; + } + + FILE_END_OF_FILE_INFO sourceFileLength; + if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to size of source file" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + FILE_BASIC_INFO sourceFileBasicInfo; + if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to source file info" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + ULONG junk; + FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity; + if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to source file integrity info" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + + HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile); + + if (hDestFile == INVALID_HANDLE_VALUE) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open dest file" << dst_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + FILE_DISPOSITION_INFO destFileDispose = { TRUE }; + if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file info" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest sparseness" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, sourceFileIntegrity.Flags }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, nullptr, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file integrity info" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file size" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + + const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes; + + DUPLICATE_EXTENTS_DATA dupExtent; + dupExtent.FileHandle = hSourceFile; + for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); remain > 0; offset += splitThreshold, remain -= splitThreshold) + { + dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; + dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); + + _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); + _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); + _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX); + _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart); + + if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) + { + DWORD err = GetLastError(); + QString additionalMessage; + if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) + { + static const int MaxClonesPerFile = 8175; + additionalMessage = QString( + " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " + "allowed %1 references for a single file. " + "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" + ).arg(MaxClonesPerFile); + + } + ec = std::error_code(err, std::system_category()); + qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err << additionalMessage; + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + } + + if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)) + { + FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr)) + { + qDebug() << "Failed to set dest file sparseness" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + } + + sourceFileBasicInfo.CreationTime.QuadPart = 0; + if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) + { + qDebug() << "Failed to set dest file creation time" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + if (!FlushFileBuffers(hDestFile)) + { + qDebug() << "Failed to flush dest file buffer" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + destFileDispose = { FALSE }; + bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)); + + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + + return result; +} #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec) @@ -1236,14 +1528,14 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std int src_fd = open(src_path.c_str(), O_RDONLY); if(src_fd == -1) { - qWarning() << "Failed to open file:" << src_path.c_str(); + qDebug() << "Failed to open file:" << src_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); return false; } int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if(dst_fd == -1) { - qWarning() << "Failed to open file:" << dst_path.c_str(); + qDebug() << "Failed to open file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); close(src_fd); @@ -1251,7 +1543,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std } // attempt to clone if(ioctl(dst_fd, FICLONE, src_fd) == -1){ - qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); + qDebug() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); close(src_fd); @@ -1259,11 +1551,11 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std return false; } if(close(src_fd)) { - qWarning() << "Failed to close file:" << src_path.c_str(); + qDebug() << "Failed to close file:" << src_path.c_str(); qDebug() << "Error:" << strerror(errno); } if(close(dst_fd)) { - qWarning() << "Failed to close file:" << dst_path.c_str(); + qDebug() << "Failed to close file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); } return true; @@ -1277,7 +1569,7 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat qDebug() << "attempting file clone via clonefile" << src_path.c_str() << "to" << dst_path.c_str(); if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) { - qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); + qDebug() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); return false; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 3f6b78e5..4c011309 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -409,6 +409,7 @@ inline QString getFilesystemTypeName(FilesystemType type) { struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; + QString fsTypeName; int blockSize; qint64 bytesAvailable; qint64 bytesFree; @@ -496,8 +497,17 @@ class clone : public QObject { bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) + +#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#endif + bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); + +#define USE_IOCTL_CLONE true + #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) -- cgit From 7870cf28e55c090543591304b05a7ef5031e1157 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:06:49 -0800 Subject: fix: add missing mingw defs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 39 ++++++++++++++++++++++++++++++++++++++- launcher/FileSystem.h | 4 ---- 2 files changed, 38 insertions(+), 5 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 948ec55e..a9461bb0 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -117,8 +117,45 @@ namespace fs = ghc::filesystem; #include // refs #include +# if defined(__MINGW32__) +# include +# endif #endif +#if defined(Q_OS_WIN) && defined(__MINGW32__) + +typedef struct _DUPLICATE_EXTENTS_DATA { + HANDLE FileHandle; + LARGE_INTEGER SourceFileOffset; + LARGE_INTEGER TargetFileOffset; + LARGE_INTEGER ByteCount; +} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; + +typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx + DWORD ChecksumChunkSizeInBytes; + DWORD ClusterSizeInBytes; +} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; + +typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx +} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; + +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER +#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER + + +#define ERROR_NOT_CAPABLE 775L +#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L + +#endif + + namespace FS { void ensureExists(const QDir& dir) @@ -1279,7 +1316,7 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: return false; } - long sourceFileLength = sourceFileLengthStruct.QuadPart; + DWORD sourceFileLength = sourceFileLengthStruct.QuadPart; // Set the destination on-disk size the same as the source. FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 4c011309..cafbd2a8 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -498,10 +498,6 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) -#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE -#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) -#endif - bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); -- cgit From a1053a4c5ac8651be3b57814918e28179eb2a1f9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 02:44:39 -0700 Subject: feat: warnings when instance resources are linked Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 4 +++ launcher/FileSystem.h | 2 ++ launcher/minecraft/World.cpp | 24 +++++++++++++++++ launcher/minecraft/World.h | 15 +++++++++++ launcher/minecraft/WorldList.cpp | 30 ++++++++++++++++++++-- launcher/minecraft/WorldList.h | 5 +++- launcher/minecraft/mod/ModFolderModel.cpp | 20 +++++++++++++++ launcher/minecraft/mod/Resource.cpp | 20 +++++++++++++++ launcher/minecraft/mod/Resource.h | 13 ++++++++++ launcher/minecraft/mod/ResourceFolderModel.cpp | 26 +++++++++++++++++++ launcher/minecraft/mod/ResourceFolderModel.h | 2 ++ launcher/minecraft/mod/ResourcePackFolderModel.cpp | 20 +++++++++++++++ launcher/ui/pages/instance/WorldListPage.cpp | 1 + 13 files changed, 179 insertions(+), 3 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index a9461bb0..af2ba299 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1641,5 +1641,9 @@ bool canLink(const QString& src, const QString& dst) return canLinkOnFS(src) && canLinkOnFS(dst); } +uintmax_t hardLinkCount(const QString& path) +{ + return fs::hard_link_count(StringUtils::toStdString(path)); +} } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index cafbd2a8..361993eb 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -528,4 +528,6 @@ bool canLinkOnFS(FilesystemType type); */ bool canLink(const QString& src, const QString& dst); +uintmax_t hardLinkCount(const QString& path); + } diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index d310f8b9..54fb9434 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -56,6 +56,8 @@ #include +#include "FileSystem.h" + using std::optional; using std::nullopt; @@ -567,3 +569,25 @@ bool World::operator==(const World &other) const { return is_valid == other.is_valid && folderName() == other.folderName(); } + +bool World::isSymLinkUnder(const QString& instPath) const +{ + if (isSymLink()) + return true; + + auto instDir = QDir(instPath); + + auto relAbsPath = instDir.relativeFilePath(m_containerFile.absoluteFilePath()); + auto relCanonPath = instDir.relativeFilePath(m_containerFile.canonicalFilePath()); + + return relAbsPath != relCanonPath; +} + +bool World::isMoreThanOneHardLink() const +{ + if (m_containerFile.isDir()) + { + return FS::hardLinkCount(QDir(m_containerFile.absoluteFilePath()).filePath("level.dat")) > 1; + } + return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1; +} diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 8327253a..10328cce 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -95,6 +95,21 @@ public: // WEAK compare operator - used for replacing worlds bool operator==(const World &other) const; + [[nodiscard]] auto isSymLink() const -> bool{ return m_containerFile.isSymLink(); } + + /** + * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance + * + * @param instPath path to an instance directory + * @return true + * @return false + */ + [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + + [[nodiscard]] bool isMoreThanOneHardLink() const; + + QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); } + private: void readFromZip(const QFileInfo &file); void readFromFS(const QFileInfo &file); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ae29a972..1262fa1d 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -128,6 +128,10 @@ bool WorldList::isValid() return m_dir.exists() && m_dir.isReadable(); } +QString WorldList::instDirPath() const { + return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); +} + bool WorldList::deleteWorld(int index) { if (index >= worlds.size() || index < 0) @@ -173,7 +177,7 @@ bool WorldList::resetIcon(int row) int WorldList::columnCount(const QModelIndex &parent) const { - return parent.isValid()? 0 : 4; + return parent.isValid()? 0 : 5; } QVariant WorldList::data(const QModelIndex &index, int role) const @@ -207,6 +211,14 @@ QVariant WorldList::data(const QModelIndex &index, int role) const case SizeColumn: return locale.formattedDataSize(world.bytes()); + case InfoColumn: + if (world.isSymLinkUnder(instDirPath())) { + return tr("This world is symbolicly linked from elsewhere."); + } + if (world.isMoreThanOneHardLink()) { + return tr("\nThis world is hard linked elsewhere."); + } + return ""; default: return QVariant(); } @@ -222,7 +234,16 @@ QVariant WorldList::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: - { + { + if (column == InfoColumn) { + if (world.isSymLinkUnder(instDirPath())) { + return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nCanonical Path: %1").arg(world.canonicalFilePath()); + } + if (world.isMoreThanOneHardLink()) { + return tr("Warning: This world is hard linked elsewhere. Editing it will also change the origonal"); + } + } return world.folderName(); } case ObjectRole: @@ -274,6 +295,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol case SizeColumn: //: World size on disk return tr("Size"); + case InfoColumn: + //: special warnings? + return tr("Info"); default: return QVariant(); } @@ -289,6 +313,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol return tr("Date and time the world was last played."); case SizeColumn: return tr("Size of the world on disk."); + case InfoColumn: + return tr("Information and warnings about the world."); default: return QVariant(); } diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 08294755..bd32dd4e 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -33,7 +33,8 @@ public: NameColumn, GameModeColumn, LastPlayedColumn, - SizeColumn + SizeColumn, + InfoColumn }; enum Roles @@ -112,6 +113,8 @@ public: return m_dir; } + QString instDirPath() const; + const QList &allWorlds() const { return worlds; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 3f31b93c..e2053f92 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -39,13 +39,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include "Application.h" + #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" @@ -97,8 +101,24 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: + if (column == NAME_COLUMN) { + if (at(row)->isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath()); + } + if (at(row)->isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + } + } return m_resources[row]->internal_id(); + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + return {}; + } case Qt::CheckStateRole: switch (column) { diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 0d35d755..a0b8a4bb 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -1,6 +1,8 @@ #include "Resource.h" + #include +#include #include "FileSystem.h" @@ -152,3 +154,21 @@ bool Resource::destroy() return FS::deletePath(m_file_info.filePath()); } + +bool Resource::isSymLinkUnder(const QString& instPath) const +{ + if (isSymLink()) + return true; + + auto instDir = QDir(instPath); + + auto relAbsPath = instDir.relativeFilePath(m_file_info.absoluteFilePath()); + auto relCanonPath = instDir.relativeFilePath(m_file_info.canonicalFilePath()); + + return relAbsPath != relCanonPath; +} + +bool Resource::isMoreThanOneHardLink() const +{ + return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; +} diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 0c37f3a3..a5e9ae91 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -94,6 +94,19 @@ class Resource : public QObject { // Delete all files of this resource. bool destroy(); + [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } + + /** + * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance + * + * @param instPath path to an instance directory + * @return true + * @return false + */ + [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + + [[nodiscard]] bool isMoreThanOneHardLink() const; + protected: /* The file corresponding to this resource. */ QFileInfo m_file_info; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index f2a77c12..d1748c1c 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -2,10 +2,14 @@ #include #include +#include +#include #include +#include #include #include +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -417,7 +421,25 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::ToolTipRole: + if (column == NAME_COLUMN) { + if (at(row).isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());; + } + if (at(row).isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + } + } + return m_resources[row]->internal_id(); + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + + return {}; + } case Qt::CheckStateRole: switch (column) { case ACTIVE_COLUMN: @@ -531,3 +553,7 @@ void ResourceFolderModel::enableInteraction(bool enabled) return (compare_result.first < 0); return (compare_result.first > 0); } + +QString ResourceFolderModel::instDirPath() const { + return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); +} diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 3bd78870..f840b2de 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -125,6 +125,8 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; }; + QString instDirPath() const; + public slots: void enableInteraction(bool enabled); void disableInteraction(bool disabled) { enableInteraction(!disabled); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index da4bd091..56584a34 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -36,6 +36,10 @@ #include "ResourcePackFolderModel.h" +#include +#include + +#include "Application.h" #include "Version.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -78,12 +82,28 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const default: return {}; } + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + return {}; + } case Qt::ToolTipRole: { if (column == PackFormatColumn) { //: The string being explained by this is in the format: ID (Lower version - Upper version) return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); } + if (column == NAME_COLUMN) { + if (at(row)->isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());; + } + if (at(row)->isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + } + } return m_resources[row]->internal_id(); } case Qt::CheckStateRole: diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index d4a395d9..b6ad159e 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -107,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worl auto head = ui->worldTreeView->header(); head->setSectionResizeMode(0, QHeaderView::Stretch); head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + head->setSectionResizeMode(4, QHeaderView::ResizeToContents); connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged); worldChanged(QModelIndex(), QModelIndex()); -- cgit From 536da704fc1ad66ac754b9fae119df7eb6fa1268 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:48:58 -0700 Subject: refactor: cleanupFilesystem.cpp * remove now redundant reflink/clone code for windows * remove unnessacery debug code that could slow things down Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 311 ++++++++++-------------------------------------- launcher/FileSystem.h | 67 ++++++++--- 2 files changed, 115 insertions(+), 263 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index af2ba299..d6e6b92f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -962,6 +962,37 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } +QString getFilesystemTypeName(FilesystemType type) { + auto iter = s_filesystem_type_names.constFind(type); + if (iter != s_filesystem_type_names.constEnd()){ + return iter.value(); + } + return getFilesystemTypeName(FilesystemType::UNKNOWN); +} + +FilesystemType getFilesystemTypeFuzzy(const QString& name) +{ + auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper()); + if (iter != s_filesystem_type_names_inverse.constEnd()){ + return iter.value(); + } + return FilesystemType::UNKNOWN; +} + +FilesystemType getFilesystemType(const QString& name) +{ + for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { + auto fs_type_name = fs_type_pair.first; + auto fs_type = fs_type_pair.second; + + if(name.toUpper().contains(fs_type_name.toUpper())) { + return fs_type; + + } + } + return FilesystemType::UNKNOWN; +} + /** * @brief path to the near ancestor that exsists * @@ -994,15 +1025,7 @@ FilesystemInfo statFS(const QString& path) info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); - for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { - auto fs_type_name = fs_type_pair.first; - auto fs_type = fs_type_pair.second; - - if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) { - info.fsType = fs_type; - break; - } - } + info.fsType = getFilesystemTypeFuzzy(info.fsTypeName); info.blockSize = storage_info.blockSize(); info.bytesAvailable = storage_info.bytesAvailable(); @@ -1029,7 +1052,7 @@ bool canCloneOnFS(const FilesystemInfo& info) return canCloneOnFS(info.fsType); } bool canCloneOnFS(FilesystemType type) -{ +{ return s_clone_filesystems.contains(type); } @@ -1081,7 +1104,7 @@ bool clone::operator()(const QString& offset, bool dryRun) clone_file(src_path, dst_path, err); } if (err) { - qDebug() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; } @@ -1118,104 +1141,55 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath())); auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath())); -#if defined(Q_OS_WIN) FilesystemInfo srcinfo = statFS(src); FilesystemInfo dstinfo = statFS(dst); - if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) || - (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) && - USE_IOCTL_CLONE) - { - if (srcinfo.rootPath != dstinfo.rootPath) { - qWarning() << "clones must be to the same device! src and dst root paths do not match."; - ec = std::make_error_code(std::errc::not_supported); - return false; - } - - qDebug() << "ioctl clone" << src << "to" << dst; - if (!ioctl_clone(src_path, dst_path, ec)) - return false; - - } else if (srcinfo.fsType == FilesystemType::BTRFS) { - if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ - qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; - qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; - ec = std::make_error_code(std::errc::not_supported); - return false; - } - - qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW"; - - if (!winbtrfs_clone(src_path, dst_path, ec)) - return false; - - // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves - - QFileInfo dstInfo(dst); - if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { - // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556 - // lets try a little workaround - // find the misnamed file - qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround."; - QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName(); - qDebug() << "trying" << badDst; - QFileInfo badDstInfo(badDst); - if (badDstInfo.exists() && badDstInfo.isFile()) { - qDebug() << badDst << "exists! moving it to the correct location."; - if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) { - qDebug() << "move from" << badDst << "to" << dst << "failed"; - ec = std::make_error_code(std::errc::no_such_file_or_directory); - return false; - } - } else { - // oof, clone failure? - qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist"; - ec = std::make_error_code(std::errc::no_such_file_or_directory); - return false; - } - } - - } else if (srcinfo.fsType == FilesystemType::REFS) { - if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){ - qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match."; - ec = std::make_error_code(std::errc::not_supported); - return false; - } + - qDebug() << "clone/reflink of ReFS on windows!"; + if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType)) + { + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "reflink/clone must be to the same device and filesystem! src and dst root filesystems do not match."; + return false; + } - if (!refs_clone(src_path, dst_path, ec)) - return false; +#if defined(Q_OS_WIN) - } else { - qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; + if (!win_ioctl_clone(src_path, dst_path, ec)) { + qDebug() << "failed win_ioctl_clone"; + qWarning() << "clone/reflink not supported on windows outside of btrfs or ReFS!"; qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; - ec = std::make_error_code(std::errc::not_supported); return false; } + + + #elif defined(Q_OS_LINUX) - if(!linux_ficlone(src_path, dst_path, ec)) + if(!linux_ficlone(src_path, dst_path, ec)) { + qDebug() << "failed linux_ficlone:"; return false; - + } + #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - if(!macos_bsd_clonefile(src_path, dst_path, ec)) + if(!macos_bsd_clonefile(src_path, dst_path, ec)) { + qDebug() << "failed macos_bsd_clonefile:"; return false; - + } + #else + qWarning() << "clone/reflink not supported! unknown OS"; ec = std::make_error_code(std::errc::not_supported); return false; + #endif return true; } #if defined(Q_OS_WIN) -typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); - -const long WinMaxChunkSize = 1L << 31; // 2GB static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) { @@ -1223,171 +1197,15 @@ static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) return (originalValue + mask) & ~mask; } -bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) -{ - // https://github.com/maharmstone/btrfs - QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path); - - std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast - - LPWSTR cmdLineWin = (wchar_t*)wstr.c_str(); - - // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609 - HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll"); - - if (shellbtrfsDLL == NULL) { - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported"; - return false; - } - - f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW"); - - if (!ReflinkCopyW) { - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported"; - return false; - } - - qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine; - - ReflinkCopyW(0, 0, cmdLineWin, 1); - - FreeLibrary(shellbtrfsDLL); - - return true; -} - -bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) -{ -#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE) - //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file - //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 - QString qSourcePath = StringUtils::fromStdString(src_path); - QString sourceVolumePath = statFS(qSourcePath).rootPath; - std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath); - - unsigned long sectorsPerCluster; - unsigned long bytesPerSector; - unsigned long numberOfFreeClusters; - unsigned long totalNumberOfClusters; - - if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){ - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to get disk info for source volume" << sourceVolumePath; - return false; - } - - long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector); - - HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (hSourceFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to open source file" << src_path.c_str(); - return false; - } - - HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); - if (hDestFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); - CloseHandle(hSourceFile); - qDebug() << "Failed to open dest file" << dst_path.c_str(); - return false; - } - - DWORD bytesReturned = 0; - - // Set the destination to be sparse while we clone. - // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle() - // below which will just be released when cloning file extents. - - if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) { - qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str(); - ec = std::error_code(GetLastError(), std::system_category()); - return false; - } - - LARGE_INTEGER sourceFileLengthStruct; - if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to get file info for source file" << src_path.c_str(); - return false; - } - - DWORD sourceFileLength = sourceFileLengthStruct.QuadPart; - - // Set the destination on-disk size the same as the source. - FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; - if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo, - &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO))) - { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to set end of file on destination file" << dst_path.c_str(); - return false; - } - - DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile }; - - long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize); - long sourceOffset = 0; - while(sourceOffset < sourceFileLength) - { - duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset; - duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset; - long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize); - duplicateExtentsData.ByteCount.QuadPart = thisChunkSize; - - DWORD numBytesReturned = 0; - bool ioctlResult = DeviceIoControl( - hDestFile, - FSCTL_DUPLICATE_EXTENTS_TO_FILE, - &duplicateExtentsData, - sizeof(DUPLICATE_EXTENTS_DATA), - nullptr, - 0, - &numBytesReturned, - nullptr); - if (!ioctlResult) - { - DWORD err = GetLastError(); - ec = std::error_code(err, std::system_category()); - QString additionalMessage; - if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) - { - static const int MaxClonesPerFile = 8175; - additionalMessage = QString( - " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " - "allowed %1 references for a single file. " - "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" - ).arg(MaxClonesPerFile); - - } - qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage; - return false; - } - - sourceOffset += thisChunkSize; - } - - CloseHandle(hDestFile); - CloseHandle(hSourceFile); - - return true; -#else - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "not built with refs support"; - return false; -#endif -} - bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { /** * This algorithm inspired from https://github.com/0xbadfca11/reflink * LICENSE MIT * + * Additional references + * https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file + * https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 */ HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); @@ -1495,11 +1313,6 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); - _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); - _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); - _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX); - _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart); - if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) { DWORD err = GetLastError(); @@ -1559,6 +1372,7 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std } #elif defined(Q_OS_LINUX) + bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec) { // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html @@ -1599,6 +1413,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std } #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) { // clonefile(const char * src, const char * dst, int flags); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 361993eb..84526c11 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -361,12 +361,20 @@ enum class FilesystemType { UNKNOWN }; +/** + * @brief Ordered Mapping of enum types to reported filesystem names + * this maping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use . + * all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup. + * + * QMap is ordered + * + */ static const QMap s_filesystem_type_names = { {FilesystemType::FAT, QString("FAT")}, {FilesystemType::NTFS, QString("NTFS")}, {FilesystemType::REFS, QString("REFS")}, {FilesystemType::EXT, QString("EXT")}, - {FilesystemType::EXT_2_OLD, QString("EXT2_OLD")}, + {FilesystemType::EXT_2_OLD, QString("EXT_2_OLD")}, {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, {FilesystemType::XFS, QString("XFS")}, {FilesystemType::BTRFS, QString("BTRFS")}, @@ -381,15 +389,27 @@ static const QMap s_filesystem_type_names = { {FilesystemType::UNKNOWN, QString("UNKNOWN")} }; + +/** + * @brief Ordered Mapping of reported filesystem names to enum types + * this maping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. + * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup. + * + * QMap is ordered + * + */ static const QMap s_filesystem_type_names_inverse = { {QString("FAT"), FilesystemType::FAT}, {QString("NTFS"), FilesystemType::NTFS}, {QString("REFS"), FilesystemType::REFS}, {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD}, + {QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, {QString("EXT2"), FilesystemType::EXT_2_3_4}, {QString("EXT3"), FilesystemType::EXT_2_3_4}, {QString("EXT4"), FilesystemType::EXT_2_3_4}, - {QString("EXT"), FilesystemType::EXT}, + {QString("EXT2/3/4"), FilesystemType::EXT_2_3_4}, + {QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, + {QString("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection {QString("XFS"), FilesystemType::XFS}, {QString("BTRFS"), FilesystemType::BTRFS}, {QString("NFS"), FilesystemType::NFS}, @@ -403,9 +423,32 @@ static const QMap s_filesystem_type_names_inverse = { {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; -inline QString getFilesystemTypeName(FilesystemType type) { - return s_filesystem_type_names.constFind(type).value(); -} +/** + * @brief Get the string name of Filesystem enum object + * + * @param type + * @return QString + */ +QString getFilesystemTypeName(FilesystemType type); + +/** + * @brief Get the Filesystem enum object from a name + * Does a lookup of the type name and returns an exact match + * + * @param name + * @return FilesystemType + */ +FilesystemType getFilesystemType(const QString& name); + +/** + * @brief Get the Filesystem enum object from a name + * Does a fuzzy lookup of the type name and returns an apropreate match + * + * @param name + * @return FilesystemType + */ +FilesystemType getFilesystemTypeFuzzy(const QString& name); + struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; @@ -430,9 +473,9 @@ QString NearestExistentAncestor(const QString& path); */ FilesystemInfo statFS(const QString& path); - -static const QList s_clone_filesystems = { - FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS, FilesystemType::REFS +static const QList s_clone_filesystems = { + FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, + FilesystemType::XFS, FilesystemType::REFS }; /** @@ -497,13 +540,7 @@ class clone : public QObject { bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) - -bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); -bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); -bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); - -#define USE_IOCTL_CLONE true - +bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) -- cgit From ae289c923c4f896dca7e6696eef7ca35b10be9bf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:40:07 -0700 Subject: fix: clean up initial review comments (flowin) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 15 ++-- launcher/FileSystem.h | 6 +- launcher/InstanceCopyTask.cpp | 6 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 129 ++++++++++++++++-------------- tests/FileSystem_test.cpp | 6 +- 5 files changed, 82 insertions(+), 80 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 45c73d24..2bd0cf52 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,10 +36,6 @@ */ #include "FileSystem.h" -#include -#include -#include -#include #include "BuildConfig.h" @@ -48,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -435,7 +432,7 @@ bool create_link::make_links() return true; } -void create_link::runPrivlaged(const QString& offset) +void create_link::runPrivileged(const QString& offset) { m_linked = 0; // reset counter m_path_results.clear(); @@ -506,10 +503,10 @@ void create_link::runPrivlaged(const QString& offset) in >> err_value; result.err_value = err_value; if (result.err_value) { - qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; + qDebug() << "privileged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; emit linkFailed(result.src, result.dst, result.err_msg, result.err_value); } else { - qDebug() << "privlaged link success" << result.src << "to" << result.dst; + qDebug() << "privileged link success" << result.src << "to" << result.dst; m_linked++; emit fileLinked(result.src, result.dst); } @@ -533,7 +530,7 @@ void create_link::runPrivlaged(const QString& offset) } ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); }); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); @@ -1041,7 +1038,7 @@ FilesystemInfo statFS(const QString& path) QStorageInfo storage_info(NearestExistentAncestor(path)); - info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + info.fsTypeName = storage_info.fileSystemType(); info.fsType = getFilesystemTypeFuzzy(info.fsTypeName); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 84526c11..71175bb4 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -220,8 +220,8 @@ class create_link : public QObject { int totalLinked() { return m_linked; } - void runPrivlaged() { runPrivlaged(QString()); } - void runPrivlaged(const QString& offset); + void runPrivileged() { runPrivileged(QString()); } + void runPrivileged(const QString& offset); QList getResults() { return m_path_results; } @@ -230,7 +230,7 @@ class create_link : public QObject { void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); void finished(); - void finishedPrivlaged(bool gotResults); + void finishedPrivileged(bool gotResults); private: diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e0a4de0b..5ef7a7fd 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -72,14 +72,14 @@ void InstanceCopyTask::executeTask() QEventLoop loop; bool got_priv_results = false; - connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ if (!gotResults) { - qDebug() << "Privlaged run exited without results!"; + qDebug() << "Privileged run exited without results!"; } got_priv_results = gotResults; loop.quit(); }); - folderLink.runPrivlaged(); + folderLink.runPrivileged(); loop.exec(); // wait for the finished signal diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 009f5b88..3101acec 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 527 - 699 + 531 + 653 @@ -112,35 +112,19 @@ - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Select all - - - false - - - - - Instance Copy Options + + + + Keep play time + + + @@ -151,6 +135,16 @@ + + + + true + + + Copy resource packs + + + @@ -161,13 +155,6 @@ - - - - Copy saves - - - @@ -182,33 +169,49 @@ - - - - true - + + - Copy resource packs + Copy saves - - + + - Keep play time + Copy screenshots - - + + + + + 0 + 0 + + + + Qt::LeftToRight + - Copy screenshots + Select all + + + false + + + + Qt::Horizontal + + + @@ -250,7 +253,7 @@ - + 6 @@ -263,24 +266,14 @@ 6 - - - - true - - - Use hard links instead of symbolic links. - - - Use hard links - - - false + + Link each resource individually instead of linking whole folders at once + Link files recursively @@ -302,14 +295,27 @@ - - + + + + true + + + Use hard links instead of copying files. + - Use symbolic links + Use hard links + + + + Use symbolic links instead of copying files. + + Use symbolic links + @@ -373,7 +379,6 @@ iconButton instNameTextBox groupBox - selectAllCheckbox keepPlaytimeCheckbox copyScreenshotsCheckbox copySavesCheckbox diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 169f0669..19565a99 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -72,15 +72,15 @@ class LinkTask : public Task { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "atempting to run with privelage"; - connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + connect(m_lnk, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ if (gotResults) { emitSucceeded(); } else { - qDebug() << "Privlaged run exited without results!"; + qDebug() << "Privileged run exited without results!"; emitFailed(); } }); - m_lnk->runPrivlaged(); + m_lnk->runPrivileged(); } else { qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); } -- cgit From 0c986ba4d006740947603afb2e18dd9d2ffdfd2f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:38:40 -0700 Subject: spelling and formatting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 14 +-- launcher/FileSystem.h | 185 +++++++++++++---------------- launcher/InstanceCopyTask.cpp | 45 ++++--- launcher/filelink/FileLink.cpp | 110 +++++++---------- launcher/filelink/FileLink.h | 20 ++-- launcher/filelink/main.cpp | 5 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 40 +++---- launcher/ui/dialogs/CopyInstanceDialog.h | 21 ++-- 8 files changed, 192 insertions(+), 248 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c913b43e..640cf9be 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -328,7 +328,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) } /** - * @brief make a list off all the links ot make + * @brief make a list off all the links to * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ @@ -363,7 +363,7 @@ void create_link::make_link_list(const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth; + qDebug() << "linking recursively:" << src << "to" << dst << "max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); @@ -414,7 +414,7 @@ bool create_link::make_links() qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; - qDebug() << "Error catagory:" << m_os_err.category().name(); + qDebug() << "Error category:" << m_os_err.category().name(); qDebug() << "Error code:" << m_os_err.value(); emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value()); } else { @@ -469,7 +469,7 @@ void create_link::runPrivileged(const QString& offset) in.setDevice(clientConnection); in.setVersion(QDataStream::Qt_5_0); qDebug() << "Reading path results from client"; - qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + qDebug() << "bytes available" << clientConnection->bytesAvailable(); // Relies on the fact that QDataStream serializes a quint32 into // sizeof(quint32) bytes @@ -479,7 +479,7 @@ void create_link::runPrivileged(const QString& offset) in >> blockSize; qDebug() << "blocksize is" << blockSize; - qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + qDebug() << "bytes available" << clientConnection->bytesAvailable(); if (clientConnection->bytesAvailable() < blockSize || in.atEnd()) return; @@ -506,7 +506,7 @@ void create_link::runPrivileged(const QString& offset) m_path_results.append(result); } gotResults = true; - qDebug() << "results recieved, closing connection"; + qDebug() << "results received, closing connection"; clientConnection->close(); }); @@ -981,7 +981,7 @@ FilesystemType getFilesystemType(const QString& name) } /** - * @brief path to the near ancestor that exsists + * @brief path to the near ancestor that exists * */ QString NearestExistentAncestor(const QString& path) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 71175bb4..673c3563 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -44,9 +44,9 @@ #include #include +#include #include #include -#include namespace FS { @@ -84,7 +84,7 @@ bool ensureFolderPathExists(QString filenamepath); /** * @brief Copies a directory and it's contents from src to dest - */ + */ class copy : public QObject { Q_OBJECT public: @@ -167,17 +167,14 @@ class ExternalLinkFileProcess : public QThread { /** * @brief links (a file / a directory and it's contents) from src to dest - */ + */ class create_link : public QObject { Q_OBJECT public: - create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) - { - m_path_pairs.append(path_pairs); - } + create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) { m_path_pairs.append(path_pairs); } create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) { - LinkPair pair = {src, dst}; + LinkPair pair = { src, dst }; m_path_pairs.append(pair); } create_link& useHardLinks(const bool useHard) @@ -211,28 +208,23 @@ class create_link : public QObject { return *this; } - std::error_code getOSError() { - return m_os_err; - } + std::error_code getOSError() { return m_os_err; } bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalLinked() { return m_linked; } - void runPrivileged() { runPrivileged(QString()); } void runPrivileged(const QString& offset); QList getResults() { return m_path_results; } - signals: void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); void finished(); void finishedPrivileged(bool gotResults); - private: bool operator()(const QString& offset, bool dryRun = false); void make_link_list(const QString& offset); @@ -262,9 +254,9 @@ class create_link : public QObject { * @brief moves a file by renaming it * @param source source file path * @param dest destination filepath - * + * */ -bool move(const QString& source, const QString& dest); +bool move(const QString& source, const QString& dest); /** * Delete a folder recursively @@ -274,7 +266,7 @@ bool deletePath(QString path); /** * Trash a folder / file */ -bool trash(QString path, QString *pathInTrash = nullptr); +bool trash(QString path, QString* pathInTrash = nullptr); QString PathCombine(const QString& path1, const QString& path2); QString PathCombine(const QString& path1, const QString& path2, const QString& path3); @@ -284,16 +276,15 @@ QString AbsolutePath(const QString& path); /** * @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc. - * + * * @param path path to measure - * @return int number of componants before base path + * @return int number of components before base path */ int PathDepth(const QString& path); - /** - * @brief cut off segments of path untill it is a max of length depth - * + * @brief cut off segments of path until it is a max of length depth + * * @param path path to truncate * @param depth max depth of new path * @return QString truncated path @@ -363,124 +354,118 @@ enum class FilesystemType { /** * @brief Ordered Mapping of enum types to reported filesystem names - * this maping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use . + * this mapping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use . * all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup. - * + * * QMap is ordered - * + * */ -static const QMap s_filesystem_type_names = { - {FilesystemType::FAT, QString("FAT")}, - {FilesystemType::NTFS, QString("NTFS")}, - {FilesystemType::REFS, QString("REFS")}, - {FilesystemType::EXT, QString("EXT")}, - {FilesystemType::EXT_2_OLD, QString("EXT_2_OLD")}, - {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, - {FilesystemType::XFS, QString("XFS")}, - {FilesystemType::BTRFS, QString("BTRFS")}, - {FilesystemType::NFS, QString("NFS")}, - {FilesystemType::ZFS, QString("ZFS")}, - {FilesystemType::APFS, QString("APFS")}, - {FilesystemType::HFS, QString("HFS")}, - {FilesystemType::HFSPLUS, QString("HFSPLUS")}, - {FilesystemType::HFSX, QString("HFSX")}, - {FilesystemType::FUSEBLK, QString("FUSEBLK")}, - {FilesystemType::F2FS, QString("F2FS")}, - {FilesystemType::UNKNOWN, QString("UNKNOWN")} -}; - +static const QMap s_filesystem_type_names = { { FilesystemType::FAT, QString("FAT") }, + { FilesystemType::NTFS, QString("NTFS") }, + { FilesystemType::REFS, QString("REFS") }, + { FilesystemType::EXT, QString("EXT") }, + { FilesystemType::EXT_2_OLD, QString("EXT_2_OLD") }, + { FilesystemType::EXT_2_3_4, QString("EXT2/3/4") }, + { FilesystemType::XFS, QString("XFS") }, + { FilesystemType::BTRFS, QString("BTRFS") }, + { FilesystemType::NFS, QString("NFS") }, + { FilesystemType::ZFS, QString("ZFS") }, + { FilesystemType::APFS, QString("APFS") }, + { FilesystemType::HFS, QString("HFS") }, + { FilesystemType::HFSPLUS, QString("HFSPLUS") }, + { FilesystemType::HFSX, QString("HFSX") }, + { FilesystemType::FUSEBLK, QString("FUSEBLK") }, + { FilesystemType::F2FS, QString("F2FS") }, + { FilesystemType::UNKNOWN, QString("UNKNOWN") } }; /** * @brief Ordered Mapping of reported filesystem names to enum types - * this maping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. + * this mapping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup. - * + * * QMap is ordered - * + * */ static const QMap s_filesystem_type_names_inverse = { - {QString("FAT"), FilesystemType::FAT}, - {QString("NTFS"), FilesystemType::NTFS}, - {QString("REFS"), FilesystemType::REFS}, - {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD}, - {QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, - {QString("EXT2"), FilesystemType::EXT_2_3_4}, - {QString("EXT3"), FilesystemType::EXT_2_3_4}, - {QString("EXT4"), FilesystemType::EXT_2_3_4}, - {QString("EXT2/3/4"), FilesystemType::EXT_2_3_4}, - {QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, - {QString("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection - {QString("XFS"), FilesystemType::XFS}, - {QString("BTRFS"), FilesystemType::BTRFS}, - {QString("NFS"), FilesystemType::NFS}, - {QString("ZFS"), FilesystemType::ZFS}, - {QString("APFS"), FilesystemType::APFS}, - {QString("HFSPLUS"), FilesystemType::HFSPLUS}, - {QString("HFSX"), FilesystemType::HFSX}, - {QString("HFS"), FilesystemType::HFS}, - {QString("FUSEBLK"), FilesystemType::FUSEBLK}, - {QString("F2FS"), FilesystemType::F2FS}, - {QString("UNKNOWN"), FilesystemType::UNKNOWN} + { QString("FAT"), FilesystemType::FAT }, + { QString("NTFS"), FilesystemType::NTFS }, + { QString("REFS"), FilesystemType::REFS }, + { QString("EXT2_OLD"), FilesystemType::EXT_2_OLD }, + { QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD }, + { QString("EXT2"), FilesystemType::EXT_2_3_4 }, + { QString("EXT3"), FilesystemType::EXT_2_3_4 }, + { QString("EXT4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT2/3/4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT"), FilesystemType::EXT }, // must come after all other EXT variants to prevent greedy detection + { QString("XFS"), FilesystemType::XFS }, + { QString("BTRFS"), FilesystemType::BTRFS }, + { QString("NFS"), FilesystemType::NFS }, + { QString("ZFS"), FilesystemType::ZFS }, + { QString("APFS"), FilesystemType::APFS }, + { QString("HFSPLUS"), FilesystemType::HFSPLUS }, + { QString("HFSX"), FilesystemType::HFSX }, + { QString("HFS"), FilesystemType::HFS }, + { QString("FUSEBLK"), FilesystemType::FUSEBLK }, + { QString("F2FS"), FilesystemType::F2FS }, + { QString("UNKNOWN"), FilesystemType::UNKNOWN } }; /** * @brief Get the string name of Filesystem enum object - * - * @param type - * @return QString + * + * @param type + * @return QString */ QString getFilesystemTypeName(FilesystemType type); /** * @brief Get the Filesystem enum object from a name - * Does a lookup of the type name and returns an exact match + * Does a lookup of the type name and returns an exact match * - * @param name - * @return FilesystemType + * @param name + * @return FilesystemType */ FilesystemType getFilesystemType(const QString& name); /** * @brief Get the Filesystem enum object from a name * Does a fuzzy lookup of the type name and returns an apropreate match - * - * @param name - * @return FilesystemType + * + * @param name + * @return FilesystemType */ FilesystemType getFilesystemTypeFuzzy(const QString& name); - struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; QString fsTypeName; - int blockSize; - qint64 bytesAvailable; - qint64 bytesFree; - qint64 bytesTotal; + int blockSize; + qint64 bytesAvailable; + qint64 bytesFree; + qint64 bytesTotal; QString name; QString rootPath; }; /** - * @brief path to the near ancestor that exsists - * + * @brief path to the near ancestor that exists + * */ QString NearestExistentAncestor(const QString& path); /** * @brief colect information about the filesystem under a file - * + * */ FilesystemInfo statFS(const QString& path); -static const QList s_clone_filesystems = { - FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, - FilesystemType::XFS, FilesystemType::REFS -}; +static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, + FilesystemType::XFS, FilesystemType::REFS }; /** - * @brief if the Filesystem is reflink/clone capable - * + * @brief if the Filesystem is reflink/clone capable + * */ bool canCloneOnFS(const QString& path); bool canCloneOnFS(const FilesystemInfo& info); @@ -488,13 +473,13 @@ bool canCloneOnFS(FilesystemType type); /** * @brief if the Filesystems are reflink/clone capable and both are on the same device - * + * */ bool canClone(const QString& src, const QString& dst); /** * @brief Copies a directory and it's contents from src to dest - */ + */ class clone : public QObject { Q_OBJECT public: @@ -535,7 +520,7 @@ class clone : public QObject { /** * @brief clone/reflink file from src to dst - * + * */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); @@ -552,8 +537,8 @@ static const QList s_non_link_filesystems = { }; /** - * @brief if the Filesystem is symlink capable - * + * @brief if the Filesystem is symlink capable + * */ bool canLinkOnFS(const QString& path); bool canLinkOnFS(const FilesystemInfo& info); @@ -561,10 +546,10 @@ bool canLinkOnFS(FilesystemType type); /** * @brief if the Filesystem is symlink capable on both ends - * + * */ bool canLink(const QString& src, const QString& dst); uintmax_t hardLinkCount(const QString& path); -} +} // namespace FS diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 6bd56de3..4ac3b51a 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -1,10 +1,10 @@ #include "InstanceCopyTask.h" -#include "settings/INISettingsObject.h" +#include +#include #include "FileSystem.h" #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" -#include -#include +#include "settings/INISettingsObject.h" InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { @@ -15,17 +15,17 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); m_useClone = prefs.isUseCloneEnabled(); - + QString filters = prefs.getSelectedFiltersAsRegex(); if (m_useLinks || m_useHardLinks) { - if (!filters.isEmpty()) filters += "|"; + if (!filters.isEmpty()) + filters += "|"; filters += "instance.cfg"; - } + } qDebug() << "CopyFilters:" << filters; - if (!filters.isEmpty()) - { + if (!filters.isEmpty()) { // Set regex filter: // FIXME: get this from the original instance type... auto matcherReal = new RegexpMatcher(filters); @@ -38,14 +38,14 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - auto copySaves = [&](){ - FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves")); + auto copySaves = [&]() { + FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves"), FS::PathCombine(m_stagingPath, "saves")); savesCopy.followSymlinks(true); return savesCopy(); }; - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); folderClone.matcher(m_matcher.get()); @@ -53,22 +53,22 @@ void InstanceCopyTask::executeTask() return folderClone(); } else if (m_useLinks || m_useHardLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); - int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder + int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); bool there_were_errors = false; - if(!folderLink()){ + if (!folderLink()) { #if defined Q_OS_WIN32 if (!m_useHardLinks) { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - qDebug() << "atempting to run with privelage"; + qDebug() << "attempting to run with privelage"; QEventLoop loop; bool got_priv_results = false; - connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ + connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) { if (!gotResults) { qDebug() << "Privileged run exited without results!"; } @@ -77,7 +77,7 @@ void InstanceCopyTask::executeTask() }); folderLink.runPrivileged(); - loop.exec(); // wait for the finished signal + loop.exec(); // wait for the finished signal for (auto result : folderLink.getResults()) { if (result.err_value != 0) { @@ -88,17 +88,17 @@ void InstanceCopyTask::executeTask() if (m_copySaves) { there_were_errors |= !copySaves(); } - + return got_priv_results && !there_were_errors; } else { qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); - } + } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); -#endif +#endif return false; } - + if (m_copySaves) { there_were_errors |= !copySaves(); } @@ -119,8 +119,7 @@ void InstanceCopyTask::executeTask() void InstanceCopyTask::copyFinished() { auto successful = m_copyFuture.result(); - if(!successful) - { + if (!successful) { emitFailed(tr("Instance folder copy failed.")); return; } @@ -130,7 +129,7 @@ void InstanceCopyTask::copyFinished() InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); inst->setName(name()); inst->setIconKey(m_instIcon); - if(!m_keepPlaytime) { + if (!m_keepPlaytime) { inst->resetTimePlayed(); } if (m_useLinks) diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index a731ecdb..ded46061 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -25,7 +25,6 @@ #include "StringUtils.h" - #include #include @@ -41,53 +40,47 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include #include +#include #endif // Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header #ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif // __APPLE__ #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) #if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) #define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check +#endif // MacOS min version check +#endif // Other OSes version check #ifndef GHC_USE_STD_FS #include namespace fs = ghc::filesystem; #endif - - -FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) +FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { #if defined Q_OS_WIN32 // attach the parent console - if(AttachConsole(ATTACH_PARENT_PROCESS)) - { + if (AttachConsole(ATTACH_PARENT_PROCESS)) { // if attach succeeds, reopen and sync all the i/o - if(freopen("CON", "w", stdout)) - { + if (freopen("CON", "w", stdout)) { std::cout.sync_with_stdio(); } - if(freopen("CON", "w", stderr)) - { + if (freopen("CON", "w", stderr)) { std::cerr.sync_with_stdio(); } - if(freopen("CON", "r", stdin)) - { + if (freopen("CON", "r", stdin)) { std::cin.sync_with_stdio(); } - auto out = GetStdHandle (STD_OUTPUT_HANDLE); + auto out = GetStdHandle(STD_OUTPUT_HANDLE); DWORD written; - const char * endline = "\n"; + const char* endline = "\n"; WriteConsole(out, endline, strlen(endline), &written, NULL); consoleAttached = true; } @@ -101,10 +94,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), QCommandLineParser parser; parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher")); - parser.addOptions({ - {{"s", "server"}, "Join the specified server on launch", "pipe name"}, - {{"H", "hard"}, "use hard links insted of symbolic", "true/false"} - }); + parser.addOptions({ { { "s", "server" }, "Join the specified server on launch", "pipe name" }, + { { "H", "hard" }, "use hard links instead of symbolic", "true/false" } }); parser.addHelpOption(); parser.addVersionOption(); @@ -122,63 +113,57 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), qDebug() << "no server to join"; exit(); } - } void FileLinkApp::joinServer(QString server) { - - blockSize = 0; + blockSize = 0; in.setDevice(&socket); in.setVersion(QDataStream::Qt_5_0); - connect(&socket, &QLocalSocket::connected, this, [&](){ - qDebug() << "connected to server"; - }); + connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; }); connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); - connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ + connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) { switch (socketError) { - case QLocalSocket::ServerNotFoundError: - qDebug() << ("The host was not found. Please make sure " + case QLocalSocket::ServerNotFoundError: + qDebug() + << ("The host was not found. Please make sure " "that the server is running and that the " "server name is correct."); - break; - case QLocalSocket::ConnectionRefusedError: - qDebug() << ("The connection was refused by the peer. " + break; + case QLocalSocket::ConnectionRefusedError: + qDebug() + << ("The connection was refused by the peer. " "Make sure the server is running, " "and check that the server name " "is correct."); - break; - case QLocalSocket::PeerClosedError: - qDebug() << ("The connection was closed by the peer. "); - break; - default: - qDebug() << "The following error occurred: " << socket.errorString(); + break; + case QLocalSocket::PeerClosedError: + qDebug() << ("The connection was closed by the peer. "); + break; + default: + qDebug() << "The following error occurred: " << socket.errorString(); } }); - connect(&socket, &QLocalSocket::disconnected, this, [&](){ - qDebug() << "dissconnected from server, should exit"; + connect(&socket, &QLocalSocket::disconnected, this, [&]() { + qDebug() << "disconnected from server, should exit"; exit(); }); socket.connectToServer(server); - - } void FileLinkApp::runLink() -{ - +{ std::error_code os_err; qDebug() << "creating links"; for (auto link : m_links_to_make) { - QString src_path = link.src; QString dst_path = link.dst; @@ -192,26 +177,25 @@ void FileLinkApp::runLink() } else { qDebug() << "making symlink:" << src_path << "to" << dst_path; fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); - } + } if (os_err) { qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; - qDebug() << "Error catagory:" << os_err.category().name(); + qDebug() << "Error category:" << os_err.category().name(); qDebug() << "Error code:" << os_err.value(); - FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()}; + FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() }; m_path_results.append(result); } else { - FS::LinkResult result = {src_path, dst_path}; + FS::LinkResult result = { src_path, dst_path }; m_path_results.append(result); } } sendResults(); qDebug() << "done, should exit soon"; - } void FileLinkApp::sendResults() @@ -245,10 +229,10 @@ void FileLinkApp::sendResults() } void FileLinkApp::readPathPairs() -{ +{ m_links_to_make.clear(); qDebug() << "Reading path pairs from server"; - qDebug() << "bytes avalible" << socket.bytesAvailable(); + qDebug() << "bytes available" << socket.bytesAvailable(); if (blockSize == 0) { // Relies on the fact that QDataStream serializes a quint32 into // sizeof(quint32) bytes @@ -258,15 +242,15 @@ void FileLinkApp::readPathPairs() in >> blockSize; } qDebug() << "blocksize is" << blockSize; - qDebug() << "bytes avalible" << socket.bytesAvailable(); + qDebug() << "bytes available" << socket.bytesAvailable(); if (socket.bytesAvailable() < blockSize || in.atEnd()) return; - + quint32 numLinks; in >> numLinks; qDebug() << "numLinks" << numLinks; - for(int i = 0; i < numLinks; i++) { + for (int i = 0; i < numLinks; i++) { FS::LinkPair pair; in >> pair.src; in >> pair.dst; @@ -277,17 +261,15 @@ void FileLinkApp::readPathPairs() runLink(); } - FileLinkApp::~FileLinkApp() -{ +{ qDebug() << "link program shutting down"; // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); #if defined Q_OS_WIN32 // Detach from Windows console - if(consoleAttached) - { + if (consoleAttached) { fclose(stdout); fclose(stdin); fclose(stderr); @@ -295,7 +277,3 @@ FileLinkApp::~FileLinkApp() } #endif } - - - - diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index d146b8d9..4c47d9bb 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -25,30 +25,26 @@ #include #include -#include +#include +#include #include #include #include -#include -#include -#include -#include #include +#include +#include #define PRISM_EXTERNAL_EXE #include "FileSystem.h" -class FileLinkApp : public QCoreApplication -{ +class FileLinkApp : public QCoreApplication { // friends for the purpose of limiting access to deprecated stuff Q_OBJECT -public: - - FileLinkApp(int &argc, char **argv); + public: + FileLinkApp(int& argc, char** argv); virtual ~FileLinkApp(); -private: - + private: void joinServer(QString server); void readPathPairs(); void runLink(); diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp index 4a22ff18..83566a3c 100644 --- a/launcher/filelink/main.cpp +++ b/launcher/filelink/main.cpp @@ -22,10 +22,9 @@ #include "FileLink.h" -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { - FileLinkApp ldh(argc, argv); - + return ldh.exec(); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index ced57ae0..347bd39f 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -43,15 +43,15 @@ #include "ui/dialogs/IconPickerDialog.h" -#include "BaseVersion.h" -#include "icons/IconList.h" #include "BaseInstance.h" -#include "InstanceList.h" -#include "FileSystem.h" +#include "BaseVersion.h" #include "DesktopServices.h" +#include "FileSystem.h" +#include "InstanceList.h" +#include "icons/IconList.h" -CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) - :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) +CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent) + : QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) { ui->setupUi(this); resize(minimumSizeHint()); @@ -74,8 +74,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) groupList.push_front(""); ui->groupBox->addItems(groupList); int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id())); - if(index == -1) - { + if (index == -1) { index = 0; } ui->groupBox->setCurrentIndex(index); @@ -108,10 +107,8 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) #if defined(Q_OS_WIN) ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield)); - ui->symbolicLinksCheckbox->setToolTip( - tr("Use symbolic links instead of copying files.") + - tr("\nOn windows symbolic links may require admin permision to create.") - ); + ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") + + tr("\nOn windows symbolic links may require admin permission to create.")); #endif updateLinkOptions(); @@ -119,7 +116,6 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help); connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help); - } CopyInstanceDialog::~CopyInstanceDialog() @@ -131,8 +127,7 @@ void CopyInstanceDialog::updateDialogState() { auto allowOK = !instName().isEmpty(); auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok); - if(OkButton->isEnabled() != allowOK) - { + if (OkButton->isEnabled() != allowOK) { OkButton->setEnabled(allowOK); } } @@ -140,8 +135,7 @@ void CopyInstanceDialog::updateDialogState() QString CopyInstanceDialog::instName() const { auto result = ui->instNameTextBox->text().trimmed(); - if(result.size()) - { + if (result.size()) { return result; } return QString(); @@ -162,7 +156,6 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const return m_selectedOptions; } - void CopyInstanceDialog::help() { DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy"))); @@ -200,7 +193,8 @@ void CopyInstanceDialog::updateLinkOptions() ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); - ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked()); + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && + !ui->useCloneCheckbox->isChecked()); ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked()); bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); @@ -220,15 +214,13 @@ void CopyInstanceDialog::on_iconButton_clicked() IconPickerDialog dlg(this); dlg.execWithSelection(InstIconKey); - if (dlg.result() == QDialog::Accepted) - { + if (dlg.result() == QDialog::Accepted) { InstIconKey = dlg.selectedIconKey; ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); } } - -void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) +void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1) { updateDialogState(); } @@ -247,7 +239,6 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) updateSelectAllCheckbox(); } - void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { m_selectedOptions.enableKeepPlaytime(state == Qt::Checked); @@ -311,7 +302,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) { m_selectedOptions.enableLinkRecursively(state == Qt::Checked); updateLinkOptions(); - } void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index c447bee9..698c6e93 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -22,17 +22,15 @@ class BaseInstance; -namespace Ui -{ +namespace Ui { class CopyInstanceDialog; } -class CopyInstanceDialog : public QDialog -{ +class CopyInstanceDialog : public QDialog { Q_OBJECT -public: - explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); + public: + explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0); ~CopyInstanceDialog(); void updateDialogState(); @@ -42,13 +40,12 @@ public: QString iconKey() const; const InstanceCopyPrefs& getChosenOptions() const; -public slots: + public slots: void help(); -private -slots: + private slots: void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); + void on_instNameTextBox_textChanged(const QString& arg1); // Checkboxes void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); @@ -65,14 +62,14 @@ slots: void on_dontLinkSavesCheckbox_stateChanged(int state); void on_useCloneCheckbox_stateChanged(int state); -private: + private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); void updateUseCloneCheckbox(); void updateLinkOptions(); /* data */ - Ui::CopyInstanceDialog *ui; + Ui::CopyInstanceDialog* ui; QString InstIconKey; InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; -- cgit From 538092b72728fa34bafc873e16abaa7f318a945c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 20:22:07 -0700 Subject: fix: typos, CamelCase to camelCase the new names Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 30 ++++++++-------- launcher/FileSystem.h | 87 ++++++++++++++++++++++++----------------------- tests/FileSystem_test.cpp | 40 +++++++++++----------- 3 files changed, 81 insertions(+), 76 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 640cf9be..c046ee86 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -328,7 +328,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) } /** - * @brief make a list off all the links to + * @brief Make a list of all the links to make * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ @@ -363,7 +363,7 @@ void create_link::make_link_list(const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug() << "linking recursively:" << src << "to" << dst << "max_depth:" << m_max_depth; + qDebug() << "linking recursively:" << src << "to" << dst << ", max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); @@ -373,8 +373,8 @@ void create_link::make_link_list(const QString& offset) auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); - if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth) { - relative_path = PathTruncate(relative_path, m_max_depth); + if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){ + relative_path = pathTruncate(relative_path, m_max_depth); src_path = src_dir.filePath(relative_path); if (linkedPaths.contains(src_path)) { continue; @@ -394,20 +394,22 @@ bool create_link::make_links() for (auto link : m_links_to_make) { QString src_path = link.src; QString dst_path = link.dst; + auto src_path_std = StringUtils::toStdString(link.src); + auto dst_path_std = StringUtils::toStdString(link.dst); ensureFilePathExists(dst_path); if (m_useHardLinks) { if (m_debug) qDebug() << "making hard link:" << src_path << "to" << dst_path; - fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); - } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + fs::create_hard_link(src_path_std, dst_path_std, m_os_err); + } else if (fs::is_directory(src_path_std)) { if (m_debug) qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; - fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + fs::create_directory_symlink(src_path_std, dst_path_std, m_os_err); } else { if (m_debug) qDebug() << "making symlink:" << src_path << "to" << dst_path; - fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + fs::create_symlink(src_path_std, dst_path_std, m_os_err); } if (m_os_err) { @@ -636,7 +638,7 @@ QString AbsolutePath(const QString& path) return QFileInfo(path).absolutePath(); } -int PathDepth(const QString& path) +int pathDepth(const QString& path) { if (path.isEmpty()) return 0; @@ -656,15 +658,15 @@ int PathDepth(const QString& path) return numParts; } -QString PathTruncate(const QString& path, int depth) +QString pathTruncate(const QString& path, int depth) { if (path.isEmpty() || (depth < 0)) return ""; QString trunc = QFileInfo(path).path(); - if (PathDepth(trunc) > depth) { - return PathTruncate(trunc, depth); + if (pathDepth(trunc) > depth ) { + return pathTruncate(trunc, depth); } #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) @@ -984,7 +986,7 @@ FilesystemType getFilesystemType(const QString& name) * @brief path to the near ancestor that exists * */ -QString NearestExistentAncestor(const QString& path) +QString nearestExistentAncestor(const QString& path) { if (QFileInfo::exists(path)) return path; @@ -1007,7 +1009,7 @@ FilesystemInfo statFS(const QString& path) { FilesystemInfo info; - QStorageInfo storage_info(NearestExistentAncestor(path)); + QStorageInfo storage_info(nearestExistentAncestor(path)); info.fsTypeName = storage_info.fileSystemType(); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 673c3563..47044d93 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -280,7 +280,7 @@ QString AbsolutePath(const QString& path); * @param path path to measure * @return int number of components before base path */ -int PathDepth(const QString& path); +int pathDepth(const QString& path); /** * @brief cut off segments of path until it is a max of length depth @@ -289,7 +289,7 @@ int PathDepth(const QString& path); * @param depth max depth of new path * @return QString truncated path */ -QString PathTruncate(const QString& path, int depth); +QString pathTruncate(const QString& path, int depth); /** * Resolve an executable @@ -360,23 +360,26 @@ enum class FilesystemType { * QMap is ordered * */ -static const QMap s_filesystem_type_names = { { FilesystemType::FAT, QString("FAT") }, - { FilesystemType::NTFS, QString("NTFS") }, - { FilesystemType::REFS, QString("REFS") }, - { FilesystemType::EXT, QString("EXT") }, - { FilesystemType::EXT_2_OLD, QString("EXT_2_OLD") }, - { FilesystemType::EXT_2_3_4, QString("EXT2/3/4") }, - { FilesystemType::XFS, QString("XFS") }, - { FilesystemType::BTRFS, QString("BTRFS") }, - { FilesystemType::NFS, QString("NFS") }, - { FilesystemType::ZFS, QString("ZFS") }, - { FilesystemType::APFS, QString("APFS") }, - { FilesystemType::HFS, QString("HFS") }, - { FilesystemType::HFSPLUS, QString("HFSPLUS") }, - { FilesystemType::HFSX, QString("HFSX") }, - { FilesystemType::FUSEBLK, QString("FUSEBLK") }, - { FilesystemType::F2FS, QString("F2FS") }, - { FilesystemType::UNKNOWN, QString("UNKNOWN") } }; +static const QMap s_filesystem_type_names = { + {FilesystemType::FAT, QStringLiteral("FAT")}, + {FilesystemType::NTFS, QStringLiteral("NTFS")}, + {FilesystemType::REFS, QStringLiteral("REFS")}, + {FilesystemType::EXT, QStringLiteral("EXT")}, + {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")}, + {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")}, + {FilesystemType::XFS, QStringLiteral("XFS")}, + {FilesystemType::BTRFS, QStringLiteral("BTRFS")}, + {FilesystemType::NFS, QStringLiteral("NFS")}, + {FilesystemType::ZFS, QStringLiteral("ZFS")}, + {FilesystemType::APFS, QStringLiteral("APFS")}, + {FilesystemType::HFS, QStringLiteral("HFS")}, + {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")}, + {FilesystemType::HFSX, QStringLiteral("HFSX")}, + {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")}, + {FilesystemType::F2FS, QStringLiteral("F2FS")}, + {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")} +}; + /** * @brief Ordered Mapping of reported filesystem names to enum types @@ -387,28 +390,28 @@ static const QMap s_filesystem_type_names = { { Filesys * */ static const QMap s_filesystem_type_names_inverse = { - { QString("FAT"), FilesystemType::FAT }, - { QString("NTFS"), FilesystemType::NTFS }, - { QString("REFS"), FilesystemType::REFS }, - { QString("EXT2_OLD"), FilesystemType::EXT_2_OLD }, - { QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD }, - { QString("EXT2"), FilesystemType::EXT_2_3_4 }, - { QString("EXT3"), FilesystemType::EXT_2_3_4 }, - { QString("EXT4"), FilesystemType::EXT_2_3_4 }, - { QString("EXT2/3/4"), FilesystemType::EXT_2_3_4 }, - { QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4 }, - { QString("EXT"), FilesystemType::EXT }, // must come after all other EXT variants to prevent greedy detection - { QString("XFS"), FilesystemType::XFS }, - { QString("BTRFS"), FilesystemType::BTRFS }, - { QString("NFS"), FilesystemType::NFS }, - { QString("ZFS"), FilesystemType::ZFS }, - { QString("APFS"), FilesystemType::APFS }, - { QString("HFSPLUS"), FilesystemType::HFSPLUS }, - { QString("HFSX"), FilesystemType::HFSX }, - { QString("HFS"), FilesystemType::HFS }, - { QString("FUSEBLK"), FilesystemType::FUSEBLK }, - { QString("F2FS"), FilesystemType::F2FS }, - { QString("UNKNOWN"), FilesystemType::UNKNOWN } + {QStringLiteral("FAT"), FilesystemType::FAT}, + {QStringLiteral("NTFS"), FilesystemType::NTFS}, + {QStringLiteral("REFS"), FilesystemType::REFS}, + {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD}, + {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, + {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection + {QStringLiteral("XFS"), FilesystemType::XFS}, + {QStringLiteral("BTRFS"), FilesystemType::BTRFS}, + {QStringLiteral("NFS"), FilesystemType::NFS}, + {QStringLiteral("ZFS"), FilesystemType::ZFS}, + {QStringLiteral("APFS"), FilesystemType::APFS}, + {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS}, + {QStringLiteral("HFSX"), FilesystemType::HFSX}, + {QStringLiteral("HFS"), FilesystemType::HFS}, + {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK}, + {QStringLiteral("F2FS"), FilesystemType::F2FS}, + {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN} }; /** @@ -452,7 +455,7 @@ struct FilesystemInfo { * @brief path to the near ancestor that exists * */ -QString NearestExistentAncestor(const QString& path); +QString nearestExistentAncestor(const QString& path); /** * @brief colect information about the filesystem under a file diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 19565a99..ec1f0bcf 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -756,30 +756,30 @@ slots: } void test_path_depth() { - QCOMPARE(FS::PathDepth(""), 0); - QCOMPARE(FS::PathDepth("."), 0); - QCOMPARE(FS::PathDepth("foo.txt"), 0); - QCOMPARE(FS::PathDepth("./foo.txt"), 0); - QCOMPARE(FS::PathDepth("./bar/foo.txt"), 1); - QCOMPARE(FS::PathDepth("../bar/foo.txt"), 0); - QCOMPARE(FS::PathDepth("/bar/foo.txt"), 1); - QCOMPARE(FS::PathDepth("baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("/baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("./baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("/baz/../bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth(""), 0); + QCOMPARE(FS::pathDepth("."), 0); + QCOMPARE(FS::pathDepth("foo.txt"), 0); + QCOMPARE(FS::pathDepth("./foo.txt"), 0); + QCOMPARE(FS::pathDepth("./bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth("../bar/foo.txt"), 0); + QCOMPARE(FS::pathDepth("/bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth("baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("/baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("./baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("/baz/../bar/foo.txt"), 1); } void test_path_trunc() { - QCOMPARE(FS::PathTruncate("", 0), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("foo.txt", 0), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("foo.txt", 1), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar")); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar")); - QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar")); - QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar")); - QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar")); + QCOMPARE(FS::pathTruncate("", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("foo.txt", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("foo.txt", 1), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::pathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::pathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar")); + QCOMPARE(FS::pathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar")); + QCOMPARE(FS::pathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar")); #if defined(Q_OS_WIN) - QCOMPARE(FS::PathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar")); + QCOMPARE(FS::pathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar")); #endif } }; -- cgit From 5b50b806ec5954aa3822443969d22ea79faa07c5 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:14:06 -0700 Subject: refactor: remove data duplication in statis FS Names Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 12 ++++----- launcher/FileSystem.cpp | 22 ++++++++-------- launcher/FileSystem.h | 70 +++++++++++++------------------------------------ 3 files changed, 35 insertions(+), 69 deletions(-) (limited to 'launcher/FileSystem.h') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b47f5746..03d3fcbf 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1123,17 +1123,17 @@ if(WIN32) add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(filelink_logic - systeminfo + # systeminfo BuildConfig - Qt${QT_VERSION_MAJOR}::Widgets + # Qt${QT_VERSION_MAJOR}::Widgets ghcFilesystem::ghc_filesystem ) target_link_libraries(filelink_logic Qt${QT_VERSION_MAJOR}::Core - Qt${QT_VERSION_MAJOR}::Xml - Qt${QT_VERSION_MAJOR}::Network - Qt${QT_VERSION_MAJOR}::Concurrent - ${Launcher_QT_LIBS} + # Qt${QT_VERSION_MAJOR}::Xml + # Qt${QT_VERSION_MAJOR}::Network + # Qt${QT_VERSION_MAJOR}::Concurrent + # ${Launcher_QT_LIBS} ) add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c046ee86..869fbe36 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -955,29 +955,29 @@ QString getFilesystemTypeName(FilesystemType type) { auto iter = s_filesystem_type_names.constFind(type); if (iter != s_filesystem_type_names.constEnd()) { - return iter.value(); + return iter.value().constFirst(); } return getFilesystemTypeName(FilesystemType::UNKNOWN); } FilesystemType getFilesystemTypeFuzzy(const QString& name) { - auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper()); - if (iter != s_filesystem_type_names_inverse.constEnd()) { - return iter.value(); + for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) { + auto fs_names = iter.value(); + for (auto fs_name : fs_names) { + if (name.toUpper().contains(fs_name.toUpper())) + return iter.key(); + } } return FilesystemType::UNKNOWN; } FilesystemType getFilesystemType(const QString& name) { - for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { - auto fs_type_name = fs_type_pair.first; - auto fs_type = fs_type_pair.second; - - if (name.toUpper().contains(fs_type_name.toUpper())) { - return fs_type; - } + for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) { + auto fs_names = iter.value(); + if(fs_names.contains(name.toUpper())) + return iter.key(); } return FilesystemType::UNKNOWN; } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 47044d93..cb581d0c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -360,58 +360,24 @@ enum class FilesystemType { * QMap is ordered * */ -static const QMap s_filesystem_type_names = { - {FilesystemType::FAT, QStringLiteral("FAT")}, - {FilesystemType::NTFS, QStringLiteral("NTFS")}, - {FilesystemType::REFS, QStringLiteral("REFS")}, - {FilesystemType::EXT, QStringLiteral("EXT")}, - {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")}, - {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")}, - {FilesystemType::XFS, QStringLiteral("XFS")}, - {FilesystemType::BTRFS, QStringLiteral("BTRFS")}, - {FilesystemType::NFS, QStringLiteral("NFS")}, - {FilesystemType::ZFS, QStringLiteral("ZFS")}, - {FilesystemType::APFS, QStringLiteral("APFS")}, - {FilesystemType::HFS, QStringLiteral("HFS")}, - {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")}, - {FilesystemType::HFSX, QStringLiteral("HFSX")}, - {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")}, - {FilesystemType::F2FS, QStringLiteral("F2FS")}, - {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")} -}; - - -/** - * @brief Ordered Mapping of reported filesystem names to enum types - * this mapping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. - * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup. - * - * QMap is ordered - * - */ -static const QMap s_filesystem_type_names_inverse = { - {QStringLiteral("FAT"), FilesystemType::FAT}, - {QStringLiteral("NTFS"), FilesystemType::NTFS}, - {QStringLiteral("REFS"), FilesystemType::REFS}, - {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD}, - {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, - {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection - {QStringLiteral("XFS"), FilesystemType::XFS}, - {QStringLiteral("BTRFS"), FilesystemType::BTRFS}, - {QStringLiteral("NFS"), FilesystemType::NFS}, - {QStringLiteral("ZFS"), FilesystemType::ZFS}, - {QStringLiteral("APFS"), FilesystemType::APFS}, - {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS}, - {QStringLiteral("HFSX"), FilesystemType::HFSX}, - {QStringLiteral("HFS"), FilesystemType::HFS}, - {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK}, - {QStringLiteral("F2FS"), FilesystemType::F2FS}, - {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN} +static const QMap s_filesystem_type_names = { + {FilesystemType::FAT, { "FAT" }}, + {FilesystemType::NTFS, { "NTFS" }}, + {FilesystemType::REFS, { "REFS" }}, + {FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" }}, + {FilesystemType::EXT_2_3_4, { "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" }}, + {FilesystemType::EXT, { "EXT" }}, + {FilesystemType::XFS, { "XFS" }}, + {FilesystemType::BTRFS, { "BTRFS" }}, + {FilesystemType::NFS, { "NFS" }}, + {FilesystemType::ZFS, { "ZFS" }}, + {FilesystemType::APFS, { "APFS" }}, + {FilesystemType::HFS, { "HFS" }}, + {FilesystemType::HFSPLUS, { "HFSPLUS" }}, + {FilesystemType::HFSX, { "HFSX" }}, + {FilesystemType::FUSEBLK, { "FUSEBLK" }}, + {FilesystemType::F2FS, { "F2FS" }}, + {FilesystemType::UNKNOWN, { "UNKNOWN" }} }; /** -- cgit