How can I run "export" command inside java process on Android?
I am trying to run shell scripts on Android using an application written in Java. I want to be able to run Android commands as root, for example am
or pm
that require a command export LD_LIBRARY_PATH=/system/lib
. Sorry, the team export
doesn't work. Here is the Java code I wrote:
private String readScript(String scriptPath) {
String line = null;
StringBuffer content = new StringBuffer();
try {
// Read the script line by line
BufferedReader reader = new BufferedReader(new FileReader(scriptPath));
while ((line = reader.readLine()) != null) {
content.append(line).append('\n');
}
content.append("exit").append('\n');
reader.close();
} catch (IOException e) {
Log.d(UpdateService.LOG_NAME, "Impossible to read script : " + e.getMessage());
}
return content.toString();
}
public StringBuffer runScript(String scriptPath) {
String line = null;
StringBuffer output = new StringBuffer();
try {
// Start the shell process
ProcessBuilder pb = new ProcessBuilder("su");
pb.redirectErrorStream(true);
pb.directory(new File(context.getApplicationInfo().dataDir));
pb.environment().put("LD_LIBRARY_PATH", "/system/lib");
Process process = pb.start();
// Execute the script
OutputStreamWriter os = new OutputStreamWriter(process.getOutputStream());
os.write(readScript(scriptPath));
os.flush();
os.close();
// Get the output of the commands
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = in.readLine()) != null) {
output.append(line).append('\n');
}
in.close();
} catch (Exception e) {
Log.d(UpdateService.LOG_NAME, "Error while executing " + scriptPath + " : " + e.getMessage());
}
return output;
}
I'll just call it:
Shell = new Shell();
StringBuffer output = shell.runScript("script.sh");
script I am trying to run:
export LD_LIBRARY_PATH=/system/lib
printenv
am start -W -n com.android.settings/.Settings\$InputMethodAndLanguageSettingsActivity
Gives me the output:
_=/system/bin/printenv ANDROID_BOOTLOGO=1 ANDROID_PROPERTY_WORKSPACE=9,32768 LOOP_MOUNTPOINT=/mnt/obb EXTERNAL_STORAGE=/mnt/sdcard ANDROID_DATA=/data RANDOM=21985 ANDROID_SOCKET_zygote=10 ASEC_MOUNTPOINT=/mnt/asec BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar ANDROID_ROOT=/system ANDROID_ASSETS=/system/app PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin Segmentation fault
As you can see, the environment variable set via the method environment()
ProcessBuilder
doesn't seem to be set, and export
it doesn't work in the script either.
Any idea on what I am doing wrong? Thank!
Edit
As I said, I also tried the version with exec()
:
public StringBuffer runScript(String scriptPath) {
String line = null;
StringBuffer output = new StringBuffer();
try {
// Start the shell process
Process process = Runtime.getRuntime().exec("su");
// Execute the script commands
OutputStreamWriter os = new OutputStreamWriter(process.getOutputStream());
os.write(readScript(scriptPath));
os.flush();
os.close();
// Get the output of the command
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = in.readLine()) != null) {
output.append(line).append('\n');
}
in.close();
process.waitFor();
} catch (Exception ex) {
Log.d(UpdateService.LOG_NAME, "Error while executing " + scriptPath + ".");
}
return output;
}
This time I get the same result without stderr
(Segmentation fault):
_=/system/bin/printenv
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=21985
ANDROID_SOCKET_zygote=10
ASEC_MOUNTPOINT=/mnt/asec
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
So the export LD_LIBRARY_PATH=/system/lib
still doesn't work. It seems to me that ProcessBuilder
is the best way to start the process as he dedicated it. Right?
Edit2
am
and pm
are actually wrappers for running the Java executable. I tried to implement the C version of this wrapper to create a complete environment that should work in all circumstances. Here is the content of am:
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
And this is my C version:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[], const char *envp[]) {
const char **p;
const int NUM_ARG = 3;
const char *APP_PROCESS = "/system/bin/app_process";
const char *BIN = "/system/bin";
const char *AM = "com.android.commands.am.Am";
const int NUM_ENV = 11;
const char *ENV[] = {
"PATH=/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/system/xbin/bb:/data/local/bin",
"SHELL=/system/bin/sh",
"MKSH=/system/bin/sh",
"HOME=/data",
"HOSTNAME=android",
"USER=root",
"LOGNAME=root",
"TERM=xterm",
"LD_LIBRARY_PATH=/system/lib/",
"CLASSPATH=/system/framework/am.jar",
NULL
};
int i = 0;
char *a[argc + NUM_ARG];
a[i] = (char *)malloc(strlen(APP_PROCESS) + 1);
strcpy(a[i++], APP_PROCESS);
a[i] = (char *)malloc(strlen(BIN) + 1);
strcpy(a[i++], BIN);
a[i] = (char *)malloc(strlen(AM) + 1);
strcpy(a[i++], AM);
p = argv + 1;
for (; i < argc + NUM_ARG - 1; i++) {
a[i] = (char *)malloc(strlen(*p) + 1);
strcpy(a[i], *p++);
}
a[i] = (char *)malloc(1);
a[i] = NULL;
p = envp;
int envc = 0;
while (*p++ != NULL) envc++;
char *v[envc + NUM_ENV];
for (i = 0; i < envc; i++) {
v[i] = (char *)malloc(strlen(envp[i]) + 1);
strcpy(v[i], envp[i]);
}
p = ENV;
while (*p != NULL) {
v[i] = (char *)malloc(strlen(*p) + 1);
strcpy(v[i++], *p++);
}
v[i] = (char *)malloc(1);
v[i] = NULL;
execve(APP_PROCESS, a, v);
fprintf(stderr, "am: %s error: %s\n", APP_PROCESS, strerror(errno));
for (i = 0; i < argc + NUM_ARG; i++) {
free(a[i]);
}
for (i = 0; i < envc + NUM_ENV; i++) {
free(v[i]);
}
return 1;
}
It works fine with ADB or SSH, but even with this implementation I get a "Segmentation Fault" of the string execve()
after executing with ProcessBuilder
. Why???
Edit3
OK, I looked at the contents of the script init.rc which has the environment variables listed. The variable LD_LIBRARY_PATH is set correctly.
on init
sysclktz 0
loglevel 7
mkdir /system
mkdir /data 0771 system system
mkdir /cache 0770 system cache
mkdir /config 0500 root root
# Setup the global environment
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
export LD_LIBRARY_PATH /vendor/lib:/system/lib
export ANDROID_BOOTLOGO 1
export ANDROID_ROOT /system
export ANDROID_ASSETS /system/app
export ANDROID_DATA /data
export EXTERNAL_STORAGE /mnt/sdcard
export ASEC_MOUNTPOINT /mnt/asec
export LOOP_MOUNTPOINT /mnt/obb
export BOOTCLASSPATH /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
But ... for some reason they change or are removed depending on the context. For example, if I am connected via ADB:
$ env
_=/system/xbin/env
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
PS1=$(precmd)$USER@$HOSTNAME:${PWD:-?} $
USER=shell
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=22124
TERM=vt100
MKSH=/system/bin/sh
HOME=/data
LD_LIBRARY_PATH=/vendor/lib:/system/lib
ASEC_MOUNTPOINT=/mnt/asec
HOSTNAME=android
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
SHELL=/system/bin/sh
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
If I am connected via SSH:
$ env
_=/system/xbin/env
ANDROID_PROPERTY_WORKSPACE=9,32768
ANDROID_BOOTLOGO=1
PS1=$(precmd)$USER@$HOSTNAME:${PWD:-?} $
USER=shell
EXTERNAL_STORAGE=/mnt/sdcard
LOGNAME=shell
ANDROID_DATA=/data
RANDOM=4546
TERM=xterm
SHELL=/system/bin/sh
MKSH=/system/bin/sh
HOME=/data/data/com.teslacoilsw.quicksshd/home
HOSTNAME=android
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/data/data/com.teslacoilsw.quicksshd/dropbear:/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/system/xbin/bb:/data/local/bin
If I run the command via Java:
$ env _=/system/bin/printenv ANDROID_BOOTLOGO=1 ANDROID_PROPERTY_WORKSPACE=9,32768 LOOP_MOUNTPOINT=/mnt/obb EXTERNAL_STORAGE=/mnt/sdcard ANDROID_DATA=/data RANDOM=7424 ANDROID_SOCKET_zygote=10 ASEC_MOUNTPOINT=/mnt/asec BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar ANDROID_ROOT=/system ANDROID_ASSETS=/system/app PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
Through ADB, the team am
works very well. Over SSH, it can work if I run export LD_LIBRARY_PATH=/system/lib/
. In Java, it never works: /
source to share
No one has answered this question yet
See similar questions:
or similar: