mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 18:55:31 +08:00
Add ShellUtils to define shell related utils since they don't belong in BackgroundJob
This commit is contained in:
@@ -5,23 +5,18 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.termux.BuildConfig;
|
|
||||||
import com.termux.app.utils.Logger;
|
import com.termux.app.utils.Logger;
|
||||||
|
import com.termux.app.utils.ShellUtils;
|
||||||
import com.termux.models.ExecutionCommand;
|
import com.termux.models.ExecutionCommand;
|
||||||
import com.termux.models.ExecutionCommand.ExecutionState;
|
import com.termux.models.ExecutionCommand.ExecutionState;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A background job launched by Termux.
|
* A background job launched by Termux.
|
||||||
@@ -37,12 +32,12 @@ public final class BackgroundJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BackgroundJob(ExecutionCommand executionCommand, final TermuxService service) {
|
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())
|
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty())
|
||||||
executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
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);
|
final String commandDescription = Arrays.toString(commandArray);
|
||||||
|
|
||||||
if(!executionCommand.setState(ExecutionState.EXECUTING))
|
if(!executionCommand.setState(ExecutionState.EXECUTING))
|
||||||
@@ -59,7 +54,7 @@ public final class BackgroundJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mProcess = process;
|
mProcess = process;
|
||||||
final int pid = getPid(mProcess);
|
final int pid = ShellUtils.getPid(mProcess);
|
||||||
final Bundle result = new Bundle();
|
final Bundle result = new Bundle();
|
||||||
final StringBuilder outResult = new StringBuilder();
|
final StringBuilder outResult = new StringBuilder();
|
||||||
final StringBuilder errResult = new StringBuilder();
|
final StringBuilder errResult = new StringBuilder();
|
||||||
@@ -138,118 +133,4 @@ public final class BackgroundJob {
|
|||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addToEnvIfPresent(List<String> 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<String> 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<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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
136
app/src/main/java/com/termux/app/utils/ShellUtils.java
Normal file
136
app/src/main/java/com/termux/app/utils/ShellUtils.java
Normal file
@@ -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<String> 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<String> 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<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) {
|
||||||
|
if(executable == null) return null;
|
||||||
|
int lastSlash = executable.lastIndexOf('/');
|
||||||
|
return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user