/* * Bittorrent Client using Qt and libtorrent. * Copyright (C) 2023 Mike Tzou (Chocobo1) * Copyright (C) 2014 sledgehammer999 * Copyright (C) 2006 Christophe Dumez * * 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; either version 2 * of the License, or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give permission to * link this program with the OpenSSL project's "OpenSSL" library (or with * modified versions of it that use the same license as the "OpenSSL" library), * and distribute the linked executables. You must obey the GNU General Public * License in all respects for all of the code used other than "OpenSSL". If you * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. */ #include "os.h" #ifdef Q_OS_WIN #include #include #include #include #endif // Q_OS_WIN #ifdef Q_OS_MACOS #include #include #include #endif // Q_OS_MACOS #include #ifdef QBT_USES_DBUS #include #endif // QBT_USES_DBUS #ifdef Q_OS_WIN #include #endif // Q_OS_WIN #include "base/global.h" #include "base/types.h" void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &action) { #if defined(Q_OS_WIN) HANDLE hToken; // handle to process token TOKEN_PRIVILEGES tkp; // pointer to token structure if (!::OpenProcessToken(::GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), &hToken)) return; // Get the LUID for shutdown privilege. ::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); tkp.PrivilegeCount = 1; // one privilege to set tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Get shutdown privilege for this process. ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0); // Cannot test the return value of AdjustTokenPrivileges. if (::GetLastError() != ERROR_SUCCESS) return; if (action == ShutdownDialogAction::Suspend) { ::SetSuspendState(FALSE, FALSE, FALSE); } else if (action == ShutdownDialogAction::Hibernate) { ::SetSuspendState(TRUE, FALSE, FALSE); } else if (action == ShutdownDialogAction::Shutdown) { std::wstring msg = QCoreApplication::translate("misc" , "qBittorrent will shutdown the computer now because all downloads are complete.").toStdWString(); ::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, FALSE); } else if (action == ShutdownDialogAction::Reboot) { std::wstring msg = QCoreApplication::translate("misc" , "qBittorrent will reboot the computer now because all downloads are complete.").toStdWString(); ::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, TRUE); } // Disable shutdown privilege. tkp.Privileges[0].Attributes = 0; ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0); #elif defined(Q_OS_MACOS) AEEventID EventToSend {}; if (action == ShutdownDialogAction::Suspend) EventToSend = kAESleep; else if (action == ShutdownDialogAction::Reboot) EventToSend = kAERestart; else if (action == ShutdownDialogAction::Shutdown) EventToSend = kAEShutDown; AEAddressDesc targetDesc; const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess}; AppleEvent eventReply = {typeNull, NULL}; AppleEvent appleEventToSend = {typeNull, NULL}; OSStatus error = ::AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess , sizeof(kPSNOfSystemProcess), &targetDesc); if (error != noErr) return; error = ::AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, kAutoGenerateReturnID , kAnyTransactionID, &appleEventToSend); AEDisposeDesc(&targetDesc); if (error != noErr) return; error = ::AESend(&appleEventToSend, &eventReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout , NULL, NULL); ::AEDisposeDesc(&appleEventToSend); if (error != noErr) return; ::AEDisposeDesc(&eventReply); #elif defined(QBT_USES_DBUS) // Use dbus to power off / suspend the system if ((action == ShutdownDialogAction::Suspend) || (action == ShutdownDialogAction::Hibernate)) { // Some recent systems use systemd's logind QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); if (login1Iface.isValid()) { if (action == ShutdownDialogAction::Suspend) login1Iface.call(u"Suspend"_s, false); else login1Iface.call(u"Hibernate"_s, false); return; } // Else, other recent systems use UPower QDBusInterface upowerIface(u"org.freedesktop.UPower"_s, u"/org/freedesktop/UPower"_s, u"org.freedesktop.UPower"_s, QDBusConnection::systemBus()); if (upowerIface.isValid()) { if (action == ShutdownDialogAction::Suspend) upowerIface.call(u"Suspend"_s); else upowerIface.call(u"Hibernate"_s); return; } // HAL (older systems) QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, QDBusConnection::systemBus()); if (action == ShutdownDialogAction::Suspend) halIface.call(u"Suspend"_s, 5); else halIface.call(u"Hibernate"_s); } else if (action == ShutdownDialogAction::Shutdown) { // Some recent systems use systemd's logind QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); if (login1Iface.isValid()) { login1Iface.call(u"PowerOff"_s, false); return; } // Else, other recent systems use ConsoleKit QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s, u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus()); if (consolekitIface.isValid()) { consolekitIface.call(u"Stop"_s); return; } // HAL (older systems) QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, QDBusConnection::systemBus()); halIface.call(u"Shutdown"_s); } else if (action == ShutdownDialogAction::Reboot) { // Some recent systems use systemd's logind QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); if (login1Iface.isValid()) { login1Iface.call(u"Reboot"_s, false); return; } // Else, other recent systems use ConsoleKit QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s, u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus()); if (consolekitIface.isValid()) { consolekitIface.call(u"Restart"_s); return; } // HAL (older systems) QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, QDBusConnection::systemBus()); halIface.call(u"Reboot"_s); } #endif } #ifdef Q_OS_WIN Path Utils::OS::windowsSystemPath() { static const Path path = []() -> Path { WCHAR systemPath[MAX_PATH] = {0}; ::GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR)); return Path(QString::fromWCharArray(systemPath)); }(); return path; } #endif // Q_OS_WIN #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) { // Trying to apply this to a non-existent file is unacceptable, // as it may unexpectedly create such a file. if (!file.exists()) return false; Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:")); #ifdef Q_OS_MACOS // References: // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230 // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955 const CFStringRef fileString = file.toString().toCFString(); [[maybe_unused]] const auto fileStringGuard = qScopeGuard([&fileString] { ::CFRelease(fileString); }); const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault , fileString, kCFURLPOSIXPathStyle, false); [[maybe_unused]] const auto fileURLGuard = qScopeGuard([&fileURL] { ::CFRelease(fileURL); }); if (CFDictionaryRef currentProperties = nullptr; ::CFURLCopyResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, ¤tProperties, NULL) && currentProperties) { [[maybe_unused]] const auto currentPropertiesGuard = qScopeGuard([¤tProperties] { ::CFRelease(currentProperties); }); if (CFStringRef quarantineType = nullptr; ::CFDictionaryGetValueIfPresent(currentProperties, kLSQuarantineTypeKey, reinterpret_cast(&quarantineType)) && quarantineType) { if (::CFStringCompare(quarantineType, kLSQuarantineTypeOtherDownload, 0) == kCFCompareEqualTo) return true; } } CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!properties) return false; [[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); }); ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload); if (!url.isEmpty()) { const CFStringRef urlCFString = url.toCFString(); [[maybe_unused]] const auto urlStringGuard = qScopeGuard([&urlCFString] { ::CFRelease(urlCFString); }); ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, urlCFString); } const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey , properties, NULL); return success; #elif defined(Q_OS_WIN) const QString zoneIDStream = file.toString() + u":Zone.Identifier"; HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), (GENERIC_READ | GENERIC_WRITE) , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE) , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) return false; [[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); }); // 5.6.1 Zone.Identifier Stream Name // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 const QString hostURL = !url.isEmpty() ? url : u"about:internet"_s; const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n") + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8(); if (LARGE_INTEGER streamSize {}; ::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0)) { const DWORD expectedReadSize = std::min(streamSize.QuadPart, 1024); QByteArray buf {static_cast(expectedReadSize), '\0'}; if (DWORD actualReadSize = 0; ::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize)) { if (buf.startsWith("[ZoneTransfer]\r\n") && buf.contains("\r\nZoneId=3\r\n") && buf.contains("\r\nHostUrl=")) return true; } } if (!::SetFilePointerEx(handle, {}, nullptr, FILE_BEGIN)) return false; if (!::SetEndOfFile(handle)) return false; DWORD written = 0; const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr); return writeResult && (static_cast(written) == zoneID.size()); #endif } #endif // Q_OS_MACOS || Q_OS_WIN