Skip to content

Commit

Permalink
imp - brk|doc - Unsafe kill/start shells internalized
Browse files Browse the repository at this point in the history
---

We've internalized the forced start and kill functions, because mods might kill all shells, forcing the user to log back in.

---

Type: imp
Breaking: True
Doc Required: True
Part: 1/1
  • Loading branch information
AptiviCEO committed Feb 15, 2024
1 parent b679ee2 commit 1d58569
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void TestRegisteredShellExecution()
shellInfo.ShellType.ShouldBe("Basic debug shell");

// Start the shell
Should.NotThrow(new Action(() => ShellManager.StartShellForced("Basic debug shell")));
Should.NotThrow(new Action(() => ShellManager.StartShellInternal("Basic debug shell")));

// Make sure that the shell stack is empty due to manual Bail.
ShellManager.ShellStack.ShouldBeEmpty();
Expand Down Expand Up @@ -119,7 +119,7 @@ public void TestRegisteredShellExecutionWithArguments()
shellInfo.ShellType.ShouldBe("Basic debug shell");

// Start the shell
Should.NotThrow(new Action(() => ShellManager.StartShellForced("Basic debug shell", "Hello", "World")));
Should.NotThrow(new Action(() => ShellManager.StartShellInternal("Basic debug shell", "Hello", "World")));

// Make sure that the shell stack is empty due to manual Bail.
ShellManager.ShellStack.ShouldBeEmpty();
Expand Down
2 changes: 1 addition & 1 deletion public/Nitrocid/Kernel/KernelEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private static void MainLoop()

// Initialize shell
DebugWriter.WriteDebug(DebugLevel.I, "Shell is being initialized.");
ShellManager.StartShellForced(ShellType.Shell);
ShellManager.StartShellInternal(ShellType.Shell);
}

// Load splash
Expand Down
6 changes: 2 additions & 4 deletions public/Nitrocid/Kernel/Power/PowerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ public static void PowerManage(PowerMode PowerMode, string IP, int Port)
KernelShutdown = true;

// Kill all shells and interrupt any input
for (int i = ShellManager.ShellStack.Count - 1; i >= 0; i--)
ShellManager.KillShellForced();
ShellManager.KillAllShells();
TermReaderTools.Interrupt();
break;
}
Expand All @@ -184,8 +183,7 @@ public static void PowerManage(PowerMode PowerMode, string IP, int Port)
DebugWriter.WriteDebug(DebugLevel.I, "Debug mode changed to {0}", rebootingToDebugMode);

// Kill all shells and interrupt any input
for (int i = ShellManager.ShellStack.Count - 1; i >= 0; i--)
ShellManager.KillShellForced();
ShellManager.KillAllShells();
TermReaderTools.Interrupt();
break;
}
Expand Down
2 changes: 1 addition & 1 deletion public/Nitrocid/Nitrocid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<NitrocidModAPIVersionMajor>3.0.25</NitrocidModAPIVersionMajor>

<!-- Increment NitrocidModAPIVersionChangeset every time there is a breaking change or an API addition in the N-KS API. -->
<NitrocidModAPIVersionChangeset>425</NitrocidModAPIVersionChangeset>
<NitrocidModAPIVersionChangeset>426</NitrocidModAPIVersionChangeset>

<!-- To be installed to the file version -->
<NitrocidModAPIVersion>$(NitrocidModAPIVersionMajor).$(NitrocidModAPIVersionChangeset)</NitrocidModAPIVersion>
Expand Down
161 changes: 86 additions & 75 deletions public/Nitrocid/Shell/ShellBase/Shells/ShellManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -661,68 +661,10 @@ public static void StartShell(string ShellType, params object[] ShellArgs)
if (ShellStack.Count >= 1)
{
// The shell stack has a mother shell. Start another shell.
StartShellForced(ShellType, ShellArgs);
}
}

/// <summary>
/// Force starts the shell
/// </summary>
/// <param name="ShellType">The shell type</param>
/// <param name="ShellArgs">Arguments to pass to shell</param>
public static void StartShellForced(ShellType ShellType, params object[] ShellArgs) =>
StartShellForced(GetShellTypeName(ShellType), ShellArgs);

/// <summary>
/// Force starts the shell
/// </summary>
/// <param name="ShellType">The shell type</param>
/// <param name="ShellArgs">Arguments to pass to shell</param>
public static void StartShellForced(string ShellType, params object[] ShellArgs)
{
int shellCount = ShellStack.Count;
try
{
// Make a shell executor based on shell type to select a specific executor (if the shell type is not UESH, and if the new shell isn't a mother shell)
// Please note that the remote debug shell is not supported because it works on its own space, so it can't be interfaced using the standard IShell.
var ShellExecute = GetShellExecutor(ShellType);

// Make a new instance of shell information
var ShellCommandThread = new KernelThread($"{ShellType} Command Thread", false, (cmdThreadParams) => CommandExecutor.ExecuteCommand((CommandExecutorParameters)cmdThreadParams));
var ShellInfo = new ShellExecuteInfo(ShellType, ShellExecute, ShellCommandThread);

// Add a new shell to the shell stack to indicate that we have a new shell (a visitor)!
ShellStack.Add(ShellInfo);
if (!histories.ContainsKey(ShellType))
histories.Add(ShellType, []);

// Reset title in case we're going to another shell
ConsoleExtensions.SetTitle(KernelReleaseInfo.ConsoleTitle);
ShellExecute.InitializeShell(ShellArgs);
}
catch (Exception ex)
{
// There is an exception trying to run the shell. Throw the message to the debugger and to the caller.
DebugWriter.WriteDebug(DebugLevel.E, "Failed initializing shell!!! Type: {0}, Message: {1}", ShellType, ex.Message);
DebugWriter.WriteDebug(DebugLevel.E, "Additional info: Args: {0} [{1}], Shell Stack: {2} shells, shellCount: {3} shells", ShellArgs.Length, string.Join(", ", ShellArgs), ShellStack.Count, shellCount);
DebugWriter.WriteDebug(DebugLevel.E, "This shell needs to be killed in order for the shell manager to proceed. Passing exception to caller...");
DebugWriter.WriteDebugStackTrace(ex);
DebugWriter.WriteDebug(DebugLevel.E, "If you don't see \"Purge\" from {0} after few lines, this indicates that we're in a seriously corrupted state.", nameof(StartShellForced));
throw new KernelException(KernelExceptionType.ShellOperation, Translate.DoTranslation("Failed trying to initialize shell"), ex);
}
finally
{
// There is either an unknown shell error trying to be initialized or a shell has manually set Bail to true prior to exiting, like the JSON shell
// that sets this property when it fails to open the JSON file due to syntax error or something. If we haven't added the shell to the shell stack,
// do nothing. Else, purge that shell with KillShell(). Otherwise, we'll get another shell's commands in the wrong shell and other problems will
// occur until the ghost shell has exited either automatically or manually, so check to see if we have added the newly created shell to the shell
// stack and kill that faulted shell so that we can have the correct shell in the most recent shell, ^1, from the stack.
int newShellCount = ShellStack.Count;
DebugWriter.WriteDebug(DebugLevel.I, "Purge: newShellCount: {0} shells, shellCount: {1} shells", newShellCount, shellCount);
if (newShellCount > shellCount)
KillShellForced();
TermReaderTools.SetHistory(histories[LastShellType]);
StartShellInternal(ShellType, ShellArgs);
}
else
throw new KernelException(KernelExceptionType.ShellOperation, Translate.DoTranslation("Shells can't start unless the mother shell has started."));
}

/// <summary>
Expand All @@ -735,20 +677,7 @@ public static void KillShell()
throw new KernelException(KernelExceptionType.ShellOperation, Translate.DoTranslation("Can not kill the mother shell!"));

// Not a mother shell, so bail.
ShellStack[^1].ShellBase.Bail = true;
PurgeShells();
}

/// <summary>
/// Force kills the last running shell
/// </summary>
public static void KillShellForced()
{
if (ShellStack.Count >= 1)
{
ShellStack[^1].ShellBase.Bail = true;
PurgeShells();
}
KillShellInternal();
}

/// <summary>
Expand Down Expand Up @@ -910,6 +839,88 @@ internal static void LoadHistories()
histories = JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(FileIO.ReadAllText(path));
}

/// <summary>
/// Force starts the shell
/// </summary>
/// <param name="ShellType">The shell type</param>
/// <param name="ShellArgs">Arguments to pass to shell</param>
internal static void StartShellInternal(ShellType ShellType, params object[] ShellArgs) =>
StartShellInternal(GetShellTypeName(ShellType), ShellArgs);

/// <summary>
/// Force starts the shell
/// </summary>
/// <param name="ShellType">The shell type</param>
/// <param name="ShellArgs">Arguments to pass to shell</param>
internal static void StartShellInternal(string ShellType, params object[] ShellArgs)
{
int shellCount = ShellStack.Count;
try
{
// Make a shell executor based on shell type to select a specific executor (if the shell type is not UESH, and if the new shell isn't a mother shell)
// Please note that the remote debug shell is not supported because it works on its own space, so it can't be interfaced using the standard IShell.
var ShellExecute = GetShellExecutor(ShellType);

// Make a new instance of shell information
var ShellCommandThread = new KernelThread($"{ShellType} Command Thread", false, (cmdThreadParams) => CommandExecutor.ExecuteCommand((CommandExecutorParameters)cmdThreadParams));
var ShellInfo = new ShellExecuteInfo(ShellType, ShellExecute, ShellCommandThread);

// Add a new shell to the shell stack to indicate that we have a new shell (a visitor)!
ShellStack.Add(ShellInfo);
if (!histories.ContainsKey(ShellType))
histories.Add(ShellType, []);

// Reset title in case we're going to another shell
ConsoleExtensions.SetTitle(KernelReleaseInfo.ConsoleTitle);
ShellExecute.InitializeShell(ShellArgs);
}
catch (Exception ex)
{
// There is an exception trying to run the shell. Throw the message to the debugger and to the caller.
DebugWriter.WriteDebug(DebugLevel.E, "Failed initializing shell!!! Type: {0}, Message: {1}", ShellType, ex.Message);
DebugWriter.WriteDebug(DebugLevel.E, "Additional info: Args: {0} [{1}], Shell Stack: {2} shells, shellCount: {3} shells", ShellArgs.Length, string.Join(", ", ShellArgs), ShellStack.Count, shellCount);
DebugWriter.WriteDebug(DebugLevel.E, "This shell needs to be killed in order for the shell manager to proceed. Passing exception to caller...");
DebugWriter.WriteDebugStackTrace(ex);
DebugWriter.WriteDebug(DebugLevel.E, "If you don't see \"Purge\" from {0} after few lines, this indicates that we're in a seriously corrupted state.", nameof(StartShellInternal));
throw new KernelException(KernelExceptionType.ShellOperation, Translate.DoTranslation("Failed trying to initialize shell"), ex);
}
finally
{
// There is either an unknown shell error trying to be initialized or a shell has manually set Bail to true prior to exiting, like the JSON shell
// that sets this property when it fails to open the JSON file due to syntax error or something. If we haven't added the shell to the shell stack,
// do nothing. Else, purge that shell with KillShell(). Otherwise, we'll get another shell's commands in the wrong shell and other problems will
// occur until the ghost shell has exited either automatically or manually, so check to see if we have added the newly created shell to the shell
// stack and kill that faulted shell so that we can have the correct shell in the most recent shell, ^1, from the stack.
int newShellCount = ShellStack.Count;
DebugWriter.WriteDebug(DebugLevel.I, "Purge: newShellCount: {0} shells, shellCount: {1} shells", newShellCount, shellCount);
if (newShellCount > shellCount)
KillShellInternal();
TermReaderTools.SetHistory(histories[LastShellType]);
}
}

/// <summary>
/// Force kills the last running shell
/// </summary>
internal static void KillShellInternal()
{
if (ShellStack.Count >= 1)
{
ShellStack[^1].ShellBase.Bail = true;
PurgeShells();
}
}

/// <summary>
/// Kills all the shells
/// </summary>
internal static void KillAllShells()
{
for (int i = ShellStack.Count - 1; i >= 0; i--)
ShellStack[i].ShellBase.Bail = true;
PurgeShells();
}

/// <summary>
/// Initializes the redirection
/// </summary>
Expand Down

0 comments on commit 1d58569

Please sign in to comment.