busybox & Android: la command line facile

Nei precedenti articoli abbiamo visto due tecniche per aggiungere nuovi programmi C su di un sistema Android (l’emulatore nello specifico); abbiamo visto come compilare un programma nativamente con la toolchain di Android e come compilare un programma con una toolchain esterna.

In questo articolo cercherò di installare una serie di programmi di utilità per semplificarmi la vita quando utilizzo la command line, in particolare vorrei installare una shell un po’ più completa di quella di default, un editor di file (io uso vi) ed altri programmi di utilità come awk, find, ecc..

Una soluzione semplicistica potrebbe essere quella di prendersi i sorgenti di ogni tool che desideriamo avere e ricompilarseli uno per uno… ma questo, oltre ad essere un lavoraccio, potrebbe creare dei problemi nel caso decidessimo di ricompilare tutti i programmi in maniera statica: sicuramente la via più veloce ma quella che occuperebbe di sicuro troppo spazio nel filesystem.

La soluzione più efficiente e semplice, in questo caso, è quella di utilizzare busybox che include al suo interno tutti i programmi di utilità che un amante della command line può utilizzare.

Cosa è

Busybox infatti è un insieme di programmi, tutti contenuti in uno stesse eseguibile, che mimano il comportamento dei rispettivi tool che si trovano in una normale distribuzione GNU/Linux.

Se avete mai avuto modo di usare la command line conoscerete senz’altro i programmi ls, ps, grep, find, awk, ecc., bene busybox non è altro che un programma unico che, a seconda del nome con cui viene invocato, si comporta come il tool equivalente. Per fare un esempio, se voi chiamate il file che contiene il programma busybox con il nome ls, allora quando lo lanciate otterrete lo stesso output del comand ls standard (o comunque un outout che ci si avvicina parecchio), stessa cosa se gli date il nome ps o grep, ecc..

Il modo per ottenere questo funzionamento è banale per chi conosce il C (e non lo affronto qui), ma quello che ognuno di voi magari si sta chiedendo è: «ma in questo modo come faccio ad avere tutti i programmi in una sola volta? Ogni volta gli devo cambiare nome?».

Ovviamente la risposta è no, e il come questo venga risolto è ancora banale per chi conosce i sistema UNIX: si usano i link simbolici. Un link simbolico non è altro che un file che punta ad un altro, ogni qual volta che un programma tenta di accedere ad uno di questi file, in realtà è come se accedesse al file puntato. Quindi, per risolvere il nostro problema con busybox, basta che il file eseguibile chiamato busybox, e che contiene al suo interno tutto il codice dei tool che vogliamo usare, venga di volta in volta puntato da un link simbolico avente il nome del comando da emulare.

Quindi se faccio:

$ ln -s busybox ls
$ ./ls

otterrò in uscita l’output del comando ls pur eseguendo il comando busybox.

Compilare i sorgenti

Bene, chiarito cosa è e cosa fa busybox vediamo come compilarlo per il nostro sistema Android. Io ho scaricato la versione 1.17.1 e l’ho esplosa:

$ tar xfj busybox-1.17.1.tar.bz2
$ cd busybox-1.17.1

Poi, con il comando make menuconfig (lo stesso che si usa per Linux), configuro il pacchetto. Dovreste ottenere un menu dal quale è possibile scegliere quali applicazioni si desiderano compilare dentro busybox ed alcune opzioni di funzionamento e/o compilazione. Fate le vostre scelte e quindi salvate la configurazione.

Prima di iniziare a compilare si vede subito, guardando il sistema di compilazione di busybox, che non sarà molto agevole utilizzare la compilazione nativa di Android, quindi utilizzeremo una compilazione statica. Da notare che, in questo caso, ciò non è affatto un problema perché, pur essendo programmi diversi, questi stanno tutti dentro un unico file, quindi l’overhead dovuto alla compilazione statica è sempre quello di un programma solo.

Per compilare staticamente dobbiamo andare nel menu Busybox Settings -> Build Options e ripostare le impostazioni seguenti:

[*] Build BusyBox as a static binary (no shared libs) 
[ ] Force NOMMU build (NEW)
[*] Build with Large File Support (for accessing files > 2 GB) (NEW)
(arm-linux-gnueabi-) Cross Compiler prefix
()  Additional CFLAGS (NEW)

Quindi diamo il solito make. Una volta finito dovremmo ottenere:

$ file busybox
busybox: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for GNU/Linux 2.6.14, stripped

Ok, ora basta copiare il file nel nostro sistema e provarlo:

$ adb push busybox /data/local/bin/
2158 KB/s (1911836 bytes in 0.864s)
$ adb shell
# cd /data/local/bin/
# chmod 777 busybox
# ./busybox
BusyBox v1.17.1 (2010-08-11 20:22:20 CEST) multi-call binary.
Copyright (C) 1998-2009 Erik Andersen, Rob Landley, Denys Vlasenko
and others. Licensed under GPLv2.
See source distribution for full notice.

Usage: busybox [function] [arguments]…
or: function [arguments]…

BusyBox is a multi-call binary that combines many common Unix
utilities into a single executable.  Most people will create a
link to busybox for each function they wish to use and BusyBox
will act like whatever it was invoked as.

Currently defined functions:
[, [[, acpid, addgroup, adduser, adjtimex, arp, arping, ash, awk,
basename, beep, blkid, bootchartd, brctl, bunzip2, bzcat, bzip2, cal,
cat, catv, chat, chattr, chgrp, chmod, chown, chpasswd, chpst, chroot,
chrt, chvt, cksum, clear, cmp, comm, cp, cpio, crond, crontab, cryptpw,
cttyhack, cut, date, dc, dd, deallocvt, delgroup, deluser, depmod,
devmem, df, dhcprelay, diff, dirname, dmesg, dnsd, dnsdomainname,
dos2unix, du, dumpkmap, dumpleases, echo, ed, egrep, eject, env,
envdir, envuidgid, ether-wake, expand, expr, fakeidentd, false, fbset,
fbsplash, fdflush, fdformat, fdisk, fgconsole, fgrep, find, findfs,
flock, fold, free, freeramdisk, fsck, fsck.minix, fsync, ftpd, ftpget,
ftpput, fuser, getopt, getty, grep, gunzip, gzip, halt, hd, hdparm,
head, hexdump, hostid, hostname, httpd, hush, hwclock, id, ifconfig,
ifdown, ifenslave, ifplugd, ifup, inetd, init, insmod, install, ionice,
ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule, iptunnel,
kbd_mode, kill, killall, killall5, klogd, last, length, less, linux32,
linux64, linuxrc, ln, loadfont, loadkmap, logger, login, logname,
logread, losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lspci, lsusb,
lzcat, lzma, lzop, lzopcat, makedevs, makemime, man, md5sum, mdev,
mesg, microcom, mkdir, mkdosfs, mke2fs, mkfifo, mkfs.ext2, mkfs.minix,
mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modinfo, modprobe, more,
mount, mountpoint, mt, mv, nameif, nc, netstat, nice, nmeter, nohup,
nslookup, ntpd, od, openvt, passwd, patch, pgrep, pidof, ping, ping6,
pipe_progress, pivot_root, pkill, popmaildir, poweroff, printenv,
printf, ps, pscan, pwd, raidautorun, rdate, rdev, readahead, readlink,
readprofile, realpath, reboot, reformime, renice, reset, resize, rev,
rm, rmdir, rmmod, route, rpm, rpm2cpio, rtcwake, run-parts, runlevel,
runsv, runsvdir, rx, script, scriptreplay, sed, sendmail, seq, setarch,
setconsole, setfont, setkeycodes, setlogcons, setsid, setuidgid, sh,
sha1sum, sha256sum, sha512sum, showkey, slattach, sleep, smemcap,
softlimit, sort, split, start-stop-daemon, stat, strings, stty, su,
sulogin, sum, sv, svlogd, swapoff, swapon, switch_root, sync, sysctl,
syslogd, tac, tail, tar, tcpsvd, tee, telnet, telnetd, test, tftp,
tftpd, time, timeout, top, touch, tr, traceroute, traceroute6, true,
tty, ttysize, tunctl, udhcpc, udhcpd, udpsvd, umount, uname, unexpand,
uniq, unix2dos, unlzma, unlzop, unxz, unzip, uptime, usleep, uudecode,
uuencode, vconfig, vi, vlock, volname, wall, watch, watchdog, wc, wget,
which, who, whoami, xargs, xz, xzcat, yes, zcat, zcip

#

Perfetto! Funziona.

Abilitare i comandi

A questo punto dobbiamo creare i link simbolici dei comandi che vogliamo usare. Busybox, in realtà, funzionerebbe anche senza; è possibile infatti simulare il comando ls con:

# ./busybox ls -l /
total 130
drwxrwx—    1 1000     2001          2048 Jul 15 13:19 cache
dr-x——    2 0        0                0 Aug 11 18:37 config
lrwxrwxrwx    1 0        0               17 Aug 11 18:37 d -> /sys/kernel/debug
drwxrwx–x    1 1000     1000          2048 Jul 15 13:20 data
-rw-r–r–    1 415      50             118 Aug  6 09:43 default.prop
drwxr-xr-x   10 0        0             2000 Aug 11 18:37 dev
lrwxrwxrwx    1 0        0               11 Aug 11 18:37 etc -> /system/etc
-rwxr-x—    1 415      50          103112 Aug  6 09:43 init
-rwxr-x—    1 415      50            1677 Aug  6 09:43 init.goldfish.rc
-rwxr-x—    1 415      50           12231 Aug  6 09:45 init.rc
dr-xr-xr-x   52 0        0                0 Jan  1  1970 proc
drwx——    2 0        0                0 Jan 28  2010 root
drwxr-x—    2 415      50               0 Aug  6 09:43 sbin
d———    2 1000     1000             0 Aug 11 18:37 sdcard
drwxrwxrwt    2 0        0               40 Aug 11 18:37 sqlite_stmt_journals
drwxr-xr-x   13 0        0                0 Jan  1  1970 sys
drwxr-xr-x    1 0        0             2048 May  6 16:16 system

ma è chiaro che avere il comando ls invece che mettere sempre il prefisso busybox è più pratico! Per ottenere questo, come già detto, basta dare il comando:

# ln -s busybox ls

Ma per crearli tutti in una volta usiamo il comando:

# /data/local/bin/busybox –install -s /data/local/bin/

Resta ancora il fatto però che dobbiamo sempre scrivere il path completo per eseguire un comando e questo perché la variabile PATH non è correttamente impostata:

# echo $PATH
/sbin:/system/sbin:/system/bin:/system/xbin

Per risolvere questo problema basta allora usare il comando:

# export PATH=/data/local/bin:$PATH

Ora scrivendo semplicemente comando voluto verrà eseguito quello di busybox invece di quello fornito nativamente da Android. Ad esempio, se diamo il comando sh otteniamo la shell di busybox che, rispetto a quella nativa di Android, ha molte più funzionalità, in primis l’auto completamento dei comandi.

Modificare init

Direi che, a questo punto, il risultato è buono: abbiamo una pletora di nuove utility da utilizzare dalla command line; ma possiamo fare ancora di più! Possiamo, ad esempio, far sì che il nostro sistema imposti automaticamente al boot la variabile PATH in modo da non doverla reimpostare tutte le volte.

Per fare tutto ciò occorre però modificare il file init.rc di Android.

Il file init.rc viene letto dal sistema al boot ed è il responsabile di tutte le impostazioni e dello start up dei programmi di utilità. La sua sintassi è semplice e lascio quindi il lettore curioso il compito di interpretarlo; io mi limiterò solo ad illustrare le modifiche da fare in modo di ottenere quello che ci siamo prefissi.

Una nota: le modifiche che andrò a fare da ora in poi sono riferite solo all’emulatore poiché quest’ultimo è il mio unico sistema di test, nel caso aveste un vero e proprio telefonino con su Android dovrete studiare da soli il sistema per come fare le modifiche necessarie.

Osserviamo subito che il file init.rc si trova nella root directory e che quest’ultima è montata via rootfs:

# mount      
rootfs / rootfs ro 0 0

Quindi utilizzare il nostro nuovo fiammante vi sul file non ci risolve il problema perché al nuovo avvio della macchina le impostazioni verrebbero perse: dobbiamo allora modificare la partizione di flash che mantiene il ramdisk iniziale.

Come già esplicitato prima io uso l’emulatore ed esso memorizza il ramdisk del rootfs in una directory diversa a seconda del target usato nell’emulatore. Quando ho creato l’emulatore ho scelto il target 7, infatti:

$ cat ~/.android/avd/my_phone.ini
target=android-7
path=/home/giometti/.android/avd/my_phone.avd

quindi, avendo installato l’SDK in /opt/android-sdk-linux_86 il ramdisk si trova nella directory:

$ cd /opt/android-sdk-linux_86/platforms/android-7/images
$ ls

NOTICE.txt  kernel-qemu*  ramdisk.img  system.img  userdata.img 

Il formato del file ramdisk.img è un semplice archivio cpio compresso, quindi per modificarlo prima va esploso:

$ gunzip -S.img ramdisk.img
$ mkdir ramdisk.d && cd ramdisk.d
$ cpio -i -F ../ramdisk
494 blocks

Ora nella directory ramdisk.d abbiamo tutti i file del ramdisk:

$ ls         
data/          dev/   init.goldfish.rc*    proc/  sys/
default.prop  init*  init.rc*        sbin/  system/

modifichiamo il file init.rc aggiungendo la directory /data/local/bin nella definizione della variabile PATH come segue

# setup the global environment
    export PATH /data/local/bin:/sbin:/system/sbin:/system/bin:/system/xbin

Ora basta ricostruire il file immagine del ramdisk con il nuovo contenuto. Prima creiamo il file della lista dei file del ramdisk (serve per cpio):

$ cpio -t -F ../ramdisk > ../ramdisk.list

e quindi ricreiamo il ramdisk:

$ cat ../ramdisk.list | cpio -o -H newc -O ../ramdisk
494 blocks
$ cd ..
$ gzip -S.img ramdisk

In fine facciamo reboot. Una volta ripartiti se ci ricolleghiamo al sistema ed esaminiamo il contenuto della variabile PATH:

# echo $PATH
/data/local/bin:/sbin:/system/sbin:/system/bin:/system/xbin

abbiamo ottenuto quello che volevamo: ora possiamo lanciare i nuovi comandi di busybox direttamente scrivendone il nome!

{loadposition donate}

Su Rodolfo Giometti

Ingegnere informatico libero professionista ed esperto GNU/Linux offre supporto per: - device drivers; - sistemi embedded; - sviluppo applicazioni industriali per controllo automatico e monitoraggio remoto; - corsi di formazione dedicati. Manutentore del progetto LinuxPPS (il sottosistema Pulse Per Second di Linux) contribuisce attivamente allo sviluppo del kernel Linux con diverse patch riguardanti varie applicazioni del kernel e dispositivi (switch, fisici di rete, RTC, USB, I2C, network, ecc.). Nei 15+ anni di esperienza su Linux ha lavorato con le piattaforme x86, ARM, MIPS & PowerPC.

Lascia un commento

Utilizzando il sito, accetti l'utilizzo dei cookie da parte nostra. maggiori informazioni

Questo sito utilizza i cookie per fonire la migliore esperienza di navigazione possibile. Continuando a utilizzare questo sito senza modificare le impostazioni dei cookie o clicchi su "Accetta" permetti al loro utilizzo.

Chiudi