mirror of https://github.com/termux/termux-app
Added|Changed!: Implement new design for shell environment generation and add support for `MIT` licensed shell environment client
- `ShellEnvironmentClient` has been renamed to `IShellEnvironment` with certain changes to its interface methods, including requirement for `Execution` command itself for `setupShellCommandEnvironment()`. - `UnixShellEnvironment` implements the `IShellEnvironment` interface as is the abstract base class of all other shell environments. - `AndroidShellEnvironment` extends from the `UnixShellEnvironment` class and provides an environment that would work for Android shells. This is `MIT` licensed and can be used by users importing the `termux-shared` library or the library itself to run `AppShell` shells. Previously, `TermuxShellEnvironmentClient` existed which was `GPLv3` licensed and it would not have been possible to use it for non-GPL code. - `TermuxShellEnvironment` extends from the `AndroidShellEnvironment` class and adds/overrides additional environment variables required for Termux shells to work, including setting `HOME`, `TMPDIR`, `PATH` and `LD_LIBRARY_PATH` appropriately. Termux app related variables will be added in a later commit. `TermuxShellEnvironment` replaces `TermuxShellEnvironmentClient` and is `GPLv3` licensed.
This commit is contained in:
parent
0328d15ea7
commit
f102ea20b2
|
@ -28,7 +28,7 @@ import com.termux.shared.errors.Errno;
|
|||
import com.termux.shared.shell.ShellUtils;
|
||||
import com.termux.shared.shell.command.runner.app.AppShell;
|
||||
import com.termux.shared.termux.settings.properties.TermuxAppSharedProperties;
|
||||
import com.termux.shared.termux.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
|
||||
import com.termux.shared.termux.shell.TermuxShellUtils;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
|
||||
|
@ -469,7 +469,8 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
|
|||
if (Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE)
|
||||
Logger.logVerboseExtended(LOG_TAG, executionCommand.toString());
|
||||
|
||||
AppShell newTermuxTask = AppShell.execute(this, executionCommand, this, new TermuxShellEnvironmentClient(), false);
|
||||
AppShell newTermuxTask = AppShell.execute(this, executionCommand, this,
|
||||
new TermuxShellEnvironment(),false);
|
||||
if (newTermuxTask == null) {
|
||||
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
|
||||
|
@ -578,7 +579,8 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
|
|||
// Otherwise if command was manually started by the user like by adding a new terminal session,
|
||||
// then no need to set stdout
|
||||
executionCommand.terminalTranscriptRows = mProperties.getTerminalTranscriptRows();
|
||||
TermuxSession newTermuxSession = TermuxSession.execute(this, executionCommand, getTermuxTerminalSessionClient(), this, new TermuxShellEnvironmentClient(), executionCommand.isPluginExecutionCommand);
|
||||
TermuxSession newTermuxSession = TermuxSession.execute(this, executionCommand, getTermuxTerminalSessionClient(),
|
||||
this, new TermuxShellEnvironment(), executionCommand.isPluginExecutionCommand);
|
||||
if (newTermuxSession == null) {
|
||||
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
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
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,5 +1,8 @@
|
|||
package com.termux.shared.shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.file.FileUtils;
|
||||
import com.termux.terminal.TerminalBuffer;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
|
@ -7,8 +10,13 @@ import com.termux.terminal.TerminalSession;
|
|||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ShellUtils {
|
||||
|
||||
/** Get process id of {@link Process}. */
|
||||
public static int getPid(Process p) {
|
||||
try {
|
||||
Field f = p.getClass().getDeclaredField("pid");
|
||||
|
@ -23,10 +31,24 @@ public class ShellUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getExecutableBasename(String executable) {
|
||||
/** Setup shell command arguments for the execute. */
|
||||
@NonNull
|
||||
public static String[] setupShellCommandArguments(@NonNull String executable, @Nullable String[] arguments) {
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(executable);
|
||||
if (arguments != null) Collections.addAll(result, arguments);
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/** Get basename for executable. */
|
||||
@Nullable
|
||||
public static String getExecutableBasename(@Nullable String executable) {
|
||||
return FileUtils.getFileBasename(executable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Get transcript for {@link TerminalSession}. */
|
||||
public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) {
|
||||
if (terminalSession == null) return null;
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package com.termux.shared.shell.command.environment;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Environment for Android.
|
||||
*
|
||||
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/core/java/android/os/Environment.java
|
||||
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:system/core/rootdir/init.environ.rc.in
|
||||
* https://cs.android.com/android/platform/superproject/+/android-5.0.0_r1.0.1:system/core/rootdir/init.environ.rc.in
|
||||
* https://cs.android.com/android/_/android/platform/system/core/+/refs/tags/android-12.0.0_r32:rootdir/init.rc;l=910
|
||||
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:packages/modules/SdkExtensions/derive_classpath/derive_classpath.cpp;l=96
|
||||
*/
|
||||
public class AndroidShellEnvironment extends UnixShellEnvironment {
|
||||
|
||||
/** Get shell environment for Android. */
|
||||
@NonNull
|
||||
@Override
|
||||
public HashMap<String, String> getEnvironment(@NonNull Context currentPackageContext, boolean isFailSafe) {
|
||||
HashMap<String, String> environment = new HashMap<>();
|
||||
|
||||
environment.put(ENV_HOME, "/");
|
||||
environment.put(ENV_LANG, "en_US.UTF-8");
|
||||
environment.put(ENV_PATH, System.getenv(ENV_PATH));
|
||||
environment.put(ENV_TMPDIR, "/data/local/tmp");
|
||||
|
||||
environment.put(ENV_COLORTERM, "truecolor");
|
||||
environment.put(ENV_TERM, "xterm-256color");
|
||||
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_ASSETS");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_DATA");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_ROOT");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_STORAGE");
|
||||
|
||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||
// https://cs.android.com/android/_/android/platform/system/core/+/fc000489
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "EXTERNAL_STORAGE");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ASEC_MOUNTPOINT");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "LOOP_MOUNTPOINT");
|
||||
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_RUNTIME_ROOT");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_ART_ROOT");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_I18N_ROOT");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "ANDROID_TZDATA_ROOT");
|
||||
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "BOOTCLASSPATH");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "DEX2OATBOOTCLASSPATH");
|
||||
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "SYSTEMSERVERCLASSPATH");
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getDefaultWorkingDirectoryPath() {
|
||||
return "/";
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getDefaultBinPath() {
|
||||
return "/system/bin";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HashMap<String, String> setupShellCommandEnvironment(@NonNull Context currentPackageContext,
|
||||
@NonNull ExecutionCommand executionCommand) {
|
||||
HashMap<String, String> environment = getEnvironment(currentPackageContext, executionCommand.isFailsafe);
|
||||
|
||||
String workingDirectory = executionCommand.workingDirectory;
|
||||
environment.put(ENV_PWD,
|
||||
workingDirectory != null && !workingDirectory.isEmpty() ? new File(workingDirectory).getAbsolutePath() : // PWD must be absolute path
|
||||
getDefaultWorkingDirectoryPath());
|
||||
ShellEnvironmentUtils.createHomeDir(environment);
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.termux.shared.shell.command.environment;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public interface IShellEnvironment {
|
||||
|
||||
/**
|
||||
* 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, like $PREFIX/bin.
|
||||
*
|
||||
* @return Should return the "/bin" path.
|
||||
*/
|
||||
@NonNull
|
||||
String getDefaultBinPath();
|
||||
|
||||
/**
|
||||
* Setup shell command 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[] setupShellCommandArguments(@NonNull String fileToExecute, @Nullable String[] arguments);
|
||||
|
||||
/**
|
||||
* Setup shell command environment to be used for commands.
|
||||
*
|
||||
* @param currentPackageContext The {@link Context} for the current package.
|
||||
* @param executionCommand The {@link ExecutionCommand} for which to set environment.
|
||||
* @return Should return the shell environment.
|
||||
*/
|
||||
@NonNull
|
||||
HashMap<String, String> setupShellCommandEnvironment(@NonNull Context currentPackageContext,
|
||||
@NonNull ExecutionCommand executionCommand);
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package com.termux.shared.shell.command.environment;
|
||||
|
||||
import static com.termux.shared.shell.command.environment.UnixShellEnvironment.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.errors.Error;
|
||||
import com.termux.shared.file.FileUtils;
|
||||
import com.termux.shared.logger.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ShellEnvironmentUtils {
|
||||
|
||||
private static final String LOG_TAG = "ShellEnvironmentUtils";
|
||||
|
||||
/**
|
||||
* Convert environment {@link HashMap} to `environ` {@link List <String>}.
|
||||
*
|
||||
* The items in the environ will have the format `name=value`.
|
||||
*
|
||||
* Check {@link #isValidEnvironmentVariableName(String)} and {@link #isValidEnvironmentVariableValue(String)}
|
||||
* for valid variable names and values.
|
||||
*
|
||||
* https://manpages.debian.org/testing/manpages/environ.7.en.html
|
||||
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
|
||||
*/
|
||||
@NonNull
|
||||
public static List<String> convertEnvironmentToEnviron(@NonNull HashMap<String, String> environmentMap) {
|
||||
List<String> environmentList = new ArrayList<>(environmentMap.size());
|
||||
String value;
|
||||
for (String name : environmentMap.keySet()) {
|
||||
value = environmentMap.get(name);
|
||||
if (isValidEnvironmentVariableNameValuePair(name, value, true))
|
||||
environmentList.add(name + "=" + environmentMap.get(name));
|
||||
}
|
||||
return environmentList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if environment variable name and value pair is valid. Errors will be logged if
|
||||
* {@code logErrors} is {@code true}.
|
||||
*
|
||||
* Check {@link #isValidEnvironmentVariableName(String)} and {@link #isValidEnvironmentVariableValue(String)}
|
||||
* for valid variable names and values.
|
||||
*/
|
||||
public static boolean isValidEnvironmentVariableNameValuePair(@Nullable String name, @Nullable String value, boolean logErrors) {
|
||||
if (!isValidEnvironmentVariableName(name)) {
|
||||
if (logErrors)
|
||||
Logger.logErrorPrivate(LOG_TAG, "Invalid environment variable name. name=`" + name + "`, value=`" + value + "`");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidEnvironmentVariableValue(value)) {
|
||||
if (logErrors)
|
||||
Logger.logErrorPrivate(LOG_TAG, "Invalid environment variable value. name=`" + name + "`, value=`" + value + "`");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if environment variable name is valid. It must not be {@code null} and must not contain
|
||||
* the null byte ('\0') and must only contain alphanumeric and underscore characters and must not
|
||||
* start with a digit.
|
||||
*/
|
||||
public static boolean isValidEnvironmentVariableName(@Nullable String name) {
|
||||
return name != null && !name.contains("\0") && name.matches("[a-zA-Z_][a-zA-Z0-9_]*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if environment variable value is valid. It must not be {@code null} and must not contain
|
||||
* the null byte ('\0').
|
||||
*/
|
||||
public static boolean isValidEnvironmentVariableValue(@Nullable String value) {
|
||||
return value != null && !value.contains("\0");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Put value in environment if variable exists in {@link System) environment. */
|
||||
public static void putToEnvIfInSystemEnv(@NonNull HashMap<String, String> environment,
|
||||
@NonNull String name) {
|
||||
String value = System.getenv(name);
|
||||
if (value != null) {
|
||||
environment.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Put {@link String} value in environment if value set. */
|
||||
public static void putToEnvIfSet(@NonNull HashMap<String, String> environment, @NonNull String name,
|
||||
@Nullable String value) {
|
||||
if (value != null) {
|
||||
environment.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Put {@link Boolean} value "true" or "false" in environment if value set. */
|
||||
public static void putToEnvIfSet(@NonNull HashMap<String, String> environment, @NonNull String name,
|
||||
@Nullable Boolean value) {
|
||||
if (value != null) {
|
||||
environment.put(name, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Create HOME directory in environment {@link Map} if set. */
|
||||
public static void createHomeDir(@NonNull HashMap<String, String> environment) {
|
||||
String homeDirectory = environment.get(ENV_HOME);
|
||||
if (homeDirectory != null && !homeDirectory.isEmpty()) {
|
||||
Error error = FileUtils.createDirectoryFile("shell home", homeDirectory);
|
||||
if (error != null) {
|
||||
Logger.logErrorExtended(LOG_TAG, "Failed to create shell home directory\n" + error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.termux.shared.shell.command.environment;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.shell.ShellUtils;
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Environment for Unix-like systems.
|
||||
*
|
||||
* https://manpages.debian.org/testing/manpages/environ.7.en.html
|
||||
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
|
||||
*/
|
||||
public abstract class UnixShellEnvironment implements IShellEnvironment {
|
||||
|
||||
/** Environment variable for the terminal's colour capabilities. */
|
||||
public static final String ENV_COLORTERM = "COLORTERM";
|
||||
|
||||
/** Environment variable for the path of the user's home directory. */
|
||||
public static final String ENV_HOME = "HOME";
|
||||
|
||||
/** Environment variable for the locale category for native language, local customs, and coded
|
||||
* character set in the absence of the LC_ALL and other LC_* environment variables. */
|
||||
public static final String ENV_LANG = "LANG";
|
||||
|
||||
/** Environment variable for the represent the sequence of directory paths separated with
|
||||
* colons ":" that should be searched in for dynamic shared libraries to link programs against. */
|
||||
public static final String ENV_LD_LIBRARY_PATH = "LD_LIBRARY_PATH";
|
||||
|
||||
/** Environment variable for the represent the sequence of directory path prefixes separated with
|
||||
* colons ":" that certain functions and utilities apply in searching for an executable file
|
||||
* known only by a filename. */
|
||||
public static final String ENV_PATH = "PATH";
|
||||
|
||||
/** Environment variable for the absolute path of the current working directory. It shall not
|
||||
* contain any components that are dot or dot-dot. The value is set by the cd utility, and by
|
||||
* the sh utility during initialization. */
|
||||
public static final String ENV_PWD = "PWD";
|
||||
|
||||
/** Environment variable for the terminal type for which output is to be prepared. This information
|
||||
* is used by utilities and application programs wishing to exploit special capabilities specific
|
||||
* to a terminal. The format and allowable values of this environment variable are unspecified. */
|
||||
public static final String ENV_TERM = "TERM";
|
||||
|
||||
/** Environment variable for the path of a directory made available for programs that need a place
|
||||
* to create temporary files. */
|
||||
public static final String ENV_TMPDIR = "TMPDIR";
|
||||
|
||||
|
||||
|
||||
|
||||
@NonNull
|
||||
public abstract HashMap<String, String> getEnvironment(@NonNull Context currentPackageContext,
|
||||
boolean isFailSafe);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public abstract String getDefaultWorkingDirectoryPath();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public abstract String getDefaultBinPath();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] setupShellCommandArguments(@NonNull String executable, @Nullable String[] arguments) {
|
||||
return ShellUtils.setupShellCommandArguments(executable, arguments);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public abstract HashMap<String, String> setupShellCommandEnvironment(@NonNull Context currentPackageContext,
|
||||
@NonNull ExecutionCommand executionCommand);
|
||||
|
||||
}
|
|
@ -7,14 +7,16 @@ import android.system.OsConstants;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.termux.shared.R;
|
||||
import com.termux.shared.data.DataUtils;
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.environment.ShellEnvironmentUtils;
|
||||
import com.termux.shared.shell.command.result.ResultData;
|
||||
import com.termux.shared.errors.Errno;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.shell.command.ExecutionCommand.ExecutionState;
|
||||
import com.termux.shared.shell.ShellEnvironmentClient;
|
||||
import com.termux.shared.shell.command.environment.IShellEnvironment;
|
||||
import com.termux.shared.shell.ShellUtils;
|
||||
import com.termux.shared.shell.StreamGobbler;
|
||||
|
||||
|
@ -22,6 +24,9 @@ import java.io.DataOutputStream;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class that maintains info for background app shells run with {@link Runtime#exec(String[], String[], File)}.
|
||||
|
@ -50,14 +55,16 @@ public final class AppShell {
|
|||
* The {@link ExecutionCommand#commandLabel}, {@link ExecutionCommand#arguments} and
|
||||
* {@link ExecutionCommand#workingDirectory} may optionally be set.
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @param currentPackageContext The {@link Context} for operations. This must be the context for
|
||||
* the current package and not the context of a `sharedUserId` package,
|
||||
* since environment setup may be dependent on current package.
|
||||
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
|
||||
* @param appShellClient The {@link AppShellClient} interface implementation.
|
||||
* The {@link AppShellClient#onAppShellExited(AppShell)} will
|
||||
* be called regardless of {@code isSynchronous} value but not if
|
||||
* {@code null} is returned by this method. This can
|
||||
* optionally be {@code null}.
|
||||
* @param shellEnvironmentClient The {@link ShellEnvironmentClient} interface implementation.
|
||||
* @param shellEnvironmentClient The {@link IShellEnvironment} interface implementation.
|
||||
* @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}
|
||||
* sub object of the {@link AppShell} returned.
|
||||
|
@ -65,9 +72,9 @@ public final class AppShell {
|
|||
* asynchronously in the background and control is returned to the caller thread.
|
||||
* @return Returns the {@link AppShell}. This will be {@code null} if failed to start the execution command.
|
||||
*/
|
||||
public static AppShell execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
||||
public static AppShell execute(@NonNull final Context currentPackageContext, @NonNull ExecutionCommand executionCommand,
|
||||
final AppShellClient appShellClient,
|
||||
@NonNull final ShellEnvironmentClient shellEnvironmentClient,
|
||||
@NonNull final IShellEnvironment shellEnvironmentClient,
|
||||
final boolean isSynchronous) {
|
||||
if (executionCommand.executable == null || executionCommand.executable.isEmpty()) {
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(),
|
||||
|
@ -81,19 +88,27 @@ public final class AppShell {
|
|||
if (executionCommand.workingDirectory.isEmpty())
|
||||
executionCommand.workingDirectory = "/";
|
||||
|
||||
String[] env = shellEnvironmentClient.buildEnvironment(context, executionCommand.isFailsafe, executionCommand.workingDirectory);
|
||||
// Transform executable path to shell/session name, e.g. "/bin/do-something.sh" => "do-something.sh".
|
||||
String executableBasename = ShellUtils.getExecutableBasename(executionCommand.executable);
|
||||
|
||||
if (executionCommand.shellName == null)
|
||||
executionCommand.shellName = executableBasename;
|
||||
|
||||
final String[] commandArray = shellEnvironmentClient.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
||||
if (executionCommand.commandLabel == null)
|
||||
executionCommand.commandLabel = executableBasename;
|
||||
|
||||
// Setup command args
|
||||
final String[] commandArray = shellEnvironmentClient.setupShellCommandArguments(executionCommand.executable, executionCommand.arguments);
|
||||
|
||||
// Setup command environment
|
||||
HashMap<String, String> environment = shellEnvironmentClient.setupShellCommandEnvironment(currentPackageContext,
|
||||
executionCommand);
|
||||
List<String> environmentList = ShellEnvironmentUtils.convertEnvironmentToEnviron(environment);
|
||||
Collections.sort(environmentList);
|
||||
String[] environmentArray = environmentList.toArray(new String[0]);
|
||||
|
||||
if (!executionCommand.setState(ExecutionState.EXECUTING)) {
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), context.getString(R.string.error_failed_to_execute_app_shell_command, executionCommand.getCommandIdAndLabelLogString()));
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), currentPackageContext.getString(R.string.error_failed_to_execute_app_shell_command, executionCommand.getCommandIdAndLabelLogString()));
|
||||
AppShell.processAppShellResult(null, executionCommand);
|
||||
return null;
|
||||
}
|
||||
|
@ -101,22 +116,23 @@ public final class AppShell {
|
|||
// No need to log stdin if logging is disabled, like for app internal scripts
|
||||
Logger.logDebugExtended(LOG_TAG, ExecutionCommand.getExecutionInputLogString(executionCommand,
|
||||
true, Logger.shouldEnableLoggingForCustomLogLevel(executionCommand.backgroundCustomLogLevel)));
|
||||
Logger.logVerboseExtended(LOG_TAG, "\"" + executionCommand.getCommandIdAndLabelLogString() + "\" AppShell Environment:\n" +
|
||||
Joiner.on("\n").join(environmentArray));
|
||||
|
||||
// Exec the process
|
||||
final Process process;
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(commandArray, env, new File(executionCommand.workingDirectory));
|
||||
process = Runtime.getRuntime().exec(commandArray, environmentArray, new File(executionCommand.workingDirectory));
|
||||
} catch (IOException e) {
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), context.getString(R.string.error_failed_to_execute_app_shell_command, executionCommand.getCommandIdAndLabelLogString()), e);
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), currentPackageContext.getString(R.string.error_failed_to_execute_app_shell_command, executionCommand.getCommandIdAndLabelLogString()), e);
|
||||
AppShell.processAppShellResult(null, executionCommand);
|
||||
return null;
|
||||
}
|
||||
|
||||
final AppShell appShell = new AppShell(process, executionCommand, appShellClient);
|
||||
|
||||
if (isSynchronous) {
|
||||
try {
|
||||
appShell.executeInner(context);
|
||||
appShell.executeInner(currentPackageContext);
|
||||
} catch (IllegalThreadStateException | InterruptedException e) {
|
||||
// TODO: Should either of these be handled or returned?
|
||||
}
|
||||
|
@ -125,7 +141,7 @@ public final class AppShell {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
appShell.executeInner(context);
|
||||
appShell.executeInner(currentPackageContext);
|
||||
} catch (IllegalThreadStateException | InterruptedException e) {
|
||||
// TODO: Should either of these be handled or returned?
|
||||
}
|
||||
|
@ -274,10 +290,10 @@ public final class AppShell {
|
|||
* then the {@link AppShellClient#onAppShellExited(AppShell)} callback will be called.
|
||||
*
|
||||
* @param appShell The {@link AppShell}, which should be set if
|
||||
* {@link #execute(Context, ExecutionCommand, AppShellClient, ShellEnvironmentClient, boolean)}
|
||||
* {@link #execute(Context, ExecutionCommand, AppShellClient, IShellEnvironment, HashMap, boolean)}
|
||||
* successfully started the process.
|
||||
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
||||
* {@link #execute(Context, ExecutionCommand, AppShellClient, ShellEnvironmentClient, boolean)}
|
||||
* {@link #execute(Context, ExecutionCommand, AppShellClient, IShellEnvironment, HashMap, boolean)}
|
||||
* failed to start the process.
|
||||
*/
|
||||
private static void processAppShellResult(final AppShell appShell, ExecutionCommand executionCommand) {
|
||||
|
|
|
@ -23,7 +23,7 @@ import com.termux.shared.shell.command.ExecutionCommand;
|
|||
import com.termux.shared.errors.Error;
|
||||
import com.termux.shared.android.PackageUtils;
|
||||
import com.termux.shared.termux.TermuxConstants.TERMUX_APP;
|
||||
import com.termux.shared.termux.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
|
@ -597,7 +597,7 @@ public class TermuxUtils {
|
|||
null, ExecutionCommand.Runner.APP_SHELL.getName(), false);
|
||||
executionCommand.commandLabel = "APT Info Command";
|
||||
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_OFF;
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironmentClient(), true);
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironment(), true);
|
||||
if (appShell == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0) {
|
||||
Logger.logErrorExtended(LOG_TAG, executionCommand.toString());
|
||||
return null;
|
||||
|
@ -656,7 +656,7 @@ public class TermuxUtils {
|
|||
null, logcatScript + "\n", "/", ExecutionCommand.Runner.APP_SHELL.getName(), true);
|
||||
executionCommand.commandLabel = "Logcat dump command";
|
||||
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_OFF;
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironmentClient(), true);
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironment(), true);
|
||||
if (appShell == null || !executionCommand.isSuccessful()) {
|
||||
Logger.logErrorExtended(LOG_TAG, executionCommand.toString());
|
||||
return null;
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.termux.shared.markdown.MarkdownUtils;
|
|||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.errors.Error;
|
||||
import com.termux.shared.file.FileUtilsErrno;
|
||||
import com.termux.shared.termux.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
|
||||
import com.termux.shared.shell.command.runner.app.AppShell;
|
||||
import com.termux.shared.android.AndroidUtils;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
@ -364,7 +364,7 @@ public class TermuxFileUtils {
|
|||
statScript.toString() + "\n", "/", ExecutionCommand.Runner.APP_SHELL.getName(), true);
|
||||
executionCommand.commandLabel = TermuxConstants.TERMUX_APP_NAME + " Files Stat Command";
|
||||
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_OFF;
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironmentClient(), true);
|
||||
AppShell appShell = AppShell.execute(context, executionCommand, null, new TermuxShellEnvironment(), true);
|
||||
if (appShell == null || !executionCommand.isSuccessful()) {
|
||||
Logger.logErrorExtended(LOG_TAG, executionCommand.toString());
|
||||
return null;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package com.termux.shared.termux.shell;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.shell.ShellEnvironmentClient;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,10 +3,10 @@ package com.termux.shared.termux.shell;
|
|||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.shared.errors.Error;
|
||||
import com.termux.shared.file.filesystem.FileTypes;
|
||||
import com.termux.shared.termux.TermuxBootstrap;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.file.FileUtils;
|
||||
import com.termux.shared.logger.Logger;
|
||||
|
@ -25,31 +25,11 @@ import java.util.List;
|
|||
|
||||
public class TermuxShellUtils {
|
||||
|
||||
public static String TERMUX_VERSION_NAME;
|
||||
public static String TERMUX_IS_DEBUGGABLE_BUILD;
|
||||
public static String TERMUX_APP_PID;
|
||||
public static String TERMUX_APK_RELEASE;
|
||||
public static Boolean TERMUX_APP_AM_SOCKET_SERVER_ENABLED;
|
||||
|
||||
public static String TERMUX_API_VERSION_NAME;
|
||||
|
||||
|
||||
private static final String LOG_TAG = "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<>();
|
||||
|
||||
loadTermuxEnvVariables(currentPackageContext);
|
||||
|
@ -71,51 +51,14 @@ public class TermuxShellUtils {
|
|||
|
||||
if (TERMUX_API_VERSION_NAME != null)
|
||||
environment.add("TERMUX_API_VERSION=" + TERMUX_API_VERSION_NAME);
|
||||
|
||||
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("PWD=" + workingDirectory);
|
||||
environment.add("TMPDIR=" + TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH);
|
||||
if (TermuxBootstrap.isAppPackageVariantAPTAndroid5()) {
|
||||
environment.add("PATH=" + TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + ":" + TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/applets");
|
||||
environment.add("LD_LIBRARY_PATH=" + TermuxConstants.TERMUX_LIB_PREFIX_DIR_PATH);
|
||||
} else {
|
||||
environment.add("PATH=" + TermuxConstants.TERMUX_BIN_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) {
|
||||
/**
|
||||
* Setup shell command arguments for the execute. The file interpreter may be prefixed to
|
||||
* command arguments if needed.
|
||||
*/
|
||||
@NonNull
|
||||
public static String[] setupShellCommandArguments(@NonNull String executable, @Nullable 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
|
||||
|
@ -123,7 +66,7 @@ public class TermuxShellUtils {
|
|||
// - 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);
|
||||
File file = new File(executable);
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[256];
|
||||
int bytesRead = in.read(buffer);
|
||||
|
@ -140,9 +83,9 @@ public class TermuxShellUtils {
|
|||
// Skip whitespace after shebang.
|
||||
} else {
|
||||
// End of shebang.
|
||||
String executable = builder.toString();
|
||||
if (executable.startsWith("/usr") || executable.startsWith("/bin")) {
|
||||
String[] parts = executable.split("/");
|
||||
String shebangExecutable = builder.toString();
|
||||
if (shebangExecutable.startsWith("/usr") || shebangExecutable.startsWith("/bin")) {
|
||||
String[] parts = shebangExecutable.split("/");
|
||||
String binary = parts[parts.length - 1];
|
||||
interpreter = TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/" + binary;
|
||||
}
|
||||
|
@ -164,11 +107,12 @@ public class TermuxShellUtils {
|
|||
|
||||
List<String> result = new ArrayList<>();
|
||||
if (interpreter != null) result.add(interpreter);
|
||||
result.add(fileToExecute);
|
||||
result.add(executable);
|
||||
if (arguments != null) Collections.addAll(result, arguments);
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/** Clear files under {@link TermuxConstants#TERMUX_TMP_PREFIX_DIR_PATH}. */
|
||||
public static void clearTermuxTMPDIR(boolean onlyIfExists) {
|
||||
// Existence check before clearing may be required since clearDirectory() will automatically
|
||||
// re-create empty directory if doesn't exist, which should not be done for things like
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package com.termux.shared.termux.shell.command.environment;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.environment.AndroidShellEnvironment;
|
||||
import com.termux.shared.termux.TermuxBootstrap;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.termux.shell.TermuxShellUtils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Environment for Termux.
|
||||
*/
|
||||
public class TermuxShellEnvironment extends AndroidShellEnvironment {
|
||||
|
||||
private static final String LOG_TAG = "TermuxShellEnvironment";
|
||||
|
||||
/** Environment variable for the termux {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH}. */
|
||||
public static final String ENV_PREFIX = "PREFIX";
|
||||
|
||||
|
||||
/** Get shell environment for Termux. */
|
||||
@NonNull
|
||||
@Override
|
||||
public HashMap<String, String> getEnvironment(@NonNull Context currentPackageContext, boolean isFailSafe) {
|
||||
|
||||
// Termux environment builds upon the Android environment
|
||||
HashMap<String, String> environment = super.getEnvironment(currentPackageContext, isFailSafe);
|
||||
|
||||
environment.put(ENV_HOME, TermuxConstants.TERMUX_HOME_DIR_PATH);
|
||||
environment.put(ENV_PREFIX, TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
||||
|
||||
// If failsafe is not enabled, then we keep default PATH and TMPDIR so that system binaries can be used
|
||||
if (!isFailSafe) {
|
||||
environment.put(ENV_TMPDIR, TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH);
|
||||
if (TermuxBootstrap.isAppPackageVariantAPTAndroid5()) {
|
||||
// Termux in android 5/6 era shipped busybox binaries in applets directory
|
||||
environment.put(ENV_PATH, TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + ":" + TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/applets");
|
||||
environment.put(ENV_LD_LIBRARY_PATH, TermuxConstants.TERMUX_LIB_PREFIX_DIR_PATH);
|
||||
} else {
|
||||
// Termux binaries on Android 7+ rely on DT_RUNPATH, so LD_LIBRARY_PATH should be unset by default
|
||||
environment.put(ENV_PATH, TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH);
|
||||
environment.remove(ENV_LD_LIBRARY_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getDefaultWorkingDirectoryPath() {
|
||||
return TermuxConstants.TERMUX_HOME_DIR_PATH;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getDefaultBinPath() {
|
||||
return TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] setupShellCommandArguments(@NonNull String executable, String[] arguments) {
|
||||
return TermuxShellUtils.setupShellCommandArguments(executable, arguments);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,17 +5,23 @@ import android.system.OsConstants;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.termux.shared.R;
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.environment.ShellEnvironmentUtils;
|
||||
import com.termux.shared.shell.command.environment.UnixShellEnvironment;
|
||||
import com.termux.shared.shell.command.result.ResultData;
|
||||
import com.termux.shared.errors.Errno;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.shell.ShellEnvironmentClient;
|
||||
import com.termux.shared.shell.command.environment.IShellEnvironment;
|
||||
import com.termux.shared.shell.ShellUtils;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
import com.termux.terminal.TerminalSessionClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class that maintains info for foreground Termux sessions.
|
||||
|
@ -49,11 +55,13 @@ public class TermuxSession {
|
|||
* If {@link ExecutionCommand#executable} is {@code null}, then a default shell is automatically
|
||||
* chosen.
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @param currentPackageContext The {@link Context} for operations. This must be the context for
|
||||
* the current package and not the context of a `sharedUserId` package,
|
||||
* since environment setup may be dependent on current package.
|
||||
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
|
||||
* @param terminalSessionClient The {@link TerminalSessionClient} interface implementation.
|
||||
* @param termuxSessionClient The {@link TermuxSessionClient} interface implementation.
|
||||
* @param shellEnvironmentClient The {@link ShellEnvironmentClient} interface implementation.
|
||||
* @param shellEnvironmentClient The {@link IShellEnvironment} interface implementation.
|
||||
* @param setStdoutOnExit If set to {@code true}, then the {@link ResultData#stdout}
|
||||
* available in the {@link TermuxSessionClient#onTermuxSessionExited(TermuxSession)}
|
||||
* callback will be set to the {@link TerminalSession} transcript. The session
|
||||
|
@ -63,9 +71,9 @@ public class TermuxSession {
|
|||
* since this requires extra processing to get it.
|
||||
* @return Returns the {@link TermuxSession}. This will be {@code null} if failed to start the execution command.
|
||||
*/
|
||||
public static TermuxSession execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
|
||||
public static TermuxSession execute(@NonNull final Context currentPackageContext, @NonNull ExecutionCommand executionCommand,
|
||||
@NonNull final TerminalSessionClient terminalSessionClient, final TermuxSessionClient termuxSessionClient,
|
||||
@NonNull final ShellEnvironmentClient shellEnvironmentClient,
|
||||
@NonNull final IShellEnvironment shellEnvironmentClient,
|
||||
final boolean setStdoutOnExit) {
|
||||
if (executionCommand.executable != null && executionCommand.executable.isEmpty())
|
||||
executionCommand.executable = null;
|
||||
|
@ -74,8 +82,6 @@ public class TermuxSession {
|
|||
if (executionCommand.workingDirectory.isEmpty())
|
||||
executionCommand.workingDirectory = "/";
|
||||
|
||||
String[] environment = shellEnvironmentClient.buildEnvironment(context, executionCommand.isFailsafe, executionCommand.workingDirectory);
|
||||
|
||||
String defaultBinPath = shellEnvironmentClient.getDefaultBinPath();
|
||||
if (defaultBinPath.isEmpty())
|
||||
defaultBinPath = "/system/bin";
|
||||
|
@ -108,30 +114,42 @@ public class TermuxSession {
|
|||
|
||||
}
|
||||
|
||||
String[] processArgs = shellEnvironmentClient.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
||||
// Setup command args
|
||||
String[] commandArgs = shellEnvironmentClient.setupShellCommandArguments(executionCommand.executable, executionCommand.arguments);
|
||||
|
||||
executionCommand.executable = processArgs[0];
|
||||
executionCommand.executable = commandArgs[0];
|
||||
String processName = (isLoginShell ? "-" : "") + ShellUtils.getExecutableBasename(executionCommand.executable);
|
||||
|
||||
String[] arguments = new String[processArgs.length];
|
||||
String[] arguments = new String[commandArgs.length];
|
||||
arguments[0] = processName;
|
||||
if (processArgs.length > 1) System.arraycopy(processArgs, 1, arguments, 1, processArgs.length - 1);
|
||||
if (commandArgs.length > 1) System.arraycopy(commandArgs, 1, arguments, 1, commandArgs.length - 1);
|
||||
|
||||
executionCommand.arguments = arguments;
|
||||
|
||||
if (executionCommand.commandLabel == null)
|
||||
executionCommand.commandLabel = processName;
|
||||
|
||||
// Setup command environment
|
||||
HashMap<String, String> environment = shellEnvironmentClient.setupShellCommandEnvironment(currentPackageContext,
|
||||
executionCommand);
|
||||
List<String> environmentList = ShellEnvironmentUtils.convertEnvironmentToEnviron(environment);
|
||||
Collections.sort(environmentList);
|
||||
String[] environmentArray = environmentList.toArray(new String[0]);
|
||||
|
||||
if (!executionCommand.setState(ExecutionCommand.ExecutionState.EXECUTING)) {
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), context.getString(R.string.error_failed_to_execute_termux_session_command, executionCommand.getCommandIdAndLabelLogString()));
|
||||
executionCommand.setStateFailed(Errno.ERRNO_FAILED.getCode(), currentPackageContext.getString(R.string.error_failed_to_execute_termux_session_command, executionCommand.getCommandIdAndLabelLogString()));
|
||||
TermuxSession.processTermuxSessionResult(null, executionCommand);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.logDebugExtended(LOG_TAG, executionCommand.toString());
|
||||
Logger.logVerboseExtended(LOG_TAG, "\"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession Environment:\n" +
|
||||
Joiner.on("\n").join(environmentArray));
|
||||
|
||||
Logger.logDebug(LOG_TAG, "Running \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession");
|
||||
TerminalSession terminalSession = new TerminalSession(executionCommand.executable, executionCommand.workingDirectory, executionCommand.arguments, environment, executionCommand.terminalTranscriptRows, terminalSessionClient);
|
||||
TerminalSession terminalSession = new TerminalSession(executionCommand.executable,
|
||||
executionCommand.workingDirectory, executionCommand.arguments, environmentArray,
|
||||
executionCommand.terminalTranscriptRows, terminalSessionClient);
|
||||
|
||||
if (executionCommand.shellName != null) {
|
||||
terminalSession.mSessionName = executionCommand.shellName;
|
||||
|
@ -219,10 +237,10 @@ public class TermuxSession {
|
|||
* callback will be called.
|
||||
*
|
||||
* @param termuxSession The {@link TermuxSession}, which should be set if
|
||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, ShellEnvironmentClient, boolean)}
|
||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, IShellEnvironment, HashMap, boolean)}
|
||||
* successfully started the process.
|
||||
* @param executionCommand The {@link ExecutionCommand}, which should be set if
|
||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, ShellEnvironmentClient, boolean)}
|
||||
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, IShellEnvironment, HashMap, boolean)}
|
||||
* failed to start the process.
|
||||
*/
|
||||
private static void processTermuxSessionResult(final TermuxSession termuxSession, ExecutionCommand executionCommand) {
|
||||
|
|
Loading…
Reference in New Issue