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: /

+3


source to share





All Articles