mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 10:45:23 +08:00
Make TermuxTask and TermuxSession agnostic to termux environment
Those classes shouldn't be tied to termux environment like variables, interpreters and working directory since commands may need to be executed with a different environment like android's or with a different logic. Now both classes use the ShellEnvironmentClient interface to dynamically get the environment to be used which currently for Termux's case is implemented by TermuxShellEnvironmentClient which is just a wrapper for TermuxShellUtils since later implements static functions.
This commit is contained in:
@@ -25,6 +25,9 @@ import com.termux.app.terminal.TermuxTerminalSessionClient;
|
|||||||
import com.termux.app.utils.PluginUtils;
|
import com.termux.app.utils.PluginUtils;
|
||||||
import com.termux.shared.data.IntentUtils;
|
import com.termux.shared.data.IntentUtils;
|
||||||
import com.termux.shared.models.errors.Errno;
|
import com.termux.shared.models.errors.Errno;
|
||||||
|
import com.termux.shared.shell.ShellUtils;
|
||||||
|
import com.termux.shared.shell.TermuxShellEnvironmentClient;
|
||||||
|
import com.termux.shared.shell.TermuxShellUtils;
|
||||||
import com.termux.shared.termux.TermuxConstants;
|
import com.termux.shared.termux.TermuxConstants;
|
||||||
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
|
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
|
||||||
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
|
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
|
||||||
@@ -34,7 +37,6 @@ import com.termux.shared.terminal.TermuxTerminalSessionClientBase;
|
|||||||
import com.termux.shared.logger.Logger;
|
import com.termux.shared.logger.Logger;
|
||||||
import com.termux.shared.notification.NotificationUtils;
|
import com.termux.shared.notification.NotificationUtils;
|
||||||
import com.termux.shared.packages.PermissionUtils;
|
import com.termux.shared.packages.PermissionUtils;
|
||||||
import com.termux.shared.shell.ShellUtils;
|
|
||||||
import com.termux.shared.data.DataUtils;
|
import com.termux.shared.data.DataUtils;
|
||||||
import com.termux.shared.models.ExecutionCommand;
|
import com.termux.shared.models.ExecutionCommand;
|
||||||
import com.termux.shared.shell.TermuxTask;
|
import com.termux.shared.shell.TermuxTask;
|
||||||
@@ -162,7 +164,7 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Logger.logVerbose(LOG_TAG, "onDestroy");
|
Logger.logVerbose(LOG_TAG, "onDestroy");
|
||||||
|
|
||||||
ShellUtils.clearTermuxTMPDIR(true);
|
TermuxShellUtils.clearTermuxTMPDIR(true);
|
||||||
|
|
||||||
actionReleaseWakeLock(false);
|
actionReleaseWakeLock(false);
|
||||||
if (!mWantsToStop)
|
if (!mWantsToStop)
|
||||||
@@ -426,7 +428,7 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
|||||||
if (Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE)
|
if (Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE)
|
||||||
Logger.logVerbose(LOG_TAG, executionCommand.toString());
|
Logger.logVerbose(LOG_TAG, executionCommand.toString());
|
||||||
|
|
||||||
TermuxTask newTermuxTask = TermuxTask.execute(this, executionCommand, this, false);
|
TermuxTask newTermuxTask = TermuxTask.execute(this, executionCommand, this, new TermuxShellEnvironmentClient(), false);
|
||||||
if (newTermuxTask == null) {
|
if (newTermuxTask == null) {
|
||||||
Logger.logError(LOG_TAG, "Failed to execute new TermuxTask command for:\n" + executionCommand.getCommandIdAndLabelLogString());
|
Logger.logError(LOG_TAG, "Failed to execute new TermuxTask command for:\n" + executionCommand.getCommandIdAndLabelLogString());
|
||||||
// If the execution command was started for a plugin, then process the error
|
// If the execution command was started for a plugin, then process the error
|
||||||
@@ -522,7 +524,7 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
|||||||
// Otherwise if command was manually started by the user like by adding a new terminal session,
|
// Otherwise if command was manually started by the user like by adding a new terminal session,
|
||||||
// then no need to set stdout
|
// then no need to set stdout
|
||||||
executionCommand.terminalTranscriptRows = getTerminalTranscriptRows();
|
executionCommand.terminalTranscriptRows = getTerminalTranscriptRows();
|
||||||
TermuxSession newTermuxSession = TermuxSession.execute(this, executionCommand, getTermuxTerminalSessionClient(), this, sessionName, executionCommand.isPluginExecutionCommand);
|
TermuxSession newTermuxSession = TermuxSession.execute(this, executionCommand, getTermuxTerminalSessionClient(), this, new TermuxShellEnvironmentClient(), sessionName, executionCommand.isPluginExecutionCommand);
|
||||||
if (newTermuxSession == null) {
|
if (newTermuxSession == null) {
|
||||||
Logger.logError(LOG_TAG, "Failed to execute new TermuxSession command for:\n" + executionCommand.getCommandIdAndLabelLogString());
|
Logger.logError(LOG_TAG, "Failed to execute new TermuxSession command for:\n" + executionCommand.getCommandIdAndLabelLogString());
|
||||||
// If the execution command was started for a plugin, then process the error
|
// If the execution command was started for a plugin, then process the error
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
package com.termux.shared.shell;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface ShellEnvironmentClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default working directory path for the environment in case the path that was passed
|
||||||
|
* was {@code null} or empty.
|
||||||
|
*
|
||||||
|
* @return Should return the default working directory path.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String getDefaultWorkingDirectoryPath();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default "/bin" path, likely $PREFIX/bin.
|
||||||
|
*
|
||||||
|
* @return Should return the "/bin" path.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String getDefaultBinPath();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the shell environment to be used for commands.
|
||||||
|
*
|
||||||
|
* @param currentPackageContext The {@link Context} for the current package.
|
||||||
|
* @param isFailSafe If running a failsafe session.
|
||||||
|
* @param workingDirectory The working directory for the environment.
|
||||||
|
* @return Should return the build environment.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup process arguments for the file to execute, like interpreter, etc.
|
||||||
|
*
|
||||||
|
* @param fileToExecute The file to execute.
|
||||||
|
* @param arguments The arguments to pass to the executable.
|
||||||
|
* @return Should return the final process arguments.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments);
|
||||||
|
|
||||||
|
}
|
@@ -1,82 +1,13 @@
|
|||||||
package com.termux.shared.shell;
|
package com.termux.shared.shell;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.termux.shared.models.errors.Error;
|
|
||||||
import com.termux.shared.termux.TermuxConstants;
|
|
||||||
import com.termux.shared.file.FileUtils;
|
|
||||||
import com.termux.shared.logger.Logger;
|
|
||||||
import com.termux.shared.packages.PackageUtils;
|
|
||||||
import com.termux.shared.termux.TermuxUtils;
|
|
||||||
import com.termux.terminal.TerminalBuffer;
|
import com.termux.terminal.TerminalBuffer;
|
||||||
import com.termux.terminal.TerminalEmulator;
|
import com.termux.terminal.TerminalEmulator;
|
||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ShellUtils {
|
public class ShellUtils {
|
||||||
|
|
||||||
public static String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory) {
|
|
||||||
TermuxConstants.TERMUX_HOME_DIR.mkdirs();
|
|
||||||
|
|
||||||
if (workingDirectory == null || workingDirectory.isEmpty()) workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
|
||||||
|
|
||||||
List<String> environment = new ArrayList<>();
|
|
||||||
|
|
||||||
// This function may be called by a different package like a plugin, so we get version for Termux package via its context
|
|
||||||
Context termuxPackageContext = TermuxUtils.getTermuxPackageContext(currentPackageContext);
|
|
||||||
if (termuxPackageContext != null) {
|
|
||||||
String termuxVersionName = PackageUtils.getVersionNameForPackage(termuxPackageContext);
|
|
||||||
if (termuxVersionName != null)
|
|
||||||
environment.add("TERMUX_VERSION=" + termuxVersionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
environment.add("TERM=xterm-256color");
|
|
||||||
environment.add("COLORTERM=truecolor");
|
|
||||||
environment.add("HOME=" + TermuxConstants.TERMUX_HOME_DIR_PATH);
|
|
||||||
environment.add("PREFIX=" + TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
|
||||||
environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH"));
|
|
||||||
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
|
|
||||||
environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
|
|
||||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
|
||||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
|
||||||
environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
|
|
||||||
|
|
||||||
// These variables are needed if running on Android 10 and higher.
|
|
||||||
addToEnvIfPresent(environment, "ANDROID_ART_ROOT");
|
|
||||||
addToEnvIfPresent(environment, "DEX2OATBOOTCLASSPATH");
|
|
||||||
addToEnvIfPresent(environment, "ANDROID_I18N_ROOT");
|
|
||||||
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
|
|
||||||
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
|
|
||||||
|
|
||||||
if (isFailSafe) {
|
|
||||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
|
||||||
environment.add("PATH= " + System.getenv("PATH"));
|
|
||||||
} else {
|
|
||||||
environment.add("LANG=en_US.UTF-8");
|
|
||||||
environment.add("PATH=" + TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH);
|
|
||||||
environment.add("PWD=" + workingDirectory);
|
|
||||||
environment.add("TMPDIR=" + TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
return environment.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addToEnvIfPresent(List<String> environment, String name) {
|
|
||||||
String value = System.getenv(name);
|
|
||||||
if (value != null) {
|
|
||||||
environment.add(name + "=" + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getPid(Process p) {
|
public static int getPid(Process p) {
|
||||||
try {
|
try {
|
||||||
Field f = p.getClass().getDeclaredField("pid");
|
Field f = p.getClass().getDeclaredField("pid");
|
||||||
@@ -91,77 +22,12 @@ public class ShellUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments) {
|
|
||||||
// The file to execute may either be:
|
|
||||||
// - An elf file, in which we execute it directly.
|
|
||||||
// - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
|
|
||||||
// system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH.
|
|
||||||
// - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo.
|
|
||||||
String interpreter = null;
|
|
||||||
try {
|
|
||||||
File file = new File(fileToExecute);
|
|
||||||
try (FileInputStream in = new FileInputStream(file)) {
|
|
||||||
byte[] buffer = new byte[256];
|
|
||||||
int bytesRead = in.read(buffer);
|
|
||||||
if (bytesRead > 4) {
|
|
||||||
if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F') {
|
|
||||||
// Elf file, do nothing.
|
|
||||||
} else if (buffer[0] == '#' && buffer[1] == '!') {
|
|
||||||
// Try to parse shebang.
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
for (int i = 2; i < bytesRead; i++) {
|
|
||||||
char c = (char) buffer[i];
|
|
||||||
if (c == ' ' || c == '\n') {
|
|
||||||
if (builder.length() == 0) {
|
|
||||||
// Skip whitespace after shebang.
|
|
||||||
} else {
|
|
||||||
// End of shebang.
|
|
||||||
String executable = builder.toString();
|
|
||||||
if (executable.startsWith("/usr") || executable.startsWith("/bin")) {
|
|
||||||
String[] parts = executable.split("/");
|
|
||||||
String binary = parts[parts.length - 1];
|
|
||||||
interpreter = TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/" + binary;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
builder.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No shebang and no ELF, use standard shell.
|
|
||||||
interpreter = TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/sh";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignore.
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
if (interpreter != null) result.add(interpreter);
|
|
||||||
result.add(fileToExecute);
|
|
||||||
if (arguments != null) Collections.addAll(result, arguments);
|
|
||||||
return result.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getExecutableBasename(String executable) {
|
public static String getExecutableBasename(String executable) {
|
||||||
if (executable == null) return null;
|
if (executable == null) return null;
|
||||||
int lastSlash = executable.lastIndexOf('/');
|
int lastSlash = executable.lastIndexOf('/');
|
||||||
return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1);
|
return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearTermuxTMPDIR(boolean onlyIfExists) {
|
|
||||||
if(onlyIfExists && !FileUtils.directoryFileExists(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Error error;
|
|
||||||
error = FileUtils.clearDirectory("$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null));
|
|
||||||
if (error != null) {
|
|
||||||
Logger.logErrorExtended(error.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) {
|
public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) {
|
||||||
if (terminalSession == null) return null;
|
if (terminalSession == null) return null;
|
||||||
|
|
||||||
|
@@ -9,7 +9,6 @@ import com.termux.shared.R;
|
|||||||
import com.termux.shared.models.ExecutionCommand;
|
import com.termux.shared.models.ExecutionCommand;
|
||||||
import com.termux.shared.models.ResultData;
|
import com.termux.shared.models.ResultData;
|
||||||
import com.termux.shared.models.errors.Errno;
|
import com.termux.shared.models.errors.Errno;
|
||||||
import com.termux.shared.termux.TermuxConstants;
|
|
||||||
import com.termux.shared.logger.Logger;
|
import com.termux.shared.logger.Logger;
|
||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
import com.termux.terminal.TerminalSessionClient;
|
import com.termux.terminal.TerminalSessionClient;
|
||||||
@@ -52,6 +51,7 @@ public class TermuxSession {
|
|||||||
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
|
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
|
||||||
* @param terminalSessionClient The {@link TerminalSessionClient} interface implementation.
|
* @param terminalSessionClient The {@link TerminalSessionClient} interface implementation.
|
||||||
* @param termuxSessionClient The {@link TermuxSessionClient} interface implementation.
|
* @param termuxSessionClient The {@link TermuxSessionClient} interface implementation.
|
||||||
|
* @param shellEnvironmentClient The {@link ShellEnvironmentClient} interface implementation.
|
||||||
* @param sessionName The optional {@link TerminalSession} name.
|
* @param sessionName The optional {@link TerminalSession} name.
|
||||||
* @param setStdoutOnExit If set to {@code true}, then the {@link ResultData#stdout}
|
* @param setStdoutOnExit If set to {@code true}, then the {@link ResultData#stdout}
|
||||||
* available in the {@link TermuxSessionClient#onTermuxSessionExited(TermuxSession)}
|
* available in the {@link TermuxSessionClient#onTermuxSessionExited(TermuxSession)}
|
||||||
@@ -64,16 +64,24 @@ public class TermuxSession {
|
|||||||
*/
|
*/
|
||||||
public static TermuxSession execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
public static TermuxSession execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
||||||
@NonNull final TerminalSessionClient terminalSessionClient, final TermuxSessionClient termuxSessionClient,
|
@NonNull final TerminalSessionClient terminalSessionClient, final TermuxSessionClient termuxSessionClient,
|
||||||
|
@NonNull final ShellEnvironmentClient shellEnvironmentClient,
|
||||||
final String sessionName, final boolean setStdoutOnExit) {
|
final String sessionName, final boolean setStdoutOnExit) {
|
||||||
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty())
|
||||||
|
executionCommand.workingDirectory = shellEnvironmentClient.getDefaultWorkingDirectoryPath();
|
||||||
|
if (executionCommand.workingDirectory.isEmpty())
|
||||||
|
executionCommand.workingDirectory = "/";
|
||||||
|
|
||||||
String[] environment = ShellUtils.buildEnvironment(context, executionCommand.isFailsafe, executionCommand.workingDirectory);
|
String[] environment = shellEnvironmentClient.buildEnvironment(context, executionCommand.isFailsafe, executionCommand.workingDirectory);
|
||||||
|
|
||||||
|
String defaultBinPath = shellEnvironmentClient.getDefaultBinPath();
|
||||||
|
if (defaultBinPath.isEmpty())
|
||||||
|
defaultBinPath = "/system/bin";
|
||||||
|
|
||||||
boolean isLoginShell = false;
|
boolean isLoginShell = false;
|
||||||
if (executionCommand.executable == null) {
|
if (executionCommand.executable == null) {
|
||||||
if (!executionCommand.isFailsafe) {
|
if (!executionCommand.isFailsafe) {
|
||||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||||
File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary);
|
File shellFile = new File(defaultBinPath, shellBinary);
|
||||||
if (shellFile.canExecute()) {
|
if (shellFile.canExecute()) {
|
||||||
executionCommand.executable = shellFile.getAbsolutePath();
|
executionCommand.executable = shellFile.getAbsolutePath();
|
||||||
break;
|
break;
|
||||||
@@ -88,7 +96,7 @@ public class TermuxSession {
|
|||||||
isLoginShell = true;
|
isLoginShell = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] processArgs = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
String[] processArgs = shellEnvironmentClient.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
||||||
|
|
||||||
executionCommand.executable = processArgs[0];
|
executionCommand.executable = processArgs[0];
|
||||||
String processName = (isLoginShell ? "-" : "") + ShellUtils.getExecutableBasename(executionCommand.executable);
|
String processName = (isLoginShell ? "-" : "") + ShellUtils.getExecutableBasename(executionCommand.executable);
|
||||||
@@ -199,10 +207,10 @@ public class TermuxSession {
|
|||||||
* callback will be called.
|
* callback will be called.
|
||||||
*
|
*
|
||||||
* @param termuxSession The {@link TermuxSession}, which should be set if
|
* @param termuxSession The {@link TermuxSession}, which should be set if
|
||||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, String, boolean)}
|
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, ShellEnvironmentClient, String, boolean)}
|
||||||
* successfully started the process.
|
* successfully started the process.
|
||||||
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
||||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, String, boolean)}
|
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, ShellEnvironmentClient, String, boolean)}
|
||||||
* failed to start the process.
|
* failed to start the process.
|
||||||
*/
|
*/
|
||||||
private static void processTermuxSessionResult(final TermuxSession termuxSession, ExecutionCommand executionCommand) {
|
private static void processTermuxSessionResult(final TermuxSession termuxSession, ExecutionCommand executionCommand) {
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
package com.termux.shared.shell;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class TermuxShellEnvironmentClient implements ShellEnvironmentClient {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getDefaultWorkingDirectoryPath() {
|
||||||
|
return TermuxShellUtils.getDefaultWorkingDirectoryPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getDefaultBinPath() {
|
||||||
|
return TermuxShellUtils.getDefaultBinPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory) {
|
||||||
|
return TermuxShellUtils.buildEnvironment(currentPackageContext, isFailSafe, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments) {
|
||||||
|
return TermuxShellUtils.setupProcessArgs(fileToExecute, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,150 @@
|
|||||||
|
package com.termux.shared.shell;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.termux.shared.models.errors.Error;
|
||||||
|
import com.termux.shared.termux.TermuxConstants;
|
||||||
|
import com.termux.shared.file.FileUtils;
|
||||||
|
import com.termux.shared.logger.Logger;
|
||||||
|
import com.termux.shared.packages.PackageUtils;
|
||||||
|
import com.termux.shared.termux.TermuxUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TermuxShellUtils {
|
||||||
|
|
||||||
|
public static String getDefaultWorkingDirectoryPath() {
|
||||||
|
return TermuxConstants.TERMUX_HOME_DIR_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDefaultBinPath() {
|
||||||
|
return TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory) {
|
||||||
|
TermuxConstants.TERMUX_HOME_DIR.mkdirs();
|
||||||
|
|
||||||
|
if (workingDirectory == null || workingDirectory.isEmpty())
|
||||||
|
workingDirectory = getDefaultWorkingDirectoryPath();
|
||||||
|
|
||||||
|
List<String> environment = new ArrayList<>();
|
||||||
|
|
||||||
|
// This function may be called by a different package like a plugin, so we get version for Termux package via its context
|
||||||
|
Context termuxPackageContext = TermuxUtils.getTermuxPackageContext(currentPackageContext);
|
||||||
|
if (termuxPackageContext != null) {
|
||||||
|
String termuxVersionName = PackageUtils.getVersionNameForPackage(termuxPackageContext);
|
||||||
|
if (termuxVersionName != null)
|
||||||
|
environment.add("TERMUX_VERSION=" + termuxVersionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.add("TERM=xterm-256color");
|
||||||
|
environment.add("COLORTERM=truecolor");
|
||||||
|
environment.add("HOME=" + TermuxConstants.TERMUX_HOME_DIR_PATH);
|
||||||
|
environment.add("PREFIX=" + TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
||||||
|
environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH"));
|
||||||
|
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
|
||||||
|
environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
|
||||||
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
|
environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
|
||||||
|
|
||||||
|
// These variables are needed if running on Android 10 and higher.
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_ART_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "DEX2OATBOOTCLASSPATH");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_I18N_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
|
||||||
|
|
||||||
|
if (isFailSafe) {
|
||||||
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
|
environment.add("PATH= " + System.getenv("PATH"));
|
||||||
|
} else {
|
||||||
|
environment.add("LANG=en_US.UTF-8");
|
||||||
|
environment.add("PATH=" + TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH);
|
||||||
|
environment.add("PWD=" + workingDirectory);
|
||||||
|
environment.add("TMPDIR=" + TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
return environment.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addToEnvIfPresent(List<String> environment, String name) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (value != null) {
|
||||||
|
environment.add(name + "=" + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments) {
|
||||||
|
// The file to execute may either be:
|
||||||
|
// - An elf file, in which we execute it directly.
|
||||||
|
// - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
|
||||||
|
// system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH.
|
||||||
|
// - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo.
|
||||||
|
String interpreter = null;
|
||||||
|
try {
|
||||||
|
File file = new File(fileToExecute);
|
||||||
|
try (FileInputStream in = new FileInputStream(file)) {
|
||||||
|
byte[] buffer = new byte[256];
|
||||||
|
int bytesRead = in.read(buffer);
|
||||||
|
if (bytesRead > 4) {
|
||||||
|
if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F') {
|
||||||
|
// Elf file, do nothing.
|
||||||
|
} else if (buffer[0] == '#' && buffer[1] == '!') {
|
||||||
|
// Try to parse shebang.
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 2; i < bytesRead; i++) {
|
||||||
|
char c = (char) buffer[i];
|
||||||
|
if (c == ' ' || c == '\n') {
|
||||||
|
if (builder.length() == 0) {
|
||||||
|
// Skip whitespace after shebang.
|
||||||
|
} else {
|
||||||
|
// End of shebang.
|
||||||
|
String executable = builder.toString();
|
||||||
|
if (executable.startsWith("/usr") || executable.startsWith("/bin")) {
|
||||||
|
String[] parts = executable.split("/");
|
||||||
|
String binary = parts[parts.length - 1];
|
||||||
|
interpreter = TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/" + binary;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No shebang and no ELF, use standard shell.
|
||||||
|
interpreter = TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/sh";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
if (interpreter != null) result.add(interpreter);
|
||||||
|
result.add(fileToExecute);
|
||||||
|
if (arguments != null) Collections.addAll(result, arguments);
|
||||||
|
return result.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearTermuxTMPDIR(boolean onlyIfExists) {
|
||||||
|
if(onlyIfExists && !FileUtils.directoryFileExists(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
error = FileUtils.clearDirectory("$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null));
|
||||||
|
if (error != null) {
|
||||||
|
Logger.logErrorExtended(error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -11,7 +11,6 @@ import com.termux.shared.R;
|
|||||||
import com.termux.shared.models.ExecutionCommand;
|
import com.termux.shared.models.ExecutionCommand;
|
||||||
import com.termux.shared.models.ResultData;
|
import com.termux.shared.models.ResultData;
|
||||||
import com.termux.shared.models.errors.Errno;
|
import com.termux.shared.models.errors.Errno;
|
||||||
import com.termux.shared.termux.TermuxConstants;
|
|
||||||
import com.termux.shared.logger.Logger;
|
import com.termux.shared.logger.Logger;
|
||||||
import com.termux.shared.models.ExecutionCommand.ExecutionState;
|
import com.termux.shared.models.ExecutionCommand.ExecutionState;
|
||||||
|
|
||||||
@@ -54,6 +53,7 @@ public final class TermuxTask {
|
|||||||
* be called regardless of {@code isSynchronous} value but not if
|
* be called regardless of {@code isSynchronous} value but not if
|
||||||
* {@code null} is returned by this method. This can
|
* {@code null} is returned by this method. This can
|
||||||
* optionally be {@code null}.
|
* optionally be {@code null}.
|
||||||
|
* @param shellEnvironmentClient The {@link ShellEnvironmentClient} interface implementation.
|
||||||
* @param isSynchronous If set to {@code true}, then the command will be executed in the
|
* @param isSynchronous If set to {@code true}, then the command will be executed in the
|
||||||
* caller thread and results returned synchronously in the {@link ExecutionCommand}
|
* caller thread and results returned synchronously in the {@link ExecutionCommand}
|
||||||
* sub object of the {@link TermuxTask} returned.
|
* sub object of the {@link TermuxTask} returned.
|
||||||
@@ -62,12 +62,17 @@ public final class TermuxTask {
|
|||||||
* @return Returns the {@link TermuxTask}. This will be {@code null} if failed to start the execution command.
|
* @return Returns the {@link TermuxTask}. This will be {@code null} if failed to start the execution command.
|
||||||
*/
|
*/
|
||||||
public static TermuxTask execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
public static TermuxTask execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
||||||
final TermuxTaskClient termuxTaskClient, final boolean isSynchronous) {
|
final TermuxTaskClient termuxTaskClient,
|
||||||
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
@NonNull final ShellEnvironmentClient shellEnvironmentClient,
|
||||||
|
final boolean isSynchronous) {
|
||||||
|
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty())
|
||||||
|
executionCommand.workingDirectory = shellEnvironmentClient.getDefaultWorkingDirectoryPath();
|
||||||
|
if (executionCommand.workingDirectory.isEmpty())
|
||||||
|
executionCommand.workingDirectory = "/";
|
||||||
|
|
||||||
String[] env = ShellUtils.buildEnvironment(context, false, executionCommand.workingDirectory);
|
String[] env = shellEnvironmentClient.buildEnvironment(context, false, executionCommand.workingDirectory);
|
||||||
|
|
||||||
final String[] commandArray = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
final String[] commandArray = shellEnvironmentClient.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
||||||
|
|
||||||
if (!executionCommand.setState(ExecutionState.EXECUTING)) {
|
if (!executionCommand.setState(ExecutionState.EXECUTING)) {
|
||||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), context.getString(R.string.error_failed_to_execute_termux_task_command, executionCommand.getCommandIdAndLabelLogString()));
|
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), context.getString(R.string.error_failed_to_execute_termux_task_command, executionCommand.getCommandIdAndLabelLogString()));
|
||||||
@@ -254,10 +259,10 @@ public final class TermuxTask {
|
|||||||
* then the {@link TermuxTaskClient#onTermuxTaskExited(TermuxTask)} callback will be called.
|
* then the {@link TermuxTaskClient#onTermuxTaskExited(TermuxTask)} callback will be called.
|
||||||
*
|
*
|
||||||
* @param termuxTask The {@link TermuxTask}, which should be set if
|
* @param termuxTask The {@link TermuxTask}, which should be set if
|
||||||
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, boolean)}
|
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, ShellEnvironmentClient, boolean)}
|
||||||
* successfully started the process.
|
* successfully started the process.
|
||||||
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
||||||
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, boolean)}
|
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, ShellEnvironmentClient, boolean)}
|
||||||
* failed to start the process.
|
* failed to start the process.
|
||||||
*/
|
*/
|
||||||
private static void processTermuxTaskResult(final TermuxTask termuxTask, ExecutionCommand executionCommand) {
|
private static void processTermuxTaskResult(final TermuxTask termuxTask, ExecutionCommand executionCommand) {
|
||||||
|
@@ -16,6 +16,7 @@ import com.termux.shared.logger.Logger;
|
|||||||
import com.termux.shared.markdown.MarkdownUtils;
|
import com.termux.shared.markdown.MarkdownUtils;
|
||||||
import com.termux.shared.models.ExecutionCommand;
|
import com.termux.shared.models.ExecutionCommand;
|
||||||
import com.termux.shared.packages.PackageUtils;
|
import com.termux.shared.packages.PackageUtils;
|
||||||
|
import com.termux.shared.shell.TermuxShellEnvironmentClient;
|
||||||
import com.termux.shared.shell.TermuxTask;
|
import com.termux.shared.shell.TermuxTask;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@@ -399,7 +400,7 @@ public class TermuxUtils {
|
|||||||
aptInfoScript = aptInfoScript.replaceAll(Pattern.quote("@TERMUX_PREFIX@"), TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
aptInfoScript = aptInfoScript.replaceAll(Pattern.quote("@TERMUX_PREFIX@"), TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
||||||
|
|
||||||
ExecutionCommand executionCommand = new ExecutionCommand(1, TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/bash", null, aptInfoScript, null, true, false);
|
ExecutionCommand executionCommand = new ExecutionCommand(1, TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/bash", null, aptInfoScript, null, true, false);
|
||||||
TermuxTask termuxTask = TermuxTask.execute(context, executionCommand, null, true);
|
TermuxTask termuxTask = TermuxTask.execute(context, executionCommand, null, new TermuxShellEnvironmentClient(), true);
|
||||||
if (termuxTask == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0) {
|
if (termuxTask == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0) {
|
||||||
Logger.logError(LOG_TAG, executionCommand.toString());
|
Logger.logError(LOG_TAG, executionCommand.toString());
|
||||||
return null;
|
return null;
|
||||||
|
Reference in New Issue
Block a user