I try to run shell scripts on Android using an application written in Java. I want to be able to run Android commands as root such as am
or pm
which need an export LD_LIBRARY_PATH=/system/lib
command before. Unfortunately, the export
command 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 simply call it like :
Shell = new Shell();
StringBuffer output = shell.runScript("script.sh");
The script I try 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 with the environment()
method of ProcessBuilder
seems to not be set and the export
in the script doesn't work either.
Any idea about what I'm doing wrong ? Thanks!
Edit
As suggested I also tried a 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 output 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 export LD_LIBRARY_PATH=/system/lib
still doesn't work. It seems to me that ProcessBuilder
is a better way to launch process as it it dedicated to it. Is it right?
Edit2
am
and pm
are actually wrappers to launch a java executable. I tried to implement a C version of this wrapper to create a full environment that should work in any circumstance. 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 perfectly with ADB or SSH but even with this implementation, I get a "Segmentation fault" a the execve()
line after an execution with ProcessBuilder
. Why ???
Edit3
OK, I looked at the contents of the script init.rc which set out the environment variables. The LD_LIBRARY_PATH variable is correctly set.
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 are changed or deleted depending on the context. For example, if I'm 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'm 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 through 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
Via ADB the command am
works very well. Via SSH it can work if I run export LD_LIBRARY_PATH=/system/lib/
. In Java it never works :/