diff --git a/app/src/main/java/com/termux/app/BackgroundJob.java b/app/src/main/java/com/termux/app/BackgroundJob.java index 171fedffa1..6a5499afc8 100644 --- a/app/src/main/java/com/termux/app/BackgroundJob.java +++ b/app/src/main/java/com/termux/app/BackgroundJob.java @@ -5,23 +5,18 @@ import android.content.Intent; import android.os.Bundle; -import com.termux.BuildConfig; import com.termux.app.utils.Logger; +import com.termux.app.utils.ShellUtils; import com.termux.models.ExecutionCommand; import com.termux.models.ExecutionCommand.ExecutionState; import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.List; /** * A background job launched by Termux. @@ -37,12 +32,12 @@ public BackgroundJob(String executable, final String[] arguments, String working } public BackgroundJob(ExecutionCommand executionCommand, final TermuxService service) { - String[] env = buildEnvironment(false, executionCommand.workingDirectory); + String[] env = ShellUtils.buildEnvironment(false, executionCommand.workingDirectory); if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH; - final String[] commandArray = setupProcessArgs(executionCommand.executable, executionCommand.arguments); + final String[] commandArray = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments); final String commandDescription = Arrays.toString(commandArray); if(!executionCommand.setState(ExecutionState.EXECUTING)) @@ -59,7 +54,7 @@ public BackgroundJob(ExecutionCommand executionCommand, final TermuxService serv } mProcess = process; - final int pid = getPid(mProcess); + final int pid = ShellUtils.getPid(mProcess); final Bundle result = new Bundle(); final StringBuilder outResult = new StringBuilder(); final StringBuilder errResult = new StringBuilder(); @@ -138,118 +133,4 @@ public void run() { }.start(); } - private static void addToEnvIfPresent(List environment, String name) { - String value = System.getenv(name); - if (value != null) { - environment.add(name + "=" + value); - } - } - - static String[] buildEnvironment(boolean isFailSafe, String workingDirectory) { - TermuxConstants.TERMUX_HOME_DIR.mkdirs(); - - if (workingDirectory == null || workingDirectory.isEmpty()) workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH; - - List environment = new ArrayList<>(); - - environment.add("TERMUX_VERSION=" + BuildConfig.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("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 int getPid(Process p) { - try { - Field f = p.getClass().getDeclaredField("pid"); - f.setAccessible(true); - try { - return f.getInt(p); - } finally { - f.setAccessible(false); - } - } catch (Throwable e) { - return -1; - } - } - - static String[] setupProcessArgs(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 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]); - } - } diff --git a/app/src/main/java/com/termux/app/utils/ShellUtils.java b/app/src/main/java/com/termux/app/utils/ShellUtils.java new file mode 100644 index 0000000000..341a01c791 --- /dev/null +++ b/app/src/main/java/com/termux/app/utils/ShellUtils.java @@ -0,0 +1,136 @@ +package com.termux.app.utils; + +import com.termux.BuildConfig; +import com.termux.app.TermuxConstants; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ShellUtils { + + public static String[] buildEnvironment(boolean isFailSafe, String workingDirectory) { + TermuxConstants.TERMUX_HOME_DIR.mkdirs(); + + if (workingDirectory == null || workingDirectory.isEmpty()) workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH; + + List environment = new ArrayList<>(); + + environment.add("TERMUX_VERSION=" + BuildConfig.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("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 environment, String name) { + String value = System.getenv(name); + if (value != null) { + environment.add(name + "=" + value); + } + } + + public static int getPid(Process p) { + try { + Field f = p.getClass().getDeclaredField("pid"); + f.setAccessible(true); + try { + return f.getInt(p); + } finally { + f.setAccessible(false); + } + } catch (Throwable e) { + return -1; + } + } + + public static String[] setupProcessArgs(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 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) { + if(executable == null) return null; + int lastSlash = executable.lastIndexOf('/'); + return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1); + } + +}