mirror of https://github.com/termux/termux-app
Added: NativeShell as a task runner that is only available ofer the plugin system.
This commit is contained in:
parent
9bc59ac0a5
commit
44267d6582
|
@ -22,6 +22,7 @@ import com.termux.R;
|
|||
import com.termux.app.event.SystemEventReceiver;
|
||||
import com.termux.app.terminal.TermuxTerminalSessionActivityClient;
|
||||
import com.termux.app.terminal.TermuxTerminalSessionServiceClient;
|
||||
import com.termux.shared.shell.command.runner.nativerunner.NativeShell;
|
||||
import com.termux.shared.termux.plugins.TermuxPluginUtils;
|
||||
import com.termux.shared.data.IntentUtils;
|
||||
import com.termux.shared.net.uri.UriUtils;
|
||||
|
@ -287,6 +288,11 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
|
|||
else
|
||||
mShellManager.mTermuxTasks.remove(termuxTasks.get(i));
|
||||
}
|
||||
|
||||
List<NativeShell> termuxNativeTasks = new ArrayList<>(mShellManager.mTermuxNativeTasks);
|
||||
for (int i = 0; i < termuxNativeTasks.size(); i++) {
|
||||
termuxNativeTasks.get(i).kill();
|
||||
}
|
||||
|
||||
for (int i = 0; i < pendingPluginExecutionCommands.size(); i++) {
|
||||
ExecutionCommand executionCommand = pendingPluginExecutionCommands.get(i);
|
||||
|
@ -423,8 +429,22 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
|
|||
TermuxPluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes a NativeShell as a TermuxTask.
|
||||
*/
|
||||
public NativeShell executeNativeShell(ExecutionCommand executionCommand, String[] environment, NativeShell.Client client) {
|
||||
final NativeShell[] shell = new NativeShell[1];
|
||||
shell[0] = new NativeShell(executionCommand, (exitCode, error) -> {
|
||||
mHandler.post(() -> {
|
||||
mShellManager.mTermuxNativeTasks.remove(shell[0]);
|
||||
updateNotification();
|
||||
});
|
||||
client.terminated(exitCode, error);
|
||||
}, environment);
|
||||
shell[0].execute();
|
||||
return shell[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -789,7 +809,7 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
|
|||
|
||||
// Set notification text
|
||||
int sessionCount = getTermuxSessionsSize();
|
||||
int taskCount = mShellManager.mTermuxTasks.size();
|
||||
int taskCount = mShellManager.mTermuxTasks.size() + mShellManager.mTermuxNativeTasks.size();
|
||||
String notificationText = sessionCount + " session" + (sessionCount == 1 ? "" : "s");
|
||||
if (taskCount > 0) {
|
||||
notificationText += ", " + taskCount + " task" + (taskCount == 1 ? "" : "s");
|
||||
|
|
|
@ -7,11 +7,14 @@ import android.content.Intent;
|
|||
import android.content.ServiceConnection;
|
||||
import android.os.BadParcelableException;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.NetworkOnMainThreadException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -19,6 +22,7 @@ import androidx.annotation.Nullable;
|
|||
import com.termux.app.TermuxService;
|
||||
import com.termux.plugin_aidl.IPluginCallback;
|
||||
import com.termux.plugin_aidl.IPluginService;
|
||||
import com.termux.plugin_aidl.Task;
|
||||
import com.termux.shared.android.BinderUtils;
|
||||
import com.termux.shared.errors.Error;
|
||||
import com.termux.shared.file.FileUtils;
|
||||
|
@ -28,13 +32,21 @@ import com.termux.shared.net.socket.local.LocalClientSocket;
|
|||
import com.termux.shared.net.socket.local.LocalSocketManager;
|
||||
import com.termux.shared.net.socket.local.LocalSocketRunConfig;
|
||||
import com.termux.shared.net.socket.local.PeerCred;
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.runner.nativerunner.NativeShell;
|
||||
import com.termux.shared.termux.plugins.TermuxPluginUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -45,11 +57,46 @@ public class PluginService extends Service
|
|||
{
|
||||
private final static String LOG_TAG = "PluginService";
|
||||
|
||||
|
||||
// map of connected clients by PID
|
||||
private final Map<Integer, Plugin> mConnectedPlugins = Collections.synchronizedMap(new HashMap<>());
|
||||
private final PluginServiceBinder mBinder = new PluginServiceBinder();
|
||||
private TermuxService mTermuxService; // can be null if TermuxService gets temporarily destroyed
|
||||
private final ServiceConnection mTermuxServiceConnection = new ServiceConnection()
|
||||
{
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mTermuxService = ((TermuxService.LocalBinder) service).service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mTermuxService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(ComponentName name) {
|
||||
mTermuxService = null;
|
||||
Logger.logError("Binding to TermuxService died"); // this should never happen, as TermuxService is in the same process
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNullBinding(ComponentName name) {
|
||||
// this should never happen, as TermuxService returns its Binder
|
||||
Logger.logError("TermuxService onBind returned no Binder");
|
||||
}
|
||||
};
|
||||
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal representation of a connected plugin for the service.
|
||||
*/
|
||||
private class Plugin {
|
||||
int pid, uid;
|
||||
|
||||
Map<Integer, NativeShell> tasks = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
@NonNull IPluginCallback callback;
|
||||
int cachedCallbackVersion;
|
||||
|
@ -89,35 +136,6 @@ public class PluginService extends Service
|
|||
unbindService(mTermuxServiceConnection);
|
||||
}
|
||||
|
||||
// map of connected clients by PID
|
||||
private final Map<Integer, Plugin> mConnectedPlugins = Collections.synchronizedMap(new HashMap<>());
|
||||
private final PluginServiceBinder mBinder = new PluginServiceBinder();
|
||||
private TermuxService mTermuxService; // can be null if TermuxService gets temporarily destroyed
|
||||
private final ServiceConnection mTermuxServiceConnection = new ServiceConnection()
|
||||
{
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mTermuxService = ((TermuxService.LocalBinder) service).service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mTermuxService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(ComponentName name) {
|
||||
mTermuxService = null;
|
||||
Logger.logError("Binding to TermuxService died"); // this should never happen, as TermuxService is in the same process
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNullBinding(ComponentName name) {
|
||||
// this should never happen, as TermuxService returns its Binder
|
||||
Logger.logError("TermuxService onBind returned no Binder");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
|
@ -263,17 +281,144 @@ public class PluginService extends Service
|
|||
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor[] runTask(String commandPath, String[] arguments, ParcelFileDescriptor stdin, String workdir, String commandLabel, String commandDescription, String commandHelp) {
|
||||
public Task runTask(String commandPath, String[] arguments, ParcelFileDescriptor stdin, String workdir, String[] environment) {
|
||||
externalAppsOrThrow();
|
||||
if (commandPath == null) throw new NullPointerException("Passed commandPath is null");
|
||||
checkClient();
|
||||
if (stdin == null) throw new NullPointerException("Passed stdin is null");
|
||||
Plugin p = checkClient();
|
||||
BinderUtils.enforceRunCommandPermission(PluginService.this);
|
||||
|
||||
|
||||
// TODO run the task with mTermuxService
|
||||
final Object sync = new Object();
|
||||
final RuntimeException[] ex = new RuntimeException[1];
|
||||
final boolean[] finished = {false};
|
||||
final NativeShell[] shell = new NativeShell[1];
|
||||
// create pipes
|
||||
final ParcelFileDescriptor[] out = new ParcelFileDescriptor[2];
|
||||
final ParcelFileDescriptor[] in = new ParcelFileDescriptor[2];
|
||||
|
||||
ParcelFileDescriptor[] pipes;
|
||||
try {
|
||||
pipes = ParcelFileDescriptor.createPipe();
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
stdin.close();
|
||||
} catch (IOException ignored) {}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
in[0] = pipes[0];
|
||||
out[0] = pipes[1];
|
||||
|
||||
return null;
|
||||
try {
|
||||
pipes = ParcelFileDescriptor.createPipe();
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
stdin.close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
out[0].close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
in[0].close();
|
||||
} catch (IOException ignored) {}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
in[1] = pipes[0];
|
||||
out[1] = pipes[1];
|
||||
|
||||
mMainThreadHandler.post(() -> {
|
||||
TermuxService s = mTermuxService;
|
||||
if (s == null) {
|
||||
synchronized (sync) {
|
||||
ex[0] = new IllegalStateException("Termux service unavailable");
|
||||
finished[0] = true;
|
||||
sync.notifyAll();
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
ExecutionCommand cmd = new ExecutionCommand();
|
||||
cmd.executable = commandPath;
|
||||
cmd.workingDirectory = workdir;
|
||||
cmd.arguments = arguments;
|
||||
cmd.stdinFD = stdin;
|
||||
cmd.stdoutFD = out[0];
|
||||
cmd.stderrFD = out[1];
|
||||
/*
|
||||
try {
|
||||
ParcelFileDescriptor od = out[0].dup();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
BufferedWriter w = new BufferedWriter(new FileWriter(od.getFileDescriptor()));
|
||||
w.write("test");
|
||||
w.flush();
|
||||
w.close();
|
||||
} catch (Exception ignored) {ignored.printStackTrace();}
|
||||
}).start();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
shell[0] = s.executeNativeShell(cmd, environment, (exitCode, error) -> {
|
||||
try {
|
||||
Logger.logDebug("NativeShell", "exit: "+exitCode);
|
||||
// TODO callback
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
p.tasks.put(shell[0].getPid(), shell[0]);
|
||||
synchronized (sync) {
|
||||
finished[0] = true;
|
||||
sync.notifyAll();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
synchronized (sync) {
|
||||
ex[0] = e;
|
||||
finished[0] = true;
|
||||
sync.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
while (! finished[0]) {
|
||||
synchronized (sync) {
|
||||
try {
|
||||
sync.wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// make sure to not leak file descriptors
|
||||
if (ex[0] != null) {
|
||||
try {
|
||||
stdin.close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
out[0].close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
out[1].close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
in[0].close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
in[1].close();
|
||||
} catch (IOException ignored) {}
|
||||
throw ex[0];
|
||||
}
|
||||
|
||||
Task t = new Task();
|
||||
t.stdout = in[0];
|
||||
t.stderr = in[1];
|
||||
t.pid = shell[0].getPid();
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.os.ParcelFileDescriptor;
|
|||
import android.app.PendingIntent;
|
||||
|
||||
import com.termux.plugin_aidl.IPluginCallback;
|
||||
|
||||
import com.termux.plugin_aidl.Task;
|
||||
|
||||
/**
|
||||
* All available methods in {@link com.termux.app.plugin.PluginService}.
|
||||
|
@ -42,13 +42,10 @@ interface IPluginService {
|
|||
|
||||
|
||||
/**
|
||||
* Runs a command like through a RUN_COMMAND intent.
|
||||
* For documentation of the parameters, see <a href="https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent#run_command-intent-command-extras">the wiki</a>.
|
||||
* If a parameter is null it is treated the same as if the extra isn't in the intent.
|
||||
* <br><br>
|
||||
* This method runs synchronously and returns stout in [0] of the result array and stderr in [1].
|
||||
* Runs a command in a Termux task in the background.
|
||||
* stdin, commandPath and workdir are required parameters.
|
||||
*/
|
||||
ParcelFileDescriptor[] runTask(String commandPath, in String[] arguments, in ParcelFileDescriptor stdin, String workdir, String commandLabel, String commandDescription, String commandHelp) = 3;
|
||||
Task runTask(String commandPath, in String[] arguments, in ParcelFileDescriptor stdin, String workdir, in String[] environment) = 3;
|
||||
|
||||
/**
|
||||
* This creates a socket file with name under {@link com.termux.shared.termux.TermuxConstants#TERMUX_PLUGINS_DIR_PATH}/<package name of caller>.
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.termux.plugin_aidl;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
parcelable Task {
|
||||
ParcelFileDescriptor stdout;
|
||||
ParcelFileDescriptor stderr;
|
||||
int pid;
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.termux.terminal;
|
|||
/**
|
||||
* Native methods for creating and managing pseudoterminal subprocesses. C code is in jni/termux.c.
|
||||
*/
|
||||
final class JNI {
|
||||
public final class JNI {
|
||||
|
||||
static {
|
||||
System.loadLibrary("termux");
|
||||
|
@ -24,6 +24,23 @@ final class JNI {
|
|||
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
||||
*/
|
||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
|
||||
|
||||
/**
|
||||
* Create a subprocess. Differs from {@link #createSubprocess(String, String, String[], String[], int[], int, int)} in that there is no
|
||||
* pseudoterminal, but all input and output is redirected through the given file descriptors without the need for {@link com.termux.shared.shell.StreamGobbler}
|
||||
* or additional threads to do the IO operations for that. Because file descriptors are used, this can also transmit more data than the normal Binder
|
||||
* transaction size limit for Intents.
|
||||
*
|
||||
* @param cmd The command to execute
|
||||
* @param cwd The current working directory for the executed command
|
||||
* @param args An array of arguments to the command
|
||||
* @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process
|
||||
* @param stdin The file descriptor that should be used for stdin for the process
|
||||
* @param stdout The file descriptor that should be used for stdout for the process
|
||||
* @param stderr The file descriptor that should be used for stderr for the process
|
||||
* @return The pid of the created subprocess.
|
||||
*/
|
||||
public static native int createTask(String cmd, String cwd, String[] args, String[] envVars, int stdin, int stdout, int stderr);
|
||||
|
||||
/** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
|
||||
public static native void setPtyWindowSize(int fd, int rows, int cols);
|
||||
|
|
|
@ -212,3 +212,107 @@ JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(
|
|||
{
|
||||
close(fileDescriptor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int create_task(JNIEnv* env, const char* cmd, const char* cwd, char** argv, char** envp, int stdinfd, int stdoutfd, int stderrfd) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
return throw_runtime_exception(env, "Fork failed");
|
||||
} else if (pid > 0) {
|
||||
return pid;
|
||||
} else {
|
||||
// Clear signals which the Android java process may have blocked:
|
||||
sigset_t signals_to_unblock;
|
||||
sigfillset(&signals_to_unblock);
|
||||
sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0);
|
||||
|
||||
setsid();
|
||||
|
||||
dup2(stdinfd, 0);
|
||||
dup2(stdoutfd, 1);
|
||||
dup2(stderrfd, 2);
|
||||
|
||||
DIR* self_dir = opendir("/proc/self/fd");
|
||||
if (self_dir != NULL) {
|
||||
int self_dir_fd = dirfd(self_dir);
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(self_dir)) != NULL) {
|
||||
int fd = atoi(entry->d_name);
|
||||
if (fd > 2 && fd != self_dir_fd) close(fd);
|
||||
}
|
||||
closedir(self_dir);
|
||||
}
|
||||
|
||||
clearenv();
|
||||
if (envp) for (; *envp; ++envp) putenv(*envp);
|
||||
|
||||
if (chdir(cwd) != 0) {
|
||||
char* error_message;
|
||||
// No need to free asprintf()-allocated memory since doing execvp() or exit() below.
|
||||
if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()";
|
||||
perror(error_message);
|
||||
fflush(stderr);
|
||||
}
|
||||
execvp(cmd, argv);
|
||||
// Show terminal output about failing exec() call:
|
||||
char* error_message;
|
||||
if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()";
|
||||
perror(error_message);
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_termux_terminal_JNI_createTask(JNIEnv *env, jclass TERMUX_UNUSED(clazz), jstring cmd, jstring cwd,
|
||||
jobjectArray args, jobjectArray envVars, jint stdinfd,
|
||||
jint stdoutfd, jint stderrfd) {
|
||||
jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
|
||||
char** argv = NULL;
|
||||
if (size > 0) {
|
||||
argv = (char**) malloc((size + 1) * sizeof(char*));
|
||||
if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i);
|
||||
char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL);
|
||||
if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv");
|
||||
argv[i] = strdup(arg_utf8);
|
||||
(*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8);
|
||||
}
|
||||
argv[size] = NULL;
|
||||
}
|
||||
|
||||
size = envVars ? (*env)->GetArrayLength(env, envVars) : 0;
|
||||
char** envp = NULL;
|
||||
if (size > 0) {
|
||||
envp = (char**) malloc((size + 1) * sizeof(char *));
|
||||
if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i);
|
||||
char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0);
|
||||
if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env");
|
||||
envp[i] = strdup(env_utf8);
|
||||
(*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8);
|
||||
}
|
||||
envp[size] = NULL;
|
||||
}
|
||||
|
||||
int procId;
|
||||
char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
|
||||
char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
|
||||
procId = create_task(env, cmd_utf8, cmd_cwd, argv, envp, stdinfd, stdoutfd, stderrfd);
|
||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);
|
||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);
|
||||
|
||||
if (argv) {
|
||||
for (char** tmp = argv; *tmp; ++tmp) free(*tmp);
|
||||
free(argv);
|
||||
}
|
||||
if (envp) {
|
||||
for (char** tmp = envp; *tmp; ++tmp) free(*tmp);
|
||||
free(envp);
|
||||
}
|
||||
|
||||
return procId;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.termux.shared.shell.command;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -163,8 +164,15 @@ public class ExecutionCommand {
|
|||
public String stdin;
|
||||
/** The current working directory for the {@link ExecutionCommand}. */
|
||||
public String workingDirectory;
|
||||
|
||||
|
||||
|
||||
/** The file descriptor the executable will read from. */
|
||||
public ParcelFileDescriptor stdinFD;
|
||||
/** The file descriptor the executable will write standard output to. */
|
||||
public ParcelFileDescriptor stdoutFD;
|
||||
/** The file descriptor the executable will write error output to. */
|
||||
public ParcelFileDescriptor stderrFD;
|
||||
|
||||
|
||||
/** The terminal transcript rows for the {@link ExecutionCommand}. */
|
||||
public Integer terminalTranscriptRows;
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package com.termux.shared.shell.command.runner.nativerunner;
|
||||
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.terminal.JNI;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This Runner is only available over Binder IPC, because it requires transferring file descriptors to Termux and back
|
||||
* to the client, which is not possible over Intents.
|
||||
*
|
||||
*/
|
||||
public final class NativeShell
|
||||
{
|
||||
private final ExecutionCommand exe;
|
||||
private final Client client;
|
||||
private final String[] env;
|
||||
private int pid = -1;
|
||||
|
||||
public NativeShell(ExecutionCommand exe, Client client, String[] env) {
|
||||
this.exe = exe;
|
||||
if (exe.executable == null) throw new IllegalArgumentException("NativeShell: Command cannot be null");
|
||||
if (exe.workingDirectory == null) throw new IllegalArgumentException("NativeShell: Working directory cannot be null");
|
||||
if (exe.stdinFD == null) throw new IllegalArgumentException("NativeShell: stdin cannot be null");
|
||||
if (exe.stdoutFD == null) throw new IllegalArgumentException("NativeShell: stdout cannot be null");
|
||||
if (exe.stderrFD == null) throw new IllegalArgumentException("NativeShell: stderr cannot be null");
|
||||
this.client = client;
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public interface Client {
|
||||
/**
|
||||
* @param exitCode The exit code of the process. Undefined if error is not null. Negative numbers mean a signal terminated the process.
|
||||
* @param error An exception that was thrown while trying to execute the command. Can be null.
|
||||
*/
|
||||
void terminated(int exitCode, Exception error);
|
||||
}
|
||||
|
||||
public synchronized void execute() {
|
||||
try {
|
||||
pid = JNI.createTask(exe.executable, exe.workingDirectory, exe.arguments, env, exe.stdinFD.getFd(), exe.stdoutFD.getFd(), exe.stderrFD.getFd());
|
||||
new Thread(() -> {
|
||||
int exit = JNI.waitFor(pid);
|
||||
client.terminated(exit, null);
|
||||
pid = -1;
|
||||
}).start();
|
||||
} catch (RuntimeException e) {
|
||||
client.terminated(0, e);
|
||||
} finally {
|
||||
// close the ParcelFileDescriptors
|
||||
try {
|
||||
exe.stdinFD.close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
exe.stdoutFD.close();
|
||||
} catch (IOException ignored) {}
|
||||
try {
|
||||
exe.stderrFD.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void kill() {
|
||||
if (pid != -1)
|
||||
Process.killProcess(pid);
|
||||
}
|
||||
|
||||
public synchronized int getPid() {
|
||||
return pid;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.runner.app.AppShell;
|
||||
import com.termux.shared.shell.command.runner.nativerunner.NativeShell;
|
||||
import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences;
|
||||
import com.termux.shared.termux.shell.command.runner.terminal.TermuxSession;
|
||||
|
||||
|
@ -35,6 +36,11 @@ public class TermuxShellManager {
|
|||
*/
|
||||
public final List<AppShell> mTermuxTasks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The background NativeShell tasks which this service manages.
|
||||
*/
|
||||
public final List<NativeShell> mTermuxNativeTasks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The pending plugin ExecutionCommands that have yet to be processed by this service.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue