package com.termux.shared.shell; import android.content.Context; 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.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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 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 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); } public static void clearTermuxTMPDIR(Context context) { String errmsg; errmsg = FileUtils.clearDirectory(context, "$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null, false)); if (errmsg != null) { Logger.logErrorAndShowToast(context, errmsg); } } }