1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "appxlocalengine.h"
41 #include "appxengine_p.h"
42 
43 #include "utils.h"
44 
45 #include <QtCore/QDateTime>
46 #include <QtCore/QDir>
47 #include <QtCore/QDirIterator>
48 #include <QtCore/QFile>
49 #include <QtCore/QFileInfo>
50 #include <QtCore/QLoggingCategory>
51 #include <QtCore/QStandardPaths>
52 #include <QtCore/QOperatingSystemVersion>
53 
54 #include <ShlObj.h>
55 #include <Shlwapi.h>
56 #include <wsdevlicensing.h>
57 #include <AppxPackaging.h>
58 #include <wrl.h>
59 #include <windows.applicationmodel.h>
60 #include <windows.management.deployment.h>
61 
62 using namespace Microsoft::WRL;
63 using namespace Microsoft::WRL::Wrappers;
64 using namespace ABI::Windows::Foundation;
65 using namespace ABI::Windows::Management::Deployment;
66 using namespace ABI::Windows::ApplicationModel;
67 using namespace ABI::Windows::System;
68 
69 typedef IAsyncOperationWithProgressCompletedHandler<DeploymentResult *, DeploymentProgress> DeploymentResultHandler;
70 typedef IAsyncOperationWithProgress<DeploymentResult *, DeploymentProgress> DeploymentOperation;
71 
72 QT_USE_NAMESPACE
73 
74 // Set a break handler for gracefully breaking long-running ops
75 static bool g_ctrlReceived = false;
76 static bool g_handleCtrl = false;
ctrlHandler(DWORD type)77 static BOOL WINAPI ctrlHandler(DWORD type)
78 {
79     switch (type) {
80     case CTRL_C_EVENT:
81     case CTRL_CLOSE_EVENT:
82     case CTRL_LOGOFF_EVENT:
83         g_ctrlReceived = g_handleCtrl;
84         return g_handleCtrl;
85     case CTRL_BREAK_EVENT:
86     case CTRL_SHUTDOWN_EVENT:
87     default:
88         break;
89     }
90     return false;
91 }
92 
sidForPackage(const QString & packageFamilyName)93 QString sidForPackage(const QString &packageFamilyName)
94 {
95     QString sid;
96     HKEY regKey;
97     LONG result = RegOpenKeyEx(
98                 HKEY_CLASSES_ROOT,
99                 L"Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings",
100                 0, KEY_READ, &regKey);
101     if (result != ERROR_SUCCESS) {
102         qCWarning(lcWinRtRunner) << "Unable to open registry key:" << qt_error_string(result);
103         return sid;
104     }
105 
106     DWORD index = 0;
107     wchar_t subKey[MAX_PATH];
108     const wchar_t *packageFamilyNameW = wchar(packageFamilyName);
109     forever {
110         result = RegEnumKey(regKey, index++, subKey, MAX_PATH);
111         if (result != ERROR_SUCCESS)
112             break;
113         wchar_t moniker[MAX_PATH];
114         DWORD monikerSize = MAX_PATH;
115         result = RegGetValue(regKey, subKey, L"Moniker", RRF_RT_REG_SZ, NULL, moniker, &monikerSize);
116         if (result != ERROR_SUCCESS)
117             continue;
118         if (lstrcmp(moniker, packageFamilyNameW) == 0) {
119             sid = QString::fromWCharArray(subKey);
120             break;
121         }
122     }
123     RegCloseKey(regKey);
124     return sid;
125 }
126 
127 class OutputDebugMonitor
128 {
129 public:
OutputDebugMonitor()130     OutputDebugMonitor()
131         : runLock(CreateEvent(NULL, FALSE, FALSE, NULL)), thread(0)
132     {
133     }
~OutputDebugMonitor()134     ~OutputDebugMonitor()
135     {
136         if (runLock) {
137             SetEvent(runLock);
138             CloseHandle(runLock);
139         }
140         if (thread) {
141             WaitForSingleObject(thread, INFINITE);
142             CloseHandle(thread);
143         }
144     }
start(const QString & packageFamilyName)145     void start(const QString &packageFamilyName)
146     {
147         if (thread) {
148             qCWarning(lcWinRtRunner) << "OutputDebugMonitor is already running.";
149             return;
150         }
151 
152         package = packageFamilyName;
153 
154         thread = CreateThread(NULL, 0, &monitor, this, NULL, NULL);
155         if (!thread) {
156             qCWarning(lcWinRtRunner) << "Unable to create thread for app debugging:"
157                                      << qt_error_string(GetLastError());
158             return;
159         }
160 
161         return;
162     }
163 private:
monitor(LPVOID param)164     static DWORD __stdcall monitor(LPVOID param)
165     {
166         OutputDebugMonitor *that = static_cast<OutputDebugMonitor *>(param);
167 
168         const QString handleBase = QStringLiteral("Local\\AppContainerNamedObjects\\")
169                 + sidForPackage(that->package);
170         const QString eventName = handleBase + QStringLiteral("\\qdebug-event");
171         const QString ackEventName = handleBase + QStringLiteral("\\qdebug-event-ack");
172         const QString shmemName = handleBase + QStringLiteral("\\qdebug-shmem");
173 
174         HANDLE event = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast<LPCWSTR>(eventName.utf16()));
175         if (!event) {
176             qCWarning(lcWinRtRunner) << "Unable to open shared event for app debugging:"
177                                      << qt_error_string(GetLastError());
178             return 1;
179         }
180 
181         HANDLE ackEvent = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast<LPCWSTR>(ackEventName.utf16()));
182         if (!ackEvent) {
183             qCWarning(lcWinRtRunner) << "Unable to open shared acknowledge event for app debugging:"
184                                      << qt_error_string(GetLastError());
185             return 1;
186         }
187 
188         HANDLE shmem = 0;
189         DWORD ret = 0;
190         quint64 size = 4096;
191         const quint32 resizeMessageType = QtInfoMsg + 1;
192         HANDLE handles[] = { that->runLock, event };
193         forever {
194             DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
195 
196             // runLock set; exit thread
197             if (result == WAIT_OBJECT_0)
198                 break;
199 
200             if (result == WAIT_OBJECT_0 + 1) {
201                 if (!shmem) {
202                     shmem = OpenFileMapping(GENERIC_READ, FALSE,
203                                             reinterpret_cast<LPCWSTR>(shmemName.utf16()));
204                     if (!shmem) {
205                         qCWarning(lcWinRtRunner) << "Unable to open shared memory for app debugging:"
206                                                  << qt_error_string(GetLastError());
207                         ret = 1;
208                         break;
209                     }
210                 }
211 
212                 const quint32 *data = reinterpret_cast<const quint32 *>(
213                             MapViewOfFile(shmem, FILE_MAP_READ, 0, 0, size));
214                 if (!data) {
215                     qCWarning(lcWinRtRunner) << "Unable to map view of shared memory for app debugging:"
216                                              << qt_error_string(GetLastError());
217                     ret = 1;
218                     break;
219                 }
220                 const quint32 type = data[0];
221                 // resize message received; Resize shared memory
222                 if (type == resizeMessageType) {
223                     size = (data[2] << 8) + data[1];
224                     if (!UnmapViewOfFile(data)) {
225                         qCWarning(lcWinRtRunner) << "Unable to unmap view of shared memory for app debugging:"
226                                                  << qt_error_string(GetLastError());
227                         ret = 1;
228                         break;
229                     }
230                     if (shmem) {
231                         if (!CloseHandle(shmem)) {
232                             qCWarning(lcWinRtRunner) << "Unable to close shared memory handle:"
233                                                      << qt_error_string(GetLastError());
234                             ret = 1;
235                             break;
236                         }
237                         shmem = 0;
238                     }
239                     SetEvent(ackEvent);
240                     continue;
241                 }
242 
243                 // debug event set; print message
244                 QtMsgType messageType = static_cast<QtMsgType>(type);
245                 QString message = QString::fromWCharArray(
246                             reinterpret_cast<const wchar_t *>(data + 1));
247                 UnmapViewOfFile(data);
248                 SetEvent(ackEvent);
249                 switch (messageType) {
250                 default:
251                 case QtDebugMsg:
252                     qCDebug(lcWinRtRunnerApp, qPrintable(message));
253                     break;
254                 case QtWarningMsg:
255                     qCWarning(lcWinRtRunnerApp, qPrintable(message));
256                     break;
257                 case QtCriticalMsg:
258                 case QtFatalMsg:
259                     qCCritical(lcWinRtRunnerApp, qPrintable(message));
260                     break;
261                 }
262                 continue;
263             }
264 
265             // An error occurred; exit thread
266             qCWarning(lcWinRtRunner) << "Debug output monitor error:"
267                                      << qt_error_string(GetLastError());
268             ret = 1;
269             break;
270         }
271         if (shmem)
272             CloseHandle(shmem);
273         if (event)
274             CloseHandle(event);
275         if (ackEvent)
276             CloseHandle(ackEvent);
277         return ret;
278     }
279     HANDLE runLock;
280     HANDLE thread;
281     QString package;
282 };
283 Q_GLOBAL_STATIC(OutputDebugMonitor, debugMonitor)
284 
285 class AppxLocalEnginePrivate : public AppxEnginePrivate
286 {
287 public:
288     ComPtr<IPackageManager> packageManager;
289     ComPtr<IApplicationActivationManager> appLauncher;
290     ComPtr<IPackageDebugSettings> packageDebug;
291 
292     Qt::HANDLE loopbackServerProcessHandle = INVALID_HANDLE_VALUE;
293 
294     void retrieveInstalledPackages();
295 };
296 
getManifestFile(const QString & fileName,QString * manifest=0)297 static bool getManifestFile(const QString &fileName, QString *manifest = 0)
298 {
299     if (!QFile::exists(fileName)) {
300         qCWarning(lcWinRtRunner) << fileName << "does not exist.";
301         return false;
302     }
303 
304     // If it looks like an appx manifest, we're done
305     if (fileName.endsWith(QStringLiteral("AppxManifest.xml"))) {
306 
307         if (manifest)
308             *manifest = fileName;
309         return true;
310     }
311 
312     // If it looks like an executable, check that manifest is next to it
313     if (fileName.endsWith(QStringLiteral(".exe"))) {
314         QDir appDir = QFileInfo(fileName).absoluteDir();
315         QString manifestFileName = appDir.absoluteFilePath(QStringLiteral("AppxManifest.xml"));
316         if (!QFile::exists(manifestFileName)) {
317             qCWarning(lcWinRtRunner) << manifestFileName << "does not exist.";
318             return false;
319         }
320 
321         if (manifest)
322             *manifest = manifestFileName;
323         return true;
324     }
325 
326     // TODO: handle already-built package as well
327 
328     qCWarning(lcWinRtRunner) << "Appx: unable to determine manifest for" << fileName << ".";
329     return false;
330 }
331 
canHandle(Runner * runner)332 bool AppxLocalEngine::canHandle(Runner *runner)
333 {
334     return getManifestFile(runner->app());
335 }
336 
create(Runner * runner)337 RunnerEngine *AppxLocalEngine::create(Runner *runner)
338 {
339     QScopedPointer<AppxLocalEngine> engine(new AppxLocalEngine(runner));
340     if (engine->d_ptr->hasFatalError)
341         return 0;
342 
343     return engine.take();
344 }
345 
deviceNames()346 QStringList AppxLocalEngine::deviceNames()
347 {
348     qCDebug(lcWinRtRunner) << __FUNCTION__;
349     return QStringList(QStringLiteral("local"));
350 }
351 
352 
353 
toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch)354 static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch)
355 {
356     switch (appxArch) {
357     case APPX_PACKAGE_ARCHITECTURE_X86:
358         return ProcessorArchitecture_X86;
359     case APPX_PACKAGE_ARCHITECTURE_ARM:
360         return ProcessorArchitecture_Arm;
361     case APPX_PACKAGE_ARCHITECTURE_X64:
362         return ProcessorArchitecture_X64;
363     case APPX_PACKAGE_ARCHITECTURE_NEUTRAL:
364         // fall-through intended
365     default:
366         return ProcessorArchitecture_Neutral;
367     }
368 }
369 
AppxLocalEngine(Runner * runner)370 AppxLocalEngine::AppxLocalEngine(Runner *runner)
371     : AppxEngine(runner, new AppxLocalEnginePrivate)
372 {
373     Q_D(AppxLocalEngine);
374     if (d->hasFatalError)
375         return;
376     d->hasFatalError = true;
377 
378     HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(),
379                                     &d->packageManager);
380     RETURN_VOID_IF_FAILED("Failed to instantiate package manager");
381 
382     hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER,
383                           IID_IApplicationActivationManager, &d->appLauncher);
384     RETURN_VOID_IF_FAILED("Failed to instantiate application activation manager");
385 
386     hr = CoCreateInstance(CLSID_PackageDebugSettings, nullptr, CLSCTX_INPROC_SERVER,
387                           IID_IPackageDebugSettings, &d->packageDebug);
388     RETURN_VOID_IF_FAILED("Failed to instantiate package debug settings");
389 
390     d->retrieveInstalledPackages();
391 
392     // Set a break handler for gracefully exiting from long-running operations
393     SetConsoleCtrlHandler(&ctrlHandler, true);
394     d->hasFatalError = false;
395 }
396 
~AppxLocalEngine()397 AppxLocalEngine::~AppxLocalEngine()
398 {
399 }
400 
installPackage(IAppxManifestReader * reader,const QString & filePath)401 bool AppxLocalEngine::installPackage(IAppxManifestReader *reader, const QString &filePath)
402 {
403     Q_D(const AppxLocalEngine);
404     qCDebug(lcWinRtRunner).nospace().noquote()
405         << __FUNCTION__ << " \"" << QDir::toNativeSeparators(filePath) << '"';
406 
407     HRESULT hr;
408     if (reader) {
409         ComPtr<IAppxManifestPackageId> packageId;
410         hr = reader->GetPackageId(&packageId);
411         RETURN_FALSE_IF_FAILED("Failed to get package ID from reader.");
412 
413         LPWSTR name;
414         hr = packageId->GetName(&name);
415         RETURN_FALSE_IF_FAILED("Failed to get package name from package ID.");
416 
417         const QString packageName = QString::fromWCharArray(name);
418         CoTaskMemFree(name);
419         if (d->installedPackages.contains(packageName)) {
420             qCDebug(lcWinRtRunner) << "The package" << packageName << "is already installed.";
421             return true;
422         }
423     }
424 
425     const QString nativeFilePath = QDir::toNativeSeparators(QFileInfo(filePath).absoluteFilePath());
426     const bool addInsteadOfRegister = nativeFilePath.endsWith(QStringLiteral(".appx"),
427                                                               Qt::CaseInsensitive);
428     ComPtr<IUriRuntimeClass> uri;
429     hr = d->uriFactory->CreateUri(hStringFromQString(nativeFilePath), &uri);
430     RETURN_FALSE_IF_FAILED("Failed to create an URI for the package");
431 
432     ComPtr<DeploymentOperation> deploymentOperation;
433     if (addInsteadOfRegister) {
434         hr = d->packageManager->AddPackageAsync(uri.Get(), NULL, DeploymentOptions_None,
435                                                 &deploymentOperation);
436         RETURN_FALSE_IF_FAILED("Failed to add package");
437     } else {
438         hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0,
439                                                      DeploymentOptions_DevelopmentMode,
440                                                      &deploymentOperation);
441         RETURN_FALSE_IF_FAILED("Failed to start package registration");
442     }
443 
444     HANDLE ev = CreateEvent(NULL, FALSE, FALSE, NULL);
445     hr = deploymentOperation->put_Completed(Callback<DeploymentResultHandler>([ev](DeploymentOperation *, AsyncStatus) {
446                                         SetEvent(ev);
447                                         return S_OK;
448                                     }).Get());
449     RETURN_FALSE_IF_FAILED("Could not register deployment completed callback.");
450     DWORD ret = WaitForSingleObjectEx(ev, 60000, FALSE);
451     CloseHandle(ev);
452     if (ret != WAIT_OBJECT_0) {
453         if (ret == WAIT_TIMEOUT)
454             qCWarning(lcWinRtRunner) << "Deployment did not finish within 15 seconds.";
455         else
456             qCWarning(lcWinRtRunner) << "Deployment finished event was not triggered.";
457         return false;
458     }
459 
460     ComPtr<IDeploymentResult> results;
461     hr = deploymentOperation->GetResults(&results);
462     RETURN_FALSE_IF_FAILED("Failed to retrieve package registration results.");
463 
464     HRESULT errorCode;
465     hr = results->get_ExtendedErrorCode(&errorCode);
466     RETURN_FALSE_IF_FAILED("Failed to retrieve extended error code.");
467 
468     if (FAILED(errorCode)) {
469         if (HRESULT_CODE(errorCode) == ERROR_INSTALL_PREREQUISITE_FAILED) {
470             qCWarning(lcWinRtRunner) << "Unable to register package: A requirement for installation was not met. "
471                 "Check that your Windows version matches TargetDeviceFamily's MinVersion set in your AppxManifest.xml.";
472         } else {
473             HString errorText;
474             if (SUCCEEDED(results->get_ErrorText(errorText.GetAddressOf()))) {
475                 qCWarning(lcWinRtRunner) << "Unable to register package:"
476                     << QString::fromWCharArray(errorText.GetRawBuffer(NULL));
477             }
478         }
479         if (HRESULT_CODE(errorCode) == ERROR_INSTALL_POLICY_FAILURE) {
480             // The user's license has expired. Give them the opportunity to renew it.
481             FILETIME expiration;
482             hr = AcquireDeveloperLicense(GetForegroundWindow(), &expiration);
483             RETURN_FALSE_IF_FAILED("Unable to renew developer license");
484             return install(false);
485         }
486         return false;
487     }
488 
489     return SUCCEEDED(hr);
490 }
491 
parseExitCode()492 bool AppxLocalEngine::parseExitCode()
493 {
494     Q_D(AppxLocalEngine);
495     const QString exitFileName(QStringLiteral("exitCode.tmp"));
496     bool ok = false;
497     QFile exitCodeFile(devicePath(QString::number(pid()).append(QStringLiteral(".pid"))));
498     if (exitCodeFile.open(QIODevice::ReadOnly)) {
499         d->exitCode = exitCodeFile.readAll().toInt(&ok);
500         exitCodeFile.close();
501         exitCodeFile.remove();
502     }
503     if (!ok && !GetExitCodeProcess(d->processHandle, &d->exitCode)) {
504         d->exitCode = UINT_MAX;
505         qCWarning(lcWinRtRunner).nospace() << "Failed to obtain process exit code.";
506         qCDebug(lcWinRtRunner, "GetLastError: 0x%x", GetLastError());
507         return false;
508     }
509     return true;
510 }
511 
install(bool removeFirst)512 bool AppxLocalEngine::install(bool removeFirst)
513 {
514     Q_D(const AppxLocalEngine);
515     qCDebug(lcWinRtRunner) << __FUNCTION__;
516 
517     ComPtr<IPackage> packageInformation;
518     HRESULT hr = d->packageManager->FindPackageByUserSecurityIdPackageFullName(
519                 NULL, hStringFromQString(d->packageFullName), &packageInformation);
520     if (SUCCEEDED(hr) && packageInformation) {
521         qCWarning(lcWinRtRunner) << "Package already installed.";
522         if (removeFirst)
523             remove();
524         else
525             return true;
526     }
527 
528     return installDependencies() && installPackage(nullptr, d->manifest);
529 }
530 
remove()531 bool AppxLocalEngine::remove()
532 {
533     Q_D(const AppxLocalEngine);
534     qCDebug(lcWinRtRunner) << __FUNCTION__;
535 
536     // ### TODO: use RemovePackageWithOptions to preserve previous state when re-installing
537     ComPtr<DeploymentOperation> deploymentOperation;
538     HRESULT hr = d->packageManager->RemovePackageAsync(hStringFromQString(d->packageFullName), &deploymentOperation);
539     RETURN_FALSE_IF_FAILED("Unable to start package removal");
540 
541     HANDLE ev = CreateEvent(NULL, FALSE, FALSE, NULL);
542     hr = deploymentOperation->put_Completed(Callback<DeploymentResultHandler>([ev](DeploymentOperation *, AsyncStatus) {
543                                         SetEvent(ev);
544                                         return S_OK;
545                                     }).Get());
546     RETURN_FALSE_IF_FAILED("Could not register deployment completed callback.");
547     DWORD ret = WaitForSingleObjectEx(ev, 60000, FALSE);
548     CloseHandle(ev);
549     if (ret != WAIT_OBJECT_0) {
550         if (ret == WAIT_TIMEOUT)
551             qCWarning(lcWinRtRunner) << "Deployment did not finish within 15 seconds.";
552         else
553             qCWarning(lcWinRtRunner) << "Deployment finished event was not triggered.";
554         return false;
555     }
556 
557     ComPtr<IAsyncInfo> asyncInfo;
558     hr = deploymentOperation.As(&asyncInfo);
559     RETURN_FALSE_IF_FAILED("Failed to cast deployment operation.");
560 
561     AsyncStatus status;
562     hr = asyncInfo->get_Status(&status);
563     RETURN_FALSE_IF_FAILED("Failed to retrieve deployment operation's status.");
564 
565     if (status != Completed) {
566         qCWarning(lcWinRtRunner) << "Unable to remove package.";
567         return false;
568     }
569 
570     return true;
571 }
572 
start()573 bool AppxLocalEngine::start()
574 {
575     Q_D(AppxLocalEngine);
576     qCDebug(lcWinRtRunner) << __FUNCTION__;
577 
578     const QString launchArguments =
579             (d->runner->arguments() << QStringLiteral("-qdevel")).join(QLatin1Char(' '));
580     DWORD pid;
581     const QString activationId = d->packageFamilyName + QStringLiteral("!App");
582     HRESULT hr = d->appLauncher->ActivateApplication(wchar(activationId),
583                                                      wchar(launchArguments), AO_NONE, &pid);
584     RETURN_FALSE_IF_FAILED("Failed to activate application");
585     d->pid = qint64(pid);
586     CloseHandle(d->processHandle);
587     d->processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, pid);
588 
589     return true;
590 }
591 
enableDebugging(const QString & debuggerExecutable,const QString & debuggerArguments)592 bool AppxLocalEngine::enableDebugging(const QString &debuggerExecutable, const QString &debuggerArguments)
593 {
594     Q_D(AppxLocalEngine);
595 
596     const QString &debuggerCommand = debuggerExecutable + QLatin1Char(' ') + debuggerArguments;
597     HRESULT hr = d->packageDebug->EnableDebugging(wchar(d->packageFullName),
598                                                   wchar(debuggerCommand),
599                                                   NULL);
600     RETURN_FALSE_IF_FAILED("Failed to enable debugging for application");
601     return true;
602 }
603 
disableDebugging()604 bool AppxLocalEngine::disableDebugging()
605 {
606     Q_D(AppxLocalEngine);
607 
608     HRESULT hr = d->packageDebug->DisableDebugging(wchar(d->packageFullName));
609     RETURN_FALSE_IF_FAILED("Failed to disable debugging for application");
610 
611     return true;
612 }
613 
setLoopbackExemptClientEnabled(bool enabled)614 bool AppxLocalEngine::setLoopbackExemptClientEnabled(bool enabled)
615 {
616     Q_D(AppxLocalEngine);
617     qCDebug(lcWinRtRunner) << __FUNCTION__ << enabled;
618 
619     if (!enabled) {
620         PACKAGE_EXECUTION_STATE state;
621         HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state);
622         RETURN_FALSE_IF_FAILED("Failed to get package execution state");
623         if (state != PES_TERMINATED && state != PES_UNKNOWN) {
624             qCWarning(lcWinRtRunner) << "Cannot unregister loopback exemption for running program."
625                                      << "Please use checknetisolation.exe to check/clean up the exemption list.";
626             return false;
627         }
628     }
629 
630     QByteArray stdOut;
631     QByteArray stdErr;
632     unsigned long exitCode = 0;
633     QString errorMessage;
634     QStringList arguments;
635     const QString binary = QStringLiteral("checknetisolation.exe");
636     arguments << QStringLiteral("LoopbackExempt")
637               << (enabled ? QStringLiteral("-a") : QStringLiteral("-d"))
638               << QStringLiteral("-p=") + sidForPackage(d->packageFamilyName);
639     if (!runProcess(binary, arguments, QString(), &exitCode, &stdOut, &stdErr, &errorMessage)) {
640         qCWarning(lcWinRtRunner) << "Could not run" << binary;
641         return false;
642     }
643     if (exitCode) {
644         if (errorMessage.isEmpty()) {
645             errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode)
646                         + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
647         }
648         qCWarning(lcWinRtRunner) << errorMessage;
649         return false;
650     }
651     return true;
652 }
653 
setLoopbackExemptServerEnabled(bool enabled)654 bool AppxLocalEngine::setLoopbackExemptServerEnabled(bool enabled)
655 {
656     Q_D(AppxLocalEngine);
657     const QOperatingSystemVersion minimal
658         = QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393);
659     if (QOperatingSystemVersion::current() < minimal) {
660         qCWarning(lcWinRtRunner) << "Cannot enable loopback exemption for servers. If you want"
661                                  << "to use this feature please update to a Windows version >="
662                                  << minimal;
663         return false;
664     }
665 
666     if (enabled) {
667         QStringList arguments;
668         const QString binary = QStringLiteral("checknetisolation.exe");
669         arguments << QStringLiteral("LoopbackExempt") << QStringLiteral("-is")
670                   << QStringLiteral("-p=") + sidForPackage(d->packageFamilyName);
671         if (!runElevatedBackgroundProcess(binary, arguments, &d->loopbackServerProcessHandle)) {
672             qCWarning(lcWinRtRunner) << "Could not start" << binary;
673             return false;
674         }
675     } else {
676         if (d->loopbackServerProcessHandle != INVALID_HANDLE_VALUE) {
677             if (!TerminateProcess(d->loopbackServerProcessHandle, 0)) {
678                 qCWarning(lcWinRtRunner) << "Could not terminate loopbackexempt debug session";
679                 return false;
680             }
681             if (!CloseHandle(d->loopbackServerProcessHandle)) {
682                 qCWarning(lcWinRtRunner) << "Could not close loopbackexempt debug session process handle";
683                 return false;
684             }
685         } else {
686             qCWarning(lcWinRtRunner) << "loopbackexempt debug session could not be found";
687             return false;
688         }
689     }
690     return true;
691 }
692 
setLoggingRules(const QByteArray & rules)693 bool AppxLocalEngine::setLoggingRules(const QByteArray &rules)
694 {
695     qCDebug(lcWinRtRunner) << __FUNCTION__;
696 
697     QDir loggingIniDir(devicePath(QLatin1String("QtProject")));
698     if (!loggingIniDir.exists() && !loggingIniDir.mkpath(QStringLiteral("."))) {
699         qCWarning(lcWinRtRunner) << "Could not create" << loggingIniDir;
700         return false;
701     }
702     QFile loggingIniFile(loggingIniDir.absolutePath().append(QLatin1String("/qtlogging.ini")));
703     if (loggingIniFile.exists() && !loggingIniFile.remove()) {
704         qCWarning(lcWinRtRunner) << loggingIniFile << "already exists.";
705         return false;
706     }
707     if (!loggingIniFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
708         qCWarning(lcWinRtRunner) << "Could not open" << loggingIniFile << "for writing.";
709         return false;
710     }
711 
712     QTextStream stream(&loggingIniFile);
713     stream << "[Rules]\n" << rules;
714 
715     return true;
716 }
717 
718 
suspend()719 bool AppxLocalEngine::suspend()
720 {
721     Q_D(AppxLocalEngine);
722     qCDebug(lcWinRtRunner) << __FUNCTION__;
723 
724     HRESULT hr = d->packageDebug->Suspend(wchar(d->packageFullName));
725     RETURN_FALSE_IF_FAILED("Failed to suspend application");
726 
727     return true;
728 }
729 
waitForFinished(int secs)730 bool AppxLocalEngine::waitForFinished(int secs)
731 {
732     Q_D(AppxLocalEngine);
733     qCDebug(lcWinRtRunner) << __FUNCTION__;
734 
735     debugMonitor->start(d->packageFamilyName);
736 
737     g_handleCtrl = true;
738     int time = 0;
739     forever {
740         PACKAGE_EXECUTION_STATE state;
741         HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state);
742         RETURN_FALSE_IF_FAILED("Failed to get package execution state");
743         qCDebug(lcWinRtRunner) << "Current execution state:" << state;
744         if (state == PES_TERMINATED || state == PES_UNKNOWN)
745             break;
746 
747         ++time;
748         if ((secs && time > secs) || g_ctrlReceived) {
749             g_handleCtrl = false;
750             return false;
751         }
752 
753         Sleep(1000); // Wait one second between checks
754         qCDebug(lcWinRtRunner) << "Waiting for app to quit - msecs to go:" << secs - time;
755     }
756     g_handleCtrl = false;
757 
758     if (!GetExitCodeProcess(d->processHandle, &d->exitCode))
759         d->exitCode = UINT_MAX;
760 
761     return true;
762 }
763 
stop()764 bool AppxLocalEngine::stop()
765 {
766     Q_D(AppxLocalEngine);
767     qCDebug(lcWinRtRunner) << __FUNCTION__;
768 
769     // ### We won't have a process handle if we didn't start the app. We can look it up
770     // using a process snapshot, or by calling start if we know the process is already running.
771     // For now, simply continue normally, but don't fetch the exit code.
772     if (!d->processHandle)
773         qCDebug(lcWinRtRunner) << "No handle to the process; the exit code won't be available.";
774 
775     if (d->processHandle && !parseExitCode()) {
776         return false;
777     }
778 
779     if (!d->processHandle || d->exitCode == STILL_ACTIVE) {
780         HRESULT hr = d->packageDebug->TerminateAllProcesses(wchar(d->packageFullName));
781         RETURN_FALSE_IF_FAILED("Failed to terminate package process");
782 
783         if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode))
784             d->exitCode = UINT_MAX;
785     }
786 
787     return true;
788 }
789 
devicePath(const QString & relativePath) const790 QString AppxLocalEngine::devicePath(const QString &relativePath) const
791 {
792     Q_D(const AppxLocalEngine);
793     qCDebug(lcWinRtRunner) << __FUNCTION__;
794 
795     // Return a path safe for passing to the application
796     QDir localAppDataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
797     const QString path = localAppDataPath.absoluteFilePath(
798                 QStringLiteral("Packages/") + d->packageFamilyName
799                 + QStringLiteral("/LocalState/") + relativePath);
800     return QDir::toNativeSeparators(path);
801 }
802 
sendFile(const QString & localFile,const QString & deviceFile)803 bool AppxLocalEngine::sendFile(const QString &localFile, const QString &deviceFile)
804 {
805     qCDebug(lcWinRtRunner) << __FUNCTION__;
806 
807     // Both files are local, just use QFile
808     QFile source(localFile);
809 
810     // Remove the destination, or copy will fail
811     if (QFileInfo(source) != QFileInfo(deviceFile))
812         QFile::remove(deviceFile);
813 
814     bool result = source.copy(deviceFile);
815     if (!result) {
816         qCWarning(lcWinRtRunner).nospace().noquote()
817             << "Unable to sendFile: " << source.errorString();
818     }
819 
820     return result;
821 }
822 
receiveFile(const QString & deviceFile,const QString & localFile)823 bool AppxLocalEngine::receiveFile(const QString &deviceFile, const QString &localFile)
824 {
825     qCDebug(lcWinRtRunner) << __FUNCTION__;
826 
827     // Both files are local, so just reverse the sendFile arguments
828     return sendFile(deviceFile, localFile);
829 }
830 
extensionSdkPath() const831 QString AppxLocalEngine::extensionSdkPath() const
832 {
833     const QByteArray extensionSdkDirRaw = qgetenv("ExtensionSdkDir");
834     if (extensionSdkDirRaw.isEmpty()) {
835         qCWarning(lcWinRtRunner) << "The environment variable ExtensionSdkDir is not set.";
836         return QString();
837     }
838     return QString::fromLocal8Bit(extensionSdkDirRaw);
839 }
840 
retrieveInstalledPackages()841 void AppxLocalEnginePrivate::retrieveInstalledPackages()
842 {
843     qCDebug(lcWinRtRunner) << __FUNCTION__;
844 
845     ComPtr<ABI::Windows::Foundation::Collections::IIterable<Package*>> packages;
846     HRESULT hr = packageManager->FindPackagesByUserSecurityId(NULL, &packages);
847     RETURN_VOID_IF_FAILED("Failed to find packages");
848 
849     ComPtr<ABI::Windows::Foundation::Collections::IIterator<Package*>> pkgit;
850     hr = packages->First(&pkgit);
851     RETURN_VOID_IF_FAILED("Failed to get package iterator");
852 
853     boolean hasCurrent;
854     hr = pkgit->get_HasCurrent(&hasCurrent);
855     while (SUCCEEDED(hr) && hasCurrent) {
856         ComPtr<IPackage> pkg;
857         hr = pkgit->get_Current(&pkg);
858         RETURN_VOID_IF_FAILED("Failed to get current package");
859 
860         ComPtr<IPackageId> pkgId;
861         hr = pkg->get_Id(&pkgId);
862         RETURN_VOID_IF_FAILED("Failed to get package id");
863 
864         HString name;
865         hr = pkgId->get_Name(name.GetAddressOf());
866         RETURN_VOID_IF_FAILED("Failed retrieve package name");
867 
868         ProcessorArchitecture architecture;
869         if (packageArchitecture == ProcessorArchitecture_Neutral) {
870             architecture = packageArchitecture;
871         } else {
872             hr = pkgId->get_Architecture(&architecture);
873             RETURN_VOID_IF_FAILED("Failed to retrieve package architecture");
874         }
875 
876         const QString pkgName = QStringFromHString(name.Get());
877         qCDebug(lcWinRtRunner) << "found installed package" << pkgName;
878 
879         if (architecture == packageArchitecture)
880             installedPackages.insert(pkgName);
881 
882         hr = pkgit->MoveNext(&hasCurrent);
883     }
884 }
885