[TRICK/CWM3/EDIFY] Output to recovery UI from shell script

Search This thread

Chainfire

Moderator Emeritus / Senior Recognized Developer
Oct 2, 2007
11,452
87,862
www.chainfire.eu
Took me a few minutes to figure this out, so I thought to share :)

This is taken from some scripts I use in CF-Root, you might need to change it slightly for other CWM3 kernels (like prefixing busybox to various commands, etc).

updater-script

Assumptions:
- rootfs is mounted as rw, so we can write temporary files anywhere (ram disk)
- you put a "myscript.sh" in the system folder inside the update

All this script does is extract whatever you have in update.zip/system folder to /tmp/update, and run the myscript.sh file.

Code:
ui_print("Extracting files ...");
package_extract_dir("system", "/tmp/update");
set_perm(0, 0, 0755, "/tmp/update/myscript.sh");
run_program("/tmp/update/myscript.sh");

myscript.sh

Assumptions:
- all busybox commands are symlinked in /sbin, this is usually the case for CWM3 kernels

Code:
#!/sbin/busybox sh

# get file descriptor for output
OUTFD=$(ps | grep -v "grep" | grep -o -E "update_binary(.*)" | cut -d " " -f 3);

# same as progress command in updater-script, for example:
#
# progress 0.25 10
#
# will update the next 25% of the progress bar over a period of 10 seconds

progress() {
  if [ $OUTFD != "" ]; then
    echo "progress ${1} ${2} " 1>&$OUTFD;
  fi;
}

# same as set_progress command in updater-script, for example:
#
# set_progress 0.25
#
# sets progress bar to 25%

set_progress() {
  if [ $OUTFD != "" ]; then
    echo "set_progress ${1} " 1>&$OUTFD;
  fi;
}

# same as ui_print command in updater_script, for example:
#
# ui_print "hello world!"
#
# will output "hello world!" to recovery, while
#
# ui_print
#
# outputs an empty line

ui_print() {
  if [ $OUTFD != "" ]; then
    echo "ui_print ${1} " 1>&$OUTFD;
    echo "ui_print " 1>&$OUTFD;
  else
    echo "${1}";
  fi;
}

# --- example usage ---

# empty line after "Extracting ..." from updater-script
ui_print;

# give the user some status
ui_print "doing something (1 of 4)";
# assume this won't take more than 30 seconds
progress 0.25 30;
# you'd do something useful here
sleep 15s;
# update status
ui_print "- done with something (1 of 4)";
# we're done, make sure the progress bar is at 25%
set_progress 0.25;
# empty line
ui_print;

# repeat this a few times ;)

ui_print "doing something (2 of 4)";
progress 0.25 30;
sleep 15s;
ui_print "- done with something (2 of 4)";
set_progress 0.50;
ui_print;

ui_print "doing something (3 of 4)";
progress 0.25 30;
sleep 15s;
ui_print "- done with something (3 of 4)";
set_progress 0.75;
ui_print;

ui_print "doing something (4 of 4)";
progress 0.25 30;
sleep 15s;
ui_print "- done with something (4 of 4)";
set_progress 1.00;
ui_print;

# done !
ui_print "done! rebooting!";

How, what, why ?
While updater-script is fine for a lot of things, like installing a new ROM and whatnot, anything sufficiently complicated still has to be done through shell scripts, because a great many things just cannot be easily done in edify. It's nice to be able to give the user some status when doing these operations. There are modded versions of CWM that make the same thing possible in other ways, like simply writing to STDOUT or STDERR. This requires either a custom update_binary or recovery binary, though.

This works because communication between recovery and update_binary is through a file descriptor (pipe). Recovery runs update_binary with the FD as command line parameter. Because the shell script is run as a child process of update_binary, it can write the same commands to that FD (commands recovery listens for), because child processes inherited FD numbers and access rights.

So, all the script has to do is figure out which FD to write to, and pass it the right commands. Finding the FD isn't difficult, as it is passed on the command line and so is listed in the output of ps. Some grep and cut magic retrieve it. See the OUTFD=$(...) line. The right commands are defined in the functions at the top.

Note: this is all taken from my rfs<=>ext4 conversion script for CF-Root/ext4. Slightly adjusted, hopefully it still works as expected ;)

Enjoy!
 
Last edited:

RolluS

Senior Member
Sep 16, 2010
733
421
Near Paris
Hi Chainfire ;)

Thanks for your tricks, I'm using it for an almost bulletproof MTD flash script.

BTW, i'd like to call some set_perm commands. But set_perm isn't a recognized command for update-binary.

IDK if I'm clear.

Do you have some advise on that?
 

RolluS

Senior Member
Sep 16, 2010
733
421
Near Paris
BTW, i'd like to call some set_perm commands. But set_perm isn't a recognized command for update-binary.

I've sort this writing equivalent functions:

Code:
set_perm() { # same as set_perm command in updater-script, for example:
#
# set_perm 0 3003 02750 "/system/bin/netcfg"
#
# sets user:group to 0:3003 and perm to 02750 for the file /system/bin/netcfg
    $CHOWN $1:$2 $4
    $CHMOD $3 $4
}
set_perm_recursive() { # same as set_perm command in updater-script, for example:
#
# set_perm_recursive 0 2000 0755 0755 "/system/bin"
#
# sets uid:gid to 0:2000 and perm to 0755 for folders and 0755 for files recursively in /system/bin
    $CHOWN -R $1:$2 $5
	$CHMOD $3 $5
	#chmod recursive of folder
	$FIND $5/* -type d |while read folder; do
		$CHMOD $3 $folder
	done
	#chmod recursive of files
	$FIND $5/* -type f |while read file; do
		$CHMOD $4 $file
	done
}

There is no error handling (yet), so be carrefull when calling these functions
 

mai77

Senior Member
Nov 16, 2011
1,429
580
no FD, no output

This works because communication between recovery and update_binary is through a file descriptor (pipe). Recovery runs update_binary with the FD as command line parameter. Because the shell script is run as a child process of update_binary, it can write the same commands to that FD (commands recovery listens for), because child processes inherited FD numbers and access rights.

So, all the script has to do is figure out which FD to write to, and pass it the right commands. Finding the FD isn't difficult, as it is passed on the command line and so is listed in the output of ps. Some grep and cut magic retrieve it. See the OUTFD=$(...) line.

finding the FD is difficult on an SGY phone under MT 2.0 kernel, because is is not passed as command line param and therefore not in the ps output.

can anyone give an example of a working FD ?

the expression returns "" on my SGY, and no output is readable from script.

above zip as one file with the (.*) business removed from FD expression in myscript.sh

I don't see that ps gives me a "FD" in its output. whatever that really is. /tmp/recovery.log says that /sbin/recovery has no command line params either on SGY :(

from recov.log:

I:Set boot command "boot-recovery"
Command: "/sbin/recovery"


there is no FD in the command line ! where is it then ?
 

Attachments

  • update.zip
    160.9 KB · Views: 580
Last edited:

rovo89

Senior Recognized Developer
Jan 4, 2012
2,585
81,434
Thanks for this trick. What I wanted to do is to redirect stdout (and stderr) of my script to the UI. I know that you can see this in "show logs", but I wanted it to display right during the installation. Here is what I came up with:

Code:
#!/sbin/busybox ash
OUTFD=$(ps | grep -v "grep" | grep -o -E "update_binary(.*)" | cut -d " " -f 3);
/sbin/busybox ash $* 2>&1 |
while read -r line
do
    echo "ui_print $line" >&$OUTFD;
    echo "ui_print " >&$OUTFD;
done

If you save this as "scripts/stdoutwrapper.sh", you can do something like this:
Code:
package_extract_dir("scripts", "/tmp/update");
set_perm(0, 0, 0755, "/tmp/update/stdoutwrapper.sh");
run_program("/tmp/update/stdoutwrapper.sh", "/tmp/update/myscript.sh", "param1", "param2");

In myscript.sh, use any shell commands. The output will be redirected to the UI. Therefore, you should be able to write scripts that work both on the command line and in recovery without changes.
Please note: As all output will be printed, the tricks with set_progress etc. don't work. It would probably be possible to use a prefix to identify commands that should be executed, not printed, so you could do e.g. "echo '<#>set_progress 0.25'".
 

mai77

Senior Member
Nov 16, 2011
1,429
580
what value does OUTFD have? find out like below:

adb shell
ps

then /sbin/recovery might have PID 2166 on SGY phone

su
ls -l --color=never /proc/2166/fd

may give you e.g.

3 -> /dev/tty0

so 3 is your OUTFD ! :)

now grep it accordingly for the script. use MT kernel 2.0 on SGY for above capability
 
Last edited:
  • Like
Reactions: michelsberg

mai77

Senior Member
Nov 16, 2011
1,429
580
time saver

edify Scripts may be tested by executing update-binary directly:

update-binary version output package

An example would be:

update-binary 2 stdout /sdcard/update.zip
 

osm0sis

Senior Recognized Developer / Contributor
Mar 14, 2012
16,773
40,456
Halifax
GT-i9250
Google Nexus 4
Just noticed Chainfire's ui_print shell script in the latest SuperSU zip, and that lead me here.

Here are some updated versions (for CWM6?); ui_print and set_perm from SuperSU and the other 2 from my tinkering today:

Code:
OUTFD=$2;
ZIP=$3
ui_print() {
  echo -ne "ui_print $1\n" > /proc/self/fd/$OUTFD;
  echo -ne "ui_print\n" > /proc/self/fd/$OUTFD;
}
set_perm() {
  chown $1.$2 $4
  chown $1:$2 $4
  chmod $3 $4
}
show_progress() { echo "progress $1 $2" > /proc/self/fd/$OUTFD; }
set_progress() { echo "set_progress $1" > /proc/self/fd/$OUTFD; }

ex:
show_progress 1.34 0;
ui_print "Hello world!";
set_progress 0.5;
ui_print " "; #blank line
set_progress 1.34;
ui_print "Done!";

Also worth noting he just uses busybox unzip to extract files from the flashable zip directly and goes from there.

ex:
ui_print "Extracting files!"
cd /tmp
unzip -o "$ZIP"

what value does OUTFD have?
I put ui_print "Test: $OUTFD" in a script and the value of OUTFD appears to increase with every zip flashed.
Not much use knowing it in that case.
 
Last edited:

kl3

Senior Member
Sep 9, 2013
145
175
Nice work, I used this in my little zip. However, it doesn't work with TWRP. Is there any way to accomplish that?
 

shohag2018

Senior Member
Mar 4, 2014
364
34
sorry to put this here but could anyone direct me towards the cwm or TWRP version for "jxjpb" (asia) baseband pls?
 

_that

Recognized Developer / Inactive RC
Oct 2, 2012
4,821
4,211
Nice work, I used this in my little zip. However, it doesn't work with TWRP. Is there any way to accomplish that?

TWRP names the extracted update-binary "updater", so you can add something like this (or modify the regex in the existing line):

Code:
[ $OUTFD != "" ] || OUTFD=$(ps | grep -v "grep" | grep -o -E "updater(.*)" | cut -d " " -f 3)
 

osm0sis

Senior Recognized Developer / Contributor
Mar 14, 2012
16,773
40,456
Halifax
GT-i9250
Google Nexus 4
TWRP names the extracted update-binary "updater", so you can add something like this (or modify the regex in the existing line):

Code:
[ $OUTFD != "" ] || OUTFD=$(ps | grep -v "grep" | grep -o -E "updater(.*)" | cut -d " " -f 3)

Shouldn't be necessary with any reasonably current recovery. That was just a hack Chainfire wrote to get the FD back in the CWM3 days.

All that should be required (and has always worked for me) is to make the update-binary your shell script, then:
Code:
OUTFD=$2;

See my other updated commands above. And some zips I've made which should provide good references for people: Nexus Louder, Xposed Framework Installer, and Nexus BootUnlocker. All available in my Odds and Ends thread, linked in my sig. :)
 
Last edited:
  • Like
Reactions: _that

_that

Recognized Developer / Inactive RC
Oct 2, 2012
4,821
4,211
Shouldn't be necessary with any reasonably current recovery. That was just a hack Chainfire wrote to get the FD back in the CWM3 days.

All that should be required (and has always worked for me) is to make the update-binary your shell script, then:
Code:
OUTFD=$2;

Yes, that works if you replace the whole update-binary with a shell script, but this method is for scripts that are called from the updater-script when using a binary updater.

I've recently switched to a shell script as updater too, but I had to write my own zip signing program because busybox unzip was unable to extract files from a zip signed by SignApk.

See my other updated commands above. And some zips I've made which should provide good references for people: Nexus Louder, Xposed Framework Installer, and Nexus BootUnlocker. All available in my Odds and Ends thread, linked in my sig. :)

Wow, nice collection! I remember some other posts from you that were very helpful, thanks!
 

osm0sis

Senior Recognized Developer / Contributor
Mar 14, 2012
16,773
40,456
Halifax
GT-i9250
Google Nexus 4
Yes, that works if you replace the whole update-binary with a shell script, but this method is for scripts that are called from the updater-script when using a binary updater.

I've recently switched to a shell script as updater too, but I had to write my own zip signing program because busybox unzip was unable to extract files from a zip signed by SignApk.

Wow, nice collection! I remember some other posts from you that were very helpful, thanks!

Nice! My misunderstanding then. :)

And ah yes, the old "1 and 8" issue with unzip. Might be resolved in the latest busybox 1.22.1 but I'm not 100% about that, and it'll be awhile before that makes it into recoveries.

I'd love to get hold of your zip signer if that'd be okay. I have Chainfire's solution, but I was still running into some verification problems (I think) from the MinSignApk whole file resigning.
 
Last edited:

_that

Recognized Developer / Inactive RC
Oct 2, 2012
4,821
4,211
And ah yes, the old "1 and 8" issue with unzip. Might be resolved in the latest busybox 1.22.1 but I'm not 100% about that, and it'll be awhile before it makes it into recoveries.

Exactly! I learned a lot about the zip format while researching this.

I'd love to get hold of your zip signer if that'd be okay. I have Chainfire's solution, but I was still running into some verification problems (I think) from the MinSignApk whole file resigning.

Argh! I saw your original question in that thread but I never found the solution because there were so many pages in between...

I ended up with a hack that appears to resemble Chainfire's minsignapk, but I didn't need to write my own zipadjust. Basically I simply sign the zip normally with signapk, then I unpack and repack the whole archive using 7zip (bonus: slightly better compression), and then I run the whole-zip-signer on the archive.

Here is my SignWholeFile.jar - the source code is just too ugly to publish. :) View attachment SignWholeFile.jar If you find that it works better than Chainfire's version, I'll clean the source and release it.

This is the script I use for signing:

Code:
#!/bin/sh
KEYDIR=~/android/aosp/build/target/product/security
SIGNAPK=~/android/aosp/prebuilts/sdk/tools/lib/signapk.jar
SIGNWHOLEFILE=~/android/src/signapk/SignWholeFile.jar

java -jar $SIGNAPK -w $KEYDIR/testkey.x509.pem $KEYDIR/testkey.pk8 "$1" "$1.signed.zip"
mv "$1" "$1.unsigned"
signedzip=$(readlink -f "$1.signed.zip")
[ -d /tmp/signapk2 ] && rm -rf /tmp/signapk2
mkdir /tmp/signapk2
pushd /tmp/signapk2
unzip $signedzip
rm $signedzip
7z a -r -mx=9 $signedzip *
#7z a -r $signedzip *
popd
mv "$1.signed.zip" "$1"
java -jar $SIGNWHOLEFILE $KEYDIR/testkey.x509.pem $KEYDIR/testkey.pk8 "$1" "$1"
 

Top Liked Posts

  • There are no posts matching your filters.
  • 43
    Took me a few minutes to figure this out, so I thought to share :)

    This is taken from some scripts I use in CF-Root, you might need to change it slightly for other CWM3 kernels (like prefixing busybox to various commands, etc).

    updater-script

    Assumptions:
    - rootfs is mounted as rw, so we can write temporary files anywhere (ram disk)
    - you put a "myscript.sh" in the system folder inside the update

    All this script does is extract whatever you have in update.zip/system folder to /tmp/update, and run the myscript.sh file.

    Code:
    ui_print("Extracting files ...");
    package_extract_dir("system", "/tmp/update");
    set_perm(0, 0, 0755, "/tmp/update/myscript.sh");
    run_program("/tmp/update/myscript.sh");

    myscript.sh

    Assumptions:
    - all busybox commands are symlinked in /sbin, this is usually the case for CWM3 kernels

    Code:
    #!/sbin/busybox sh
    
    # get file descriptor for output
    OUTFD=$(ps | grep -v "grep" | grep -o -E "update_binary(.*)" | cut -d " " -f 3);
    
    # same as progress command in updater-script, for example:
    #
    # progress 0.25 10
    #
    # will update the next 25% of the progress bar over a period of 10 seconds
    
    progress() {
      if [ $OUTFD != "" ]; then
        echo "progress ${1} ${2} " 1>&$OUTFD;
      fi;
    }
    
    # same as set_progress command in updater-script, for example:
    #
    # set_progress 0.25
    #
    # sets progress bar to 25%
    
    set_progress() {
      if [ $OUTFD != "" ]; then
        echo "set_progress ${1} " 1>&$OUTFD;
      fi;
    }
    
    # same as ui_print command in updater_script, for example:
    #
    # ui_print "hello world!"
    #
    # will output "hello world!" to recovery, while
    #
    # ui_print
    #
    # outputs an empty line
    
    ui_print() {
      if [ $OUTFD != "" ]; then
        echo "ui_print ${1} " 1>&$OUTFD;
        echo "ui_print " 1>&$OUTFD;
      else
        echo "${1}";
      fi;
    }
    
    # --- example usage ---
    
    # empty line after "Extracting ..." from updater-script
    ui_print;
    
    # give the user some status
    ui_print "doing something (1 of 4)";
    # assume this won't take more than 30 seconds
    progress 0.25 30;
    # you'd do something useful here
    sleep 15s;
    # update status
    ui_print "- done with something (1 of 4)";
    # we're done, make sure the progress bar is at 25%
    set_progress 0.25;
    # empty line
    ui_print;
    
    # repeat this a few times ;)
    
    ui_print "doing something (2 of 4)";
    progress 0.25 30;
    sleep 15s;
    ui_print "- done with something (2 of 4)";
    set_progress 0.50;
    ui_print;
    
    ui_print "doing something (3 of 4)";
    progress 0.25 30;
    sleep 15s;
    ui_print "- done with something (3 of 4)";
    set_progress 0.75;
    ui_print;
    
    ui_print "doing something (4 of 4)";
    progress 0.25 30;
    sleep 15s;
    ui_print "- done with something (4 of 4)";
    set_progress 1.00;
    ui_print;
    
    # done !
    ui_print "done! rebooting!";

    How, what, why ?
    While updater-script is fine for a lot of things, like installing a new ROM and whatnot, anything sufficiently complicated still has to be done through shell scripts, because a great many things just cannot be easily done in edify. It's nice to be able to give the user some status when doing these operations. There are modded versions of CWM that make the same thing possible in other ways, like simply writing to STDOUT or STDERR. This requires either a custom update_binary or recovery binary, though.

    This works because communication between recovery and update_binary is through a file descriptor (pipe). Recovery runs update_binary with the FD as command line parameter. Because the shell script is run as a child process of update_binary, it can write the same commands to that FD (commands recovery listens for), because child processes inherited FD numbers and access rights.

    So, all the script has to do is figure out which FD to write to, and pass it the right commands. Finding the FD isn't difficult, as it is passed on the command line and so is listed in the output of ps. Some grep and cut magic retrieve it. See the OUTFD=$(...) line. The right commands are defined in the functions at the top.

    Note: this is all taken from my rfs<=>ext4 conversion script for CF-Root/ext4. Slightly adjusted, hopefully it still works as expected ;)

    Enjoy!
    4
    Thanks for this trick. What I wanted to do is to redirect stdout (and stderr) of my script to the UI. I know that you can see this in "show logs", but I wanted it to display right during the installation. Here is what I came up with:

    Code:
    #!/sbin/busybox ash
    OUTFD=$(ps | grep -v "grep" | grep -o -E "update_binary(.*)" | cut -d " " -f 3);
    /sbin/busybox ash $* 2>&1 |
    while read -r line
    do
        echo "ui_print $line" >&$OUTFD;
        echo "ui_print " >&$OUTFD;
    done

    If you save this as "scripts/stdoutwrapper.sh", you can do something like this:
    Code:
    package_extract_dir("scripts", "/tmp/update");
    set_perm(0, 0, 0755, "/tmp/update/stdoutwrapper.sh");
    run_program("/tmp/update/stdoutwrapper.sh", "/tmp/update/myscript.sh", "param1", "param2");

    In myscript.sh, use any shell commands. The output will be redirected to the UI. Therefore, you should be able to write scripts that work both on the command line and in recovery without changes.
    Please note: As all output will be printed, the tricks with set_progress etc. don't work. It would probably be possible to use a prefix to identify commands that should be executed, not printed, so you could do e.g. "echo '<#>set_progress 0.25'".
    4
    Just noticed Chainfire's ui_print shell script in the latest SuperSU zip, and that lead me here.

    Here are some updated versions (for CWM6?); ui_print and set_perm from SuperSU and the other 2 from my tinkering today:

    Code:
    OUTFD=$2;
    ZIP=$3
    ui_print() {
      echo -ne "ui_print $1\n" > /proc/self/fd/$OUTFD;
      echo -ne "ui_print\n" > /proc/self/fd/$OUTFD;
    }
    set_perm() {
      chown $1.$2 $4
      chown $1:$2 $4
      chmod $3 $4
    }
    show_progress() { echo "progress $1 $2" > /proc/self/fd/$OUTFD; }
    set_progress() { echo "set_progress $1" > /proc/self/fd/$OUTFD; }

    ex:
    show_progress 1.34 0;
    ui_print "Hello world!";
    set_progress 0.5;
    ui_print " "; #blank line
    set_progress 1.34;
    ui_print "Done!";

    Also worth noting he just uses busybox unzip to extract files from the flashable zip directly and goes from there.

    ex:
    ui_print "Extracting files!"
    cd /tmp
    unzip -o "$ZIP"

    what value does OUTFD have?
    I put ui_print "Test: $OUTFD" in a script and the value of OUTFD appears to increase with every zip flashed.
    Not much use knowing it in that case.
    4
    Nice work, I used this in my little zip. However, it doesn't work with TWRP. Is there any way to accomplish that?

    TWRP names the extracted update-binary "updater", so you can add something like this (or modify the regex in the existing line):

    Code:
    [ $OUTFD != "" ] || OUTFD=$(ps | grep -v "grep" | grep -o -E "updater(.*)" | cut -d " " -f 3)
    3
    Any ideas how to get the name of the zip update-binary is running from, via a .sh executed by the EDIFY?

    It's dead easy with a pure shell script update-binary ($3) but I'm having trouble figuring it out when doing EDIFY.


    Edit: Nevermind! Figured it out myself! :p

    Very similar to the OUTFD, we can just pull it from the updater commandline (duh $3!) through ps, but we have to go wide to get the whole thing. :good:

    Full path:
    Code:
    ps -w | grep -v "grep" | grep -oE "update(.*)" | cut -d" " -f4

    Just the zip name:
    Code:
    basename `ps -w | grep -v "grep" | grep -oE "update(.*)" | cut -d" " -f4`