diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 8025d0bd2c..da5fdc471b 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -154,6 +154,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { Logger.logDebug(LOG_TAG, "ACTION_SERVICE_EXECUTE intent received"); actionServiceExecute(intent); break; + case TERMUX_SERVICE.ACTION_SERVICE_STOP: + Logger.logDebug(LOG_TAG, "ACTION_SERVICE_STOP intent received"); + actionServiceStop(intent); + break; default: Logger.logError(LOG_TAG, "Invalid action: \"" + action + "\""); break; @@ -354,6 +358,26 @@ private void actionReleaseWakeLock(boolean updateNotification) { Logger.logDebug(LOG_TAG, "WakeLocks released successfully"); } + private void actionServiceStop(Intent intent) { + if (intent == null) { + Logger.logError(LOG_TAG, "Ignoring null intent to actionServiceStop"); + return; + } + + String shellName = IntentUtils.getStringExtraIfSet(intent, TERMUX_SERVICE.EXTRA_SHELL_NAME, null); + if (shellName == null) { + Logger.logError(LOG_TAG, "Ignoring intent since it did not contain explicit shell name"); + return; + } + + int sigkillDelayOnStop = IntentUtils.getIntegerExtraIfSet(intent, TERMUX_SERVICE.EXTRA_SIGKILL_DELAY_ON_STOP, 5000); + AppShell appShell = getTermuxTaskForShellName(shellName); + while (appShell != null) { + appShell.terminateIfExecuting(getApplicationContext(), sigkillDelayOnStop, true); + appShell = getTermuxTaskForShellName(shellName); + } + } + /** Process {@link TERMUX_SERVICE#ACTION_SERVICE_EXECUTE} intent to execute a shell command in * a foreground TermuxSession or in a background TermuxTask. */ private void actionServiceExecute(Intent intent) { diff --git a/termux-shared/src/main/java/com/termux/shared/shell/command/runner/app/AppShell.java b/termux-shared/src/main/java/com/termux/shared/shell/command/runner/app/AppShell.java index 3f49e5aebb..a37c7827d2 100644 --- a/termux-shared/src/main/java/com/termux/shared/shell/command/runner/app/AppShell.java +++ b/termux-shared/src/main/java/com/termux/shared/shell/command/runner/app/AppShell.java @@ -1,6 +1,7 @@ package com.termux.shared.shell.command.runner.app; import android.content.Context; +import android.os.Handler; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -245,6 +246,37 @@ private void executeInner(@NonNull final Context context) throws IllegalThreadSt AppShell.processAppShellResult(this, null); } + /** + * Terminate this {@link AppShell} by sending a {@link OsConstants#SIGTERM} to its {@link #mProcess} + * if it is still executing. After {@code sigkillDelayOnStop} milliseconds {@link OsConstants#SIGTERM} is + * signalled. + * + * @param context The {@link Context} for operations. + * @param sigkillDelayOnStop The delay after which a SIGKILL is send. + * @param processResult If set to {@code true}, then the {@link #processAppShellResult(AppShell, ExecutionCommand)} + * will be called to process the failure. + */ + public void terminateIfExecuting(@NonNull final Context context, long sigkillDelayOnStop, boolean processResult) { + if (sigkillDelayOnStop == 0) { + killIfExecuting(context, processResult); + return; + } + + // If execution command has already finished executing, then no need to process results or sending any signals + if (mExecutionCommand.hasExecuted()) { + Logger.logDebug(LOG_TAG, "Ignoring sending SIGTERM or SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" AppShell since it has already finished executing"); + return; + } + + Logger.logDebug(LOG_TAG, "Send SIGTERM to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" AppShell"); + + if (mExecutionCommand.isExecuting()) { + term(); + } + + (new Handler()).postDelayed(() -> killIfExecuting(context, processResult), sigkillDelayOnStop); + } + /** * Kill this {@link AppShell} by sending a {@link OsConstants#SIGILL} to its {@link #mProcess} * if its still executing. @@ -274,6 +306,20 @@ public void killIfExecuting(@NonNull final Context context, boolean processResul } } + + /** + * Terminate this {@link AppShell} by sending a {@link OsConstants#SIGTERM} to its {@link #mProcess}. + */ + public void term() { + int pid = ShellUtils.getPid(mProcess); + try { + // Send SIGTERM to process + Os.kill(pid, OsConstants.SIGTERM); + } catch (ErrnoException e) { + Logger.logWarn(LOG_TAG, "Failed to send SIGTERM to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" AppShell with pid " + pid + ": " + e.getMessage()); + } + } + /** * Kill this {@link AppShell} by sending a {@link OsConstants#SIGILL} to its {@link #mProcess}. */ diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 59f523af72..8d4a5ceccb 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -993,6 +993,9 @@ public static final class TERMUX_SERVICE { /** Intent action to execute command with TERMUX_SERVICE */ public static final String ACTION_SERVICE_EXECUTE = TERMUX_PACKAGE_NAME + ".service_execute"; // Default: "com.termux.service_execute" + /** Intent action to stop command execution with TERMUX_SERVICE */ + public static final String ACTION_SERVICE_STOP = TERMUX_PACKAGE_NAME + ".service_execution_stop"; // Default: "com.termux.service_execution_stop" + /** Uri scheme for paths sent via intent to TERMUX_SERVICE */ public static final String URI_SCHEME_SERVICE_EXECUTE = TERMUX_PACKAGE_NAME + ".file"; // Default: "com.termux.file" /** Intent {@code String[]} extra for arguments to the executable of the command for the TERMUX_SERVICE.ACTION_SERVICE_EXECUTE intent */ @@ -1046,6 +1049,9 @@ public static final class TERMUX_SERVICE { * be created in {@link #EXTRA_RESULT_DIRECTORY} if {@link #EXTRA_RESULT_SINGLE_FILE} is * {@code false} for the TERMUX_SERVICE.ACTION_SERVICE_EXECUTE intent */ public static final String EXTRA_RESULT_FILES_SUFFIX = TERMUX_PACKAGE_NAME + ".execute.result_files_suffix"; // Default: "com.termux.execute.result_files_suffix" + /** Intent {@code long} extra for the delay between SIGTERM and SIGKILL + * for the TERMUX_SERVICE.ACTION_SERVICE_STOP intent */ + public static final String EXTRA_SIGKILL_DELAY_ON_STOP = TERMUX_PACKAGE_NAME + ".execute.sigkill_delay_on_stop"; // Default: "com.termux.execute.sigkill_delay_on_stop"