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/CMakeLists.txt | 52 ++++++++ launcher/FileSystem.cpp | 87 ++++++++++++- launcher/FileSystem.h | 68 +++++++++- launcher/InstanceCopyPrefs.cpp | 30 +++++ launcher/InstanceCopyPrefs.h | 9 ++ launcher/filelink/FileLink.cpp | 119 +++++++++++++++++ launcher/filelink/FileLink.h | 52 ++++++++ launcher/filelink/filelink.exe.manifest | 28 ++++ launcher/filelink/main.cpp | 31 +++++ launcher/ui/dialogs/CopyInstanceDialog.cpp | 19 +++ launcher/ui/dialogs/CopyInstanceDialog.h | 3 + launcher/ui/dialogs/CopyInstanceDialog.ui | 200 +++++++++++++++++++---------- 12 files changed, 627 insertions(+), 71 deletions(-) create mode 100644 launcher/filelink/FileLink.cpp create mode 100644 launcher/filelink/FileLink.h create mode 100644 launcher/filelink/filelink.exe.manifest create mode 100644 launcher/filelink/main.cpp (limited to 'launcher') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 074570e3..2216aa1b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -559,6 +559,11 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) +set(LINKDAEMON_SOURCES + filelink/FileLink.h + filelink/FileLink.cpp +) + ######## Logging categories ######## ecm_qt_declare_logging_category(CORE_SOURCES @@ -1107,6 +1112,53 @@ install(TARGETS ${Launcher_Name} FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) +if(WIN32) + add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES}) + target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(filelink_logic + systeminfo + BuildConfig + 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 + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + ${Launcher_QT_LIBS} + ) + + add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) + + target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) + + target_link_libraries("${Launcher_Name}_filelink" filelink_logic) + + if(DEFINED Launcher_APP_BINARY_NAME) + set_target_properties("${Launcher_Name}_filelink" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_filelink") + endif() + if(DEFINED Launcher_BINARY_RPATH) + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}") + endif() + + if(CMAKE_GENERATOR MATCHES "Visual Studio") + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE") + else() + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE") + endif() + + + install(TARGETS "${Launcher_Name}_filelink" + BUNDLE DESTINATION "." COMPONENT Runtime + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime + RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime + FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime + ) +endif() + if (UNIX AND APPLE) # Add Sparkle updater # It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index aee5245d..ec4af98c 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -152,9 +152,11 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } -/// @brief Copies 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 +/** + * @brief Copies 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 copy::operator()(const QString& offset, bool dryRun) { using copy_opts = fs::copy_options; @@ -215,6 +217,85 @@ bool copy::operator()(const QString& offset, bool dryRun) return err.value() == 0; } + +/** + * @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) +{ + m_linked = 0; // reset counter + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + std::error_code err; + + // 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), 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); + } else { + if (m_debug) + qDebug() << "making symlink:" << src_path << "to" << dst_path; + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + } + + } + if (err) { + qWarning() << "Failed to link files:" << QString::fromStdString(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); + } 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); + } + } + + return err.value() == 0; +} + bool move(const QString& source, const QString& dest) { std::error_code err; 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 diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 7b93a516..18a6d704 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -93,6 +93,21 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const return copyScreenshots; } +bool InstanceCopyPrefs::isLinkFilesEnabled() const +{ + return linkFiles; +} + +bool InstanceCopyPrefs::isUseHardLinksEnabled() const +{ + return useHardLinks; +} + +bool InstanceCopyPrefs::isLinkWorldsEnabled() const +{ + return linkWorlds; +} + // ======= Setters ======= void InstanceCopyPrefs::enableCopySaves(bool b) { @@ -133,3 +148,18 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b) { copyScreenshots = b; } + +void InstanceCopyPrefs::enableLinkFiles(bool b) +{ + linkFiles = b; +} + +void InstanceCopyPrefs::enableUseHardLinks(bool b) +{ + useHardLinks = b; +} + +void InstanceCopyPrefs::enableLinkWorlds(bool b) +{ + linkWorlds = b; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 6988b2df..25c0f3fc 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -19,6 +19,9 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const; + [[nodiscard]] bool isLinkFilesEnabled() const; + [[nodiscard]] bool isUseHardLinksEnabled() const; + [[nodiscard]] bool isLinkWorldsEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -28,6 +31,9 @@ struct InstanceCopyPrefs { void enableCopyServers(bool b); void enableCopyMods(bool b); void enableCopyScreenshots(bool b); + void enableLinkFiles(bool b); + void enableUseHardLinks(bool b); + void enableLinkWorlds(bool b); protected: // data bool copySaves = true; @@ -38,4 +44,7 @@ struct InstanceCopyPrefs { bool copyServers = true; bool copyMods = true; bool copyScreenshots = true; + bool linkFiles = false; + bool useHardLinks = false; + bool linkWorlds = true; }; diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp new file mode 100644 index 00000000..9b5589ab --- /dev/null +++ b/launcher/filelink/FileLink.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * 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 + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "FileLink.h" +#include "BuildConfig.h" + + +#include + +#include +#include + +#include + + +#include +#include + +#include + +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#endif + + + + +FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) +{ +#if defined Q_OS_WIN32 + // attach the parent console + if(AttachConsole(ATTACH_PARENT_PROCESS)) + { + // if attach succeeds, reopen and sync all the i/o + if(freopen("CON", "w", stdout)) + { + std::cout.sync_with_stdio(); + } + if(freopen("CON", "w", stderr)) + { + std::cerr.sync_with_stdio(); + } + if(freopen("CON", "r", stdin)) + { + std::cin.sync_with_stdio(); + } + auto out = GetStdHandle (STD_OUTPUT_HANDLE); + DWORD written; + const char * endline = "\n"; + WriteConsole(out, endline, strlen(endline), &written, NULL); + consoleAttached = true; + } +#endif + setOrganizationName(BuildConfig.LAUNCHER_NAME); + setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); + setApplicationName(BuildConfig.LAUNCHER_NAME + "FileLink"); + setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); + + // Commandline parsing + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher")); + + parser.addOptions({ + + }); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.process(arguments()); + + qDebug() << "link program launched"; + +} + + +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) + { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } +#endif +} + + + + diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h new file mode 100644 index 00000000..253d1394 --- /dev/null +++ b/launcher/filelink/FileLink.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * 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 + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class FileLinkApp : public QCoreApplication +{ + // friends for the purpose of limiting access to deprecated stuff + Q_OBJECT +public: + + FileLinkApp(int &argc, char **argv); + virtual ~FileLinkApp(); + +private: + QDateTime m_startTime; + +#if defined Q_OS_WIN32 + // used on Windows to attach the standard IO streams + bool consoleAttached = false; +#endif +}; diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest new file mode 100644 index 00000000..a4e16264 --- /dev/null +++ b/launcher/filelink/filelink.exe.manifest @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp new file mode 100644 index 00000000..7f06795e --- /dev/null +++ b/launcher/filelink/main.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * 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 + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "FileLink.h" + +int main(int argc, char *argv[]) +{ + + FileLinkApp ldh(argc, argv); + + return ldh.exec(); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 3f5122f6..981352ae 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -85,6 +85,10 @@ 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->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); + ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -220,3 +224,18 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) m_selectedOptions.enableCopyScreenshots(state == Qt::Checked); updateSelectAllCheckbox(); } + +void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) +{ + m_selectedOptions.enableLinkFiles(checked); +} + +void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseHardLinks(state == Qt::Checked); +} + +void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableLinkWorlds(state == Qt::Checked); +} diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 884501d1..a80faab9 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -55,6 +55,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_hardLinksCheckbox_stateChanged(int state); + void on_linkWorldsCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index b7828fe3..e41ad526 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 341 - 399 + 525 + 581 @@ -136,70 +136,126 @@ - - - - - Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. - - - Copy mods - - - - - + + + Instance copy options + + + + + + Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. + + + Copy mods + + + + + + + Copy the in-game options like FOV, max framerate, etc. + + + Copy game options + + + + + + + Copy saves + + + + + + + Copy shader packs + + + + + + + Copy servers + + + + + + + true + + + Copy resource packs + + + + + + + Keep play time + + + + + + + Copy screenshots + + + + + + + + + + - Copy the in-game options like FOV, max framerate, etc. + Use symbolic links instead of copying files. - - Copy game options + + Link files instead of copying them - - - - - - Copy saves - - - - - - - Copy shader packs - - - - - - - Copy servers + + false - - - - - + true - - Copy resource packs - - - - - - - Keep play time - - - - - - - Copy screenshots + + false + + + + + Use hard links instead of symbolic links + + + Use hard links + + + + + + + World save data will be linked and thus shared between instances. + + + Link worlds + + + true + + + false + + + + @@ -210,7 +266,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok @@ -220,10 +276,20 @@ iconButton instNameTextBox groupBox + selectAllCheckbox + keepPlaytimeCheckbox + copyScreenshotsCheckbox + copySavesCheckbox + copyShaderPacksCheckbox + copyGameOptionsCheckbox + copyServersCheckbox + copyResPacksCheckbox + copyModsCheckbox + linkFilesGroup + hardLinksCheckbox + linkWorldsCheckbox - - - + buttonBox @@ -232,8 +298,8 @@ accept() - 254 - 316 + 263 + 571 157 @@ -248,8 +314,8 @@ reject() - 322 - 316 + 331 + 571 286 -- cgit From 32409a361b797342d625bfc6d0726cc330ced760 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 7 Feb 2023 03:31:46 -0700 Subject: fix CMakeLits.txt for non MSVC windows builds Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'launcher') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2216aa1b..18d4ce0b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -559,7 +559,7 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) -set(LINKDAEMON_SOURCES +set(LINKEXE_SOURCES filelink/FileLink.h filelink/FileLink.cpp ) @@ -1113,7 +1113,7 @@ install(TARGETS ${Launcher_Name} ) if(WIN32) - add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES}) + add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(filelink_logic systeminfo @@ -1144,10 +1144,11 @@ if(WIN32) SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}") endif() + # may be unnessacery with manifest if(CMAKE_GENERATOR MATCHES "Visual Studio") SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE") - else() - SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE") + # else() # link arg /MANIFESTUAC only works with MSVC + # SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE") endif() -- 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 +++++ 8 files changed, 315 insertions(+), 31 deletions(-) (limited to 'launcher') 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 -- 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 +- 4 files changed, 285 insertions(+), 111 deletions(-) (limited to 'launcher') 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 + {