#!/bin/bash ######################################################### # NeoTool 1.1 # By AntiSol, antisol (at) internode (dot) on (dot) net # GNU GPL licensed # # It works for me (tm), No warranty whatsoever: # No responsibility taken for furry animals harmed while using this software # # loosely based on a script by 'rorschach' # Thanks to: # Minh Ha Duong (suggestion to flash splash) # Charles Pax (suggestion to fetch dfu-util) # David Samblas (info on 'which' command) # Shawn "prjktdtnt" Thompson, William Kenworthy, and Al Johnson (multiple dfu-capable devices) ######################################################### # ChangeLog: # v1.1 - 15/09/2008 - 17/09/2008: # - Added ChangeLog # - Added ability to handle multiple DFU-capable devices # - Changed references to 'freerunner' to 'neo' # - name changed to NeoTool (despite implied connection with Keanu Reeves' crotch) # - Added 'Reset to defaults' option # - Added warning that flashing uboot on neo1973 may result in a paperweight # - several small bugfixes # - made backup functionality much more versatile: # - made backup filenames configurable # - Added ability to choose what to backup # - Added rootfs backup type option (jffs2 or tar) # - Added kernel backup method option (scp or dfu) # - Added ability to backup uboot and splash ######################################################### settings_file='/etc/frutil' tmp_error_log_path='/tmp/flash-error-log' tmpfile='/tmp/frutil.tmp' function find_dfu { #use 'which'... dfutils_path=`which dfu-util 2>/dev/null` if [ -z $dfutils_path ]; then #look in current directory... if [ -x './dfu-util' ]; then dfutils_path='./dfu-util' fi fi } function check_dfu { if [ ! -x "$dfutils_path" ]; then find_dfu if [ -z "$dfutils_path" ]; then #not found. zenity --title "dfu-util not found" --question --text "The dfu utility could not be found, or is not executable. Do you want to automagically download it from downloads.openmoko.org?" if [ $? = 0 ]; then clear echo 'Downloading dfu-util...' rm ./dfu-util > /dev/null 2>&1 wget http://downloads.openmoko.org/daily/dfu-util chmod a+x ./dfu-util >/dev/null dfutils_path='./dfu-util' else dfutils_path=$(zenity --file-selection --title="Locate the dfu-util executable:" --filename="$dfutils_path") fi if [ -z "$dfutils_path" ]; then echo "Cancelled!" exit fi #check selection... check_dfu fi fi } function perform_flash { # $1 - what we're flashing (for display) # $2 - dfu-params # $3 - file to flash if [ -n "$device" ]; then #use a hexcode #echo "flashing with hexcode: $dfutils_path -d $device $2 $3 2> $tmp_error_log_path" $dfutils_path -d $device $2 $3 2> $tmp_error_log_path else $dfutils_path $2 $3 2> $tmp_error_log_path fi; # add the following line to the '$dfutils_path' lines to get back the zenity progress dialog: # | zenity --progress --pulsate --percentage=0 --title "Flashing in progress.." --auto-close if [ "${PIPESTATUS[0]}" != "0" ];then zenity --error --text "Some error occured while flashing $1.\n\nError message:\n`cat $tmp_error_log_path`\n\nAny other pending flashes will be aborted." return 1 else #sleep to allow dfu to reset... sleep 2 fi } function handle_multiple_devices { #check for multiple DFU-capable devices $dfutils_path -l | grep Found > $tmpfile #for testing, simulate multiple devices. #cp ./testfile $tmpfile devcount="`wc -l < $tmpfile`" if [ "$devcount" -gt 1 ]; then #multiple devices attached, show a list. frcount=0 neocount=0 exec < $tmpfile while read line; do #a=$(($a + 1)) hex=`echo "$line" | awk '{print substr($3,2,13)}'` devlist="$devlist $hex" if [ "$hex" = "0x1d50:0x5119" ]; then devlist="$devlist FreeRunner" frcount=$(($frcount+1)) elif [ "$hex" = "0x1457:0x5119" ]; then devlist="$devlist Neo1973" neocount=$(($neocount+1)) else devlist="$devlist Unknown" fi; done if [ "$frcount" -gt 1 ] || [ "$neocount" -gt 1 ]; then zenity --info --title "Multiple Devices detected" --text "It has been determined that you have more than one of the same type of device attached (i.e two FreeRunners or two Neo1973s). Unfortunately there is no known way to specify which device to flash in this situation. Please disconnect the device you do not wish to flash, and try again." return -1 fi device=$(zenity --title "NeoTool" --text "Multiple DFU-capable devices were detected, please choose which one to flash:" --list --height=300 --width=400 --column "Hex Code" --column "Device Type" $devlist) if [ -z "$device" ]; then return -1 fi; fi; return 0 } function flash { if [ "$user" != root ]; then zenity --error --text "You can only flash your device if you are root!" return fi #set these to null incase we've already flashed in the same frutil session uboot="" kernel="" rootfs="" splash="" check_dfu ans=$(zenity --list --text "What do you wanna flash?" --title "NeoTool" --height=300 --checklist --column "Pick" --column "Option" TRUE Root-Filesystem TRUE Kernel FALSE Bootloader FALSE Splash) if [ -z "$ans" ]; then #handle cancel / no selection... #echo "Nothing to do!" return fi txt="The flashing will now start.\n\nNow is the time to connect your Neo is to the USB-Device and boot it into the Nor (for kernel and rootfs) or Nand (for u-boot) screen before proceeding.\nRemember that flashing a large image like a rootfs will take some time so don't abort it.\n\n" if [ -n "`echo $ans | grep Root-Filesystem`" ];then du_param_r='-a rootfs -R -D' rootfs=True img_file_r=$(zenity --file-selection --title="Select an image-file for the ROOTFS:") if [ "$img_file_r" = "" ]; then #echo "Cancelled!" return fi txt="$txt - Flashing rootfs with $img_file_r \n\n" fi if [ -n "`echo $ans | grep Kernel`" ];then du_param_k='-a kernel -R -D' kernel=True img_file_k=$(zenity --file-selection --title="Select an image-file for the KERNEL:") if [ "$img_file_k" = "" ]; then #echo "Cancelled!" return fi txt="$txt - Flashing kernel with $img_file_k \n\n" fi if [ -n "` echo $ans | grep Bootloader`" ];then du_param_u='-a u-boot -R -D' uboot=True img_file_u=$(zenity --file-selection --title="Select an image-file for UBOOT:") if [ "$img_file_u" = "" ]; then #echo "Cancelled!" return fi txt="$txt - Flashing uboot with $img_file_u \n\n" fi if [ -n "` echo $ans | grep Splash`" ];then du_param_s='-a splash -R -D' splash=True img_file_s=$(zenity --file-selection --title="Select an image-file for SPLASH:") if [ "$img_file_s" = "" ]; then #echo "Cancelled!" return fi txt="$txt - Flashing splash with $img_file_s \n\n" fi touch $tmp_error_log_path if [ "$uboot" = True ]; then txt="$txt\n\n*** WARNING! WARNING! DANGER WILL ROBINSON! ***\n\nFlashing u-boot on a Neo1973 may result in a paperweight! It is not recommended unless you have a debug board, and/or really know what you're doing!\n(This does not apply for the FreeRunner)" fi zenity --question --text "$txt" --title "Ready to flash" if [ $? != 0 ]; then return fi handle_multiple_devices if [ $? != 0 ]; then return fi if [ "$rootfs" = True ]; then echo echo "*** Flashing rootfs with $img_file_r..." echo perform_flash rootfs "$du_param_r" "$img_file_r" if [ $? != 0 ]; then return fi fi if [ "$kernel" = True ]; then echo echo "*** Flashing kernel with $img_file_k..." echo perform_flash kernel "$du_param_k" "$img_file_k" if [ $? != 0 ]; then return fi fi if [ "$uboot" = True ]; then echo echo "*** Flashing u-boot with $img_file_u..." echo perform_flash uboot "$du_param_u" "$img_file_u" if [ $? != 0 ]; then return fi fi if [ "$splash" = True ]; then echo echo "*** Flashing splash with $img_file_s..." echo perform_flash splash "$du_param_s" "$img_file_s" if [ $? != 0 ]; then return fi fi rm $tmp_error_log_path } function show_menu { ans=`zenity --title "NeoTool" --text "Choose an option" --list --height=300 --width=400 --column "" --column "" 1 "Setup NeoTool" 2 "Flash your Neo" 3 "Backup Your Neo" 4 "Quit"` } function setup_menu { sans=`zenity --title "NeoTool Configurification" --text "Choose an option" --list --height=320 --width=500 --column "" --column "" 1 "Locate dfu-util ($dfutils_path)" 2 "Set your device's IP ($FRIP)" 3 "Choose backup filename ($backupfn)" 4 "Setup date command ($datecmd)" 5 "Test settings defined in (3) and (4)" 6 "Save These Settings" 7 "Reset to defaults" 8 "Back to Main Menu"` } function setup { setup_menu while [ -n "$sans" ]; do case "$sans" in 1) check_dfu zenity --question --text "The dfu-utility was located at $dfutils_path, is this correct? (Cancel for no)" if [ $? != 0 ]; then dfutils_path=$(zenity --file-selection --title="Locate the dfu-util executable:" --filename="$dfutils_path") check_dfu fi ;; 2) FRIP=$(zenity --entry --text "Enter the IP / hostname for your Neo:" --entry-text "$FRIP") ;; 3) oldbackupfn="$backupfn" backupfn=$(zenity --entry --text "`printf "Enter the default filename to be used for backups.\nuse '{image}' for the Image type (kernel, rootfs, etc), \nand '{date}' for the date.\n the file extension will be added automatically."`" --entry-text "$backupfn" --title "Configure Backup Filename") if [ -z "$backupfn" ]; then backupfn=$oldbackupfn fi ;; 4) olddatecmd="$datecmd" datecmd=$(zenity --entry --text "`printf "Enter the 'date' command to be used in backup filenames.\nthe output of this command will replace the {date} tag in \nbackup filenames.\n\nThis is intended to allow you to use different date formats, \nbut may use any command which produces output to stdout.\n\nSee 'man date' for details on date formats"`" --entry-text "$datecmd" --title "Configure Date Command") if [ -z "$datecmd" ]; then datecmd=$olddatecmd fi ;; 5) tmp=${backupfn//"{date}"/"`$datecmd`"} tmp=${tmp//"{image}"/"rootfs"}".jffs2\n"${tmp//"{image}"/"kernel"}".bin" zenity --info --title "Example Backup File" --text "Using the rules defined by current settings, backup filenames will look something like this:\n\n$tmp" ;; 6) if [ "$user" != root ]; then zenity --error --text "You are not root, cannot save settings!" else echo "dfutils_path='$dfutils_path'" > $settings_file echo "FRIP='$FRIP'" >> $settings_file echo "backupfn='$backupfn'" >> $settings_file echo "datecmd='$datecmd'" >> $settings_file zenity --info --text "Settings saved to $settings_file" --title "Done." fi;; 7) if [ "$user" != root ]; then zenity --error --text "You are not root, cannot modify settings!" else rm -f $settings_file zenity --info --text "Settings will reset to default once you restart this tool" --title "Done." fi;; *) return;; esac setup_menu done } function show_dfu_msg { if [ "$user" != root ]; then zenity --error --text "The remaining backup tasks cannot be completed, because you are not running NeoTool as root!" return -1 fi if [ "$msg_shown" = "" ]; then zenity --question --title "About to backup using DFU" --text "The remaining backup portion(s) need access to the device's DFU, please reboot your device into NAND/NOR and press OK to proceed." if [ $? != 0 ]; then return -1 fi msg_shown=True handle_multiple_devices if [ $? != 0 ]; then return -1 fi return 0 fi } function ip_test { if [ "$iptested" != "" ]; then return 0 fi echo " - Checking validity of device IP ($FRIP)..." if [ -z "$FRIP" ]; then zenity --error --text "You have not setup your device's IP address / hostname! Do this via the settings menu and try again." return -1 fi #ping the IP to make sure it's valid... ping -c 1 $FRIP > /dev/null if [ $? != 0 ]; then zenity --error --text "There was no ping response from a device at $FRIP! Please ensure it is connected and that networking is working, and try again." return -1 fi #echo " - Checking for authorized_keys file..." if [ -z "`ssh root@$FRIP 'ls /home/root/.ssh/authorized_keys 2>/dev/null'`" ]; then zenity --question --text "it is recommended that you set up an authorized key on your device to eliminate the need to enter a password.\n\nIf you do not do this, you will likely be prompted for the device's password repeatedly during this procedure.\n\nThis is done by running 'ssh-keygen' on your host, and then copying the contents of ~/.ssh/id_rsa.pub on your host into home/root/.ssh/authorized_keys on your device. See the man pages for 'ssh' and 'ssh-keygen' for more info.\n\nDo you want to cancel the backup and do this now?\n(press cancel to proceed with the backup anyway)" #maybe even set it up automatically(?) if [ $? = 0 ]; then return -1 fi fi } function backup { msg_shown="" iptested="" clear echo "Performing backup of the following data: $backup_items" text="Backup Complete.\n" if [ "$backup_rootfs" = True ]; then echo " - Backing up rootfs at $FRIP..." case "$rootfs_type" in "jffs2") ip_test if [ $? != 0 ]; then return fi echo ' - Checking device for mkfs-jffs...' if [ -z "`ssh root@$FRIP 'opkg status mkfs-jffs2'`" ]; then zenity --error --text 'prerequisite mkfs-jffs2 is not installed on the device! \n\n do "opkg install mkfs-jffs2" and try again.' return fi echo " - Mounting flash at /var/tmp/root..." ssh root@$FRIP "mkdir /var/tmp/root; mount -t jffs2 /dev/mtdblock6 /var/tmp/root" #ssh root@$FRIP "mkdir /var/tmp/root" #ssh root@$FRIP "mount -t jffs2 /dev/mtdblock6 /var/tmp/root" echo ' - Performing backup... (this will take a while)' if [ -z "`which pv 2>/dev/null`" ]; then #pv is not installed, so don't use it... ssh root@$FRIP "mkfs.jffs2 -d /var/tmp/root -e 128 --pad --no-cleanmarkers -x lzo" > $FSIMG else ssh root@$FRIP "mkfs.jffs2 -d /var/tmp/root -e 128 --pad --no-cleanmarkers -x lzo" | pv -W > $FSIMG fi echo Cleanup... ssh root@$FRIP "umount /var/tmp/root; rmdir /var/tmp/root" ;; "tar") echo ' - Performing backup... (this will take a while)' if [ -z "`which pv 2>/dev/null`" ]; then #pv is not installed, so don't use it... ssh root@$FRIP "tar c /bin /etc /home /lib /opt /sbin /tmp /usr /var" > $FSIMG else ssh root@$FRIP "tar c /bin /etc /home /lib /opt /sbin /tmp /usr /var" | pv -W > $FSIMG fi ;; *) return;; esac text="$text\n - rootfs backed up to $FSIMG" fi if [ "$backup_kernel" = True ]; then case "$kernel_type" in scp) ip_test if [ $? != 0 ]; then return fi echo " - Backing up Kernel image..." scp root@$FRIP:/boot/uImage $KIMAGE ;; dfu) show_dfu_msg if [ $? != 0 ]; then return; fi echo " - Backing up Kernel image..." perform_flash kernel "-a kernel -R -U" "$KIMAGE" if [ $? != 0 ]; then return fi ;; *) return;; esac text="$text\n - kernel backed up to $KIMAGE" fi if [ "$backup_uboot" = True ]; then show_dfu_msg if [ $? != 0 ]; then return; fi echo " - Backing up u-boot image..." perform_flash uboot "-a u-boot -R -U" "$UIMAGE" if [ $? != 0 ]; then return fi text="$text\n - uboot backed up to $UIMAGE" fi if [ "$backup_splash" = True ]; then show_dfu_msg if [ $? != 0 ]; then return; fi echo " - Backing up splash image..." perform_flash splash "-a splash -R -U" "$SIMAGE" if [ $? != 0 ]; then return fi text="$text\n - splash backed up to $SIMAGE" fi #ssh root@$FRIP "umount /var/tmp/root" #ssh root@$FRIP "rmdir /var/tmp/root" zenity --info --title "Done." --text "$text" #"Done!\n\n- rootfs backed up to $FSIMG\n- Kernel backed up to $KIMAGE" } function show_backup_menu { IFS=";" bk_menu_items="1;Choose what to backup ($backup_items)" if [ "$backup_rootfs" = True ]; then bk_menu_items=$bk_menu_items";2;Choose rootfs backup type ($rootfs_type)" fi if [ "$backup_kernel" = True ]; then bk_menu_items=$bk_menu_items";3;Choose kernel backup method ($kernel_type)" fi bk_menu_items=$bk_menu_items";4;Customise Backup Filenames;5;Go!;6;Back to Main Menu" bans=`zenity --title "NeoTool" --text "Choose an option" --list --height=300 --width=400 --column "" --column "" $bk_menu_items` IFS=" " } function backup_what { backup_rootfs="" backup_kernel="" backup_uboot="" backup_splash="" backup_items=$(zenity --list --text "What do you wanna backup?" --title "NeoTool" --height=300 --checklist --separator="," --column "Pick" --column "Option" TRUE Root-Filesystem TRUE Kernel FALSE Bootloader FALSE Splash) if [ -z "$backup_items" ]; then return fi if [ -n "`echo $backup_items | grep Root-Filesystem`" ];then backup_rootfs=True fi if [ -n "`echo $backup_items | grep Kernel`" ];then backup_kernel=True fi if [ -n "`echo $backup_items | grep Bootloader`" ];then backup_uboot=True fi if [ -n "`echo $backup_items | grep Splash`" ];then backup_splash=True fi } function root_backup_type { old_rootfs_type=$rootfs_type rootfs_type=$(zenity --list --text "Choose rootfs backup type:\n\nIf you have modified your rootsf filename, this will reset it to the default." --column "Format" --column "Description" jffs2 "Flashable JFFS2 Image" "tar" "For SD card install") if [ -z "$rootfs_type" ]; then rootfs_type=$old_rootfs_type fi FSIMG=${backupfn//"{date}"/"`$datecmd`"}".$rootfs_type" FSIMG=${FSIMG//"{image}"/"rootfs"} #echo fsimg = $FSIMG } function kernel_backup_type { old_kernel_type=$kernel_type kernel_type=$(zenity --list --text "Choose kernel backup method:" --column "Method" --column "Description" scp "Copy via SCP" "dfu" "Extract using DFU") if [ -z "$kernel_type" ]; then kernel_type=$old_kernel_type fi } function show_bkfn_menu { bk_menu_items="" if [ "$backup_rootfs" = True ]; then bk_menu_items=$bk_menu_items";rootfs;$FSIMG" fi if [ "$backup_kernel" = True ]; then bk_menu_items=$bk_menu_items";kernel;$KIMAGE" fi if [ "$backup_uboot" = True ]; then bk_menu_items=$bk_menu_items";uboot;$UIMAGE" fi if [ "$backup_splash" = True ]; then bk_menu_items=$bk_menu_items";splash;$SIMAGE" fi bk_menu_items=${bk_menu_items:1}";;Return to Backup Menu" IFS=";" customise_what=$(zenity --list --height=300 --width=500 --text "Choose what filename you want to customise:" --column "Item" --column "Filename" $bk_menu_items) IFS=" " } function backup_filenames { show_bkfn_menu while [ -n "$customise_what" ]; do case "$customise_what" in #TODO: write code here... rootfs) old_filename=$FSIMG FSIMG=$(zenity --file-selection --save --title "Choose the filename for the rootfs backup:" --filename "$FSIMG") if [ -z "$FSIMG" ]; then $FSIMG=old_filename fi ;; kernel) old_filename=$KIMAGE KIMAGE=$(zenity --file-selection --save --title "Choose the filename for the kernel backup:" --filename "$KIMAGE") if [ -z "$KIMAGE" ]; then $KIMAGE=old_filename fi ;; uboot) old_filename=$UIMAGE UIMAGE=$(zenity --file-selection --save --title "Choose the filename for the u-boot backup:" --filename "$UIMAGE") if [ -z "$UIMAGE" ]; then $UIMAGE=old_filename fi ;; splash) old_filename=$SIMAGE SIMAGE=$(zenity --file-selection --save --title "Choose the filename for the splash backup:" --filename "$SIMAGE") if [ -z "$SIMAGE" ]; then $SIMAGE=old_filename fi ;; *) return;; esac show_bkfn_menu done } function backup_menu { rootfs_type=jffs2 kernel_type=scp FSIMG=${backupfn//"{date}"/"`$datecmd`"}".$rootfs_type" FSIMG=${FSIMG//"{image}"/"rootfs"} KIMAGE=${backupfn//"{date}"/"`$datecmd`"}".bin" KIMAGE=${KIMAGE//"{image}"/"kernel"} UIMAGE=${backupfn//"{date}"/"`$datecmd`"}".bin" UIMAGE=${UIMAGE//"{image}"/"u-boot"} SIMAGE=${backupfn//"{date}"/"`$datecmd`"}".gz" SIMAGE=${SIMAGE//"{image}"/"splash"} #echo fsimg = $FSIMG #echo kimage = $KIMAGE backup_what show_backup_menu while [ -n "$bans" ]; do case "$bans" in #TODO: write code here... 1) backup_what;; 2) root_backup_type;; 3) kernel_backup_type;; 4) backup_filenames;; 5) backup;; *) return;; esac show_backup_menu done } ################################################## # Main #check for zenity... #TODO: Once we're handling command-line params, this will have to happen later... if [ -z "`which zenity 2>/dev/null`" ]; then echo "it looks like zenity isn't installed, and this tool requires it!" exit fi #check user... user=`whoami` if [ "$user" != root ]; then zenity --title "You are not root!" --question --text 'This utility should be run as root. You can proceed, but you will not be able to save your settings or flash your Neo.\n\n Do you want to continue?' if [ $? != 0 ]; then echo "exiting." exit fi; fi #load settings file if it exists... if [ -e $settings_file ]; then echo "loading settings from $settings_file ..." . $settings_file fi check_dfu if [ -z "$backupfn" ]; then backupfn="./Neo_{image}_Backup_{date}" fi if [ -z "$datecmd" ]; then datecmd="date +%Y%m%d%H%M" fi if [ -z "$FRIP" ]; then FRIP="192.168.0.202" fi #main Menu... show_menu while [ -n "$ans" ]; do case "$ans" in 1) setup;; 2) flash;; 3) backup_menu;; *) exit;; esac show_menu done