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: [PATCH] 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 | 223 +++++++++++++++++++++------------ launcher/FileSystem.h | 35 ++++-- launcher/filelink/FileLink.cpp | 136 ++++++++++++++++---- launcher/filelink/FileLink.h | 6 +- tests/FileSystem_test.cpp | 11 +- 5 files changed, 295 insertions(+), 116 deletions(-) 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; - - 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); + // 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); + 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() -{ - 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(); +{ + + 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); + } } - //exit(); - qDebug() << "done, should exit"; + + sendResults(); + qDebug() << "done, should exit soon"; + +} + +void FileLinkApp::sendResults() +{ + // 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)); + } + 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();