This repository has been archived on 2022-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Luma3DS-3GX/sysmodules/pm/source/termination.c

354 lines
11 KiB
C

#include <3ds.h>
#include "termination.h"
#include "info.h"
#include "manager.h"
#include "util.h"
#include "exheader_info_heap.h"
#include "task_runner.h"
static Result terminateUnusedDependencies(const u64 *dependencies, u32 numDeps)
{
ProcessData *process;
Result res = 0;
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->terminationStatus != TERMSTATUS_RUNNING || !(process->flags & PROCESSFLAG_AUTOLOADED)) {
continue;
}
u32 i;
for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++);
if (i >= numDeps || --process->refcount > 0) {
// Process not a listed dependency or still used
continue;
}
res = ProcessData_SendTerminationNotification(process);
res = R_SUMMARY(res) == RS_NOTFOUND ? 0 : res;
if (R_FAILED(res)) {
assertSuccess(svcTerminateProcess(process->handle));
}
}
ProcessList_Unlock(&g_manager.processList);
return res;
}
Result listAndTerminateDependencies(ProcessData *process, ExHeader_Info *exheaderInfo)
{
Result res = 0;
u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space
u32 numDeps = 0;
TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo));
return terminateUnusedDependencies(dependencies, numDeps);
}
static Result terminateProcessImpl(ProcessData *process, ExHeader_Info *exheaderInfo)
{
// NOTE: list dependencies BEFORE sending the notification -- race condition material
Result res = 0;
u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space
u32 numDeps = 0;
if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) {
TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo));
process->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_SendTerminationNotification(process);
return terminateUnusedDependencies(dependencies, numDeps);
} else {
ProcessData_SendTerminationNotification(process);
return 0;
}
}
static Result commitPendingTerminations(s64 timeout)
{
// Wait for all of the processes that have received notification 0x100 to terminate
// till the timeout, then actually terminate these processes, etc.
Result res = 0;
bool atLeastOneListener = false;
ProcessList_Lock(&g_manager.processList);
ProcessData *process;
FOREACH_PROCESS(&g_manager.processList, process) {
switch (process->terminationStatus) {
case TERMSTATUS_NOTIFICATION_SENT:
atLeastOneListener = true;
break;
case TERMSTATUS_NOTIFICATION_FAILED:
res = svcTerminateProcess(process->handle); // official pm does not panic on failure here
break;
default:
break;
}
}
ProcessList_Unlock(&g_manager.processList);
if (atLeastOneListener) {
Result res = assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, timeout));
if (R_DESCRIPTION(res) == RD_TIMEOUT) {
ProcessList_Lock(&g_manager.processList);
ProcessData *process;
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) {
res = svcTerminateProcess(process->handle);
}
}
ProcessList_Unlock(&g_manager.processList);
assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, -1LL));
}
} else {
res = 0;
}
return res;
}
static void TerminateProcessOrTitleAsync(void *argdata)
{
struct {
u64 id;
s64 timeout;
bool useTitleId;
} *args = argdata;
ProcessData *process;
bool notify = false;
u8 variation;
if (args->timeout >= 0) {
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
}
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
// It's the only place where it uses the full titleId, and doesn't break after the first result.
// Maybe it's to allow killing all the builtins at once with their dummy titleIds? Otherwise,
// two processes can't have the same titleId.
if ((args->useTitleId && process->titleId == args->id) || process->pid == args->id) {
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
notify = true;
variation = process->terminatedNotificationVariation;
process->flags = (process->flags & ~PROCESSFLAG_NOTIFY_TERMINATION) | PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED;
}
terminateProcessImpl(process, exheaderInfo);
if (!args->useTitleId) {
break;
}
}
}
ProcessList_Unlock(&g_manager.processList);
ExHeaderInfoHeap_Delete(exheaderInfo);
if (args->timeout >= 0) {
commitPendingTerminations(args->timeout);
g_manager.waitingForTermination = false;
if (notify) {
notifySubscribers(0x110 + variation);
}
}
}
static Result TerminateProcessOrTitle(u64 id, s64 timeout, bool useTitleId)
{
ProcessData *process;
if (g_manager.preparingForReboot) {
if (timeout != 0) {
return 0xC8A05801;
}
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if ((useTitleId && process->titleId == id) || process->pid == id) {
assertSuccess(svcTerminateProcess(process->handle));
if (!useTitleId) {
break;
}
}
}
ProcessList_Unlock(&g_manager.processList);
return 0;
} else {
struct {
u64 id;
s64 timeout;
bool useTitleId;
} args = { id, timeout, useTitleId };
TaskRunner_RunTask(TerminateProcessOrTitleAsync, &args, sizeof(args));
return 0;
}
}
Result TerminateApplication(s64 timeout)
{
Result res = 0;
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
ProcessList_Lock(&g_manager.processList);
if (g_manager.runningApplicationData != NULL) {
terminateProcessImpl(g_manager.runningApplicationData, exheaderInfo);
}
ProcessList_Unlock(&g_manager.processList);
res = commitPendingTerminations(timeout);
ExHeaderInfoHeap_Delete(exheaderInfo);
g_manager.waitingForTermination = false;
return res;
}
Result TerminateTitle(u64 titleId, s64 timeout)
{
return TerminateProcessOrTitle(titleId, timeout, true);
}
Result TerminateProcess(u32 pid, s64 timeout)
{
return TerminateProcessOrTitle(pid, timeout, false);
}
ProcessData *terminateAllProcesses(u32 callerPid, s64 timeout)
{
u64 dstTimePoint = svcGetSystemTick() + nsToTicks(timeout);
ProcessData *process;
ProcessData *callerProcess = NULL; // note: official pm returns the caller's handle instead
u64 dependencies[48];
u32 numDeps = 0;
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
// List the dependencies of the caller
if (callerPid != (u32)-1) {
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessById(&g_manager.processList, callerPid);
if (process != NULL) {
callerProcess = process;
getAndListDependencies(dependencies, &numDeps, process, exheaderInfo);
}
ProcessList_Unlock(&g_manager.processList);
}
ProcessList_Lock(&g_manager.processList);
// Send notification 0x100 to the currently running application
if (g_manager.runningApplicationData != NULL) {
g_manager.runningApplicationData->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_SendTerminationNotification(g_manager.runningApplicationData);
}
// Send notification 0x100 to anything but the caller deps or the caller; and *increase* the refcount of the latter if autoloaded
// Ignore KIPs
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->flags & PROCESSFLAG_KIP) {
continue;
} else if (process == callerProcess && (process->flags & PROCESSFLAG_AUTOLOADED) != 0) {
ProcessData_Incref(process, 1);
continue;
}
u32 i;
for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++);
if (i >= numDeps) {
// Process not a listed dependency: send notification 0x100
ProcessData_SendTerminationNotification(process);
} else if (process->flags & PROCESSFLAG_AUTOLOADED){
ProcessData_Incref(process, 1);
}
}
ProcessList_Unlock(&g_manager.processList);
ExHeaderInfoHeap_Delete(exheaderInfo);
s64 timeoutTicks = dstTimePoint - svcGetSystemTick();
commitPendingTerminations(timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL);
g_manager.waitingForTermination = false;
// Now, send termination notification to PXI (PID 4)
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessById(&g_manager.processList, 4);
if (process != NULL) {
ProcessData_SendTerminationNotification(process);
} else {
panic(0LL);
}
ProcessList_Unlock(&g_manager.processList);
// Allow 1.5 extra seconds for PXI (approx 402167783 ticks)
timeoutTicks = dstTimePoint - svcGetSystemTick();
commitPendingTerminations(1500 * 1000 * 1000LL + (timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL));
g_manager.waitingForTermination = false;
return callerProcess;
}
static void PrepareForRebootAsync(void *argdata)
{
struct {
u32 pid;
s64 timeout;
} *args = argdata;
ProcessData *caller = terminateAllProcesses(args->pid, args->timeout);
if (caller != NULL) {
ProcessData_Notify(caller, 0x179);
}
}
Result PrepareForReboot(u32 pid, s64 timeout)
{
struct {
u32 pid;
s64 timeout;
} args = { pid, timeout };
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
g_manager.preparingForReboot = true;
TaskRunner_RunTask(PrepareForRebootAsync, &args, sizeof(args));
return 0;
}