#!/bin/sh # reflash # ensure the flash disk is not mounted # save configuration files # update the kernel # update the flashdisk # restore the saved configuration files # the set of configuration files is described by # /etc/default/conffiles. # # /etc/default/functions contains useful utility functions . /etc/default/functions load_functions sysconf # # NSLU2 flash layout is non-standard. case "$(machine)" in nslu2) isnslu2=1;; *) isnslu2=;; esac # # CHECKING FOR INPUT (ARGUMENTS ETC) # ---------------------------------- # # find the kernel and the new flash file system, an image file can # be used to specify both images. ffsfile= kfile= imgfile= while test $# -gt 0 do case "$1" in -k) shift test $# -gt 0 || { echo "reflash: -k: give the file containing the kernel image" >&2 exit 1 } kfile="$1" shift;; -[jr]) shift test $# -gt 0 || { echo "reflash: -j: give the file containing the root jffs2 image" >&2 exit 1 } ffsfile="$1" shift;; -i) shift test -n "$isnslu2" || { echo "reflash: -i: only supported on the LinkSys NSLU2" >&2 echo " use -k and -j to specify the kernel and root file system" >&2 exit 1 } test $# -gt 0 || { echo "reflash: -i: give the file containing the complete flash image" >&2 exit 1 } imgfile="$1" shift;; *) if test -n "$isnslu2" then echo "reflash: usage: $0 [-k kernel] [-j rootfs] [-i image]" >&2 else echo "reflash: usage: $0 [-k kernel] [-j rootfs]" >&2 fi echo " -k file: the new compressed kernel image ('zImage')" >&2 echo " -j file: the new root file system (jffs2)" >&2 test -n "$isnslu2" && echo " -i file: a complete flash image (gives both kernel and jffs2)" >&2 echo " The current jffs2 will be umounted if mounted." >&2 exit 1;; esac done # # Sanity check on the arguments (note that the first case can only fire # on NSLU2 because of the check for -i above.) if test -n "$imgfile" -a -n "$ffsfile" -a -n "$kfile" then echo "reflash: specify at most two files" >&2 echo " -i has both a kernel and rootfs, the kernel from -k and" >&2 echo " the rootfs from -j override the one in the image (if given)" >&2 exit 1 elif test -z "$imgfile" -a -z "$ffsfile" -a -z "$kfile" then echo "reflash: specify at least one file to flash" >&2 exit 1 fi # # Perform basic checks on the input (must exist, size must be ok). if test -n "$imgfile" then if test -r "$imgfile" then # read the partition table and from this find the offset # and size of Kernel and Flashdisk partitions. The following # devio command just dumps the partition table in a format # similar to /proc/mtd (but it outputs decimal values!) #NOTE: this uses a here document because this allows the while # loop to set the variables, a pipe would put the while in # a sub-shell and the variable settings would be lost. This # works in ash, no guarantees about other shells! while read size base name do case "$name" in Kernel) imgksize="$size" imgkoffset="$base";; Flashdisk) imgffssize="$size" imgffsoffset="$base";; esac done <') EOI # check the result test "$imgksize" -gt 0 -a "$imgkoffset" -ge 0 || { echo "reflash: $imgfile: failed to find Kernel partition in image" >&2 exit 1 } # the kernel is after a 16 byte header which holds the # values length,0,0,0 Get the true size. ktmp="$(devio "<<$imgfile" "L=$imgksize" "O=$imgkoffset" ' $( OL+$>! <= O A= b $( AL>! pr A $) 0 $) 0')" test "$ktmp" -gt 0 || { echo "reflash: $imgfile($imgkoffset,$imgksize): invalid kernel offset/size" >&2 exit 1 } # update the size and offset to these values (the offset is 16+ because # of the header). imgksize="$ktmp" imgkoffset="$(devio "O=$imgkoffset" 'pr O16+')" # just test the size for the rootfs test "$imgffssize" -gt 0 -a "$imgffsoffset" -ge 0 || { echo "reflash: $imgfile: failed to find Flashdisk" >&2 exit 1 } else echo "reflash: $imgfile: image file not found" >&2 exit 1 fi fi if test -n "$kfile" then if test ! -r "$kfile" then echo "reflash: $kfile: kernel file not found" >&2 exit 1 fi # the file values override anything from the image. imgksize="$(devio "<<$kfile" 'pr$')" imgkoffset=0 else kfile="$imgfile" fi if test -n "$ffsfile" then if test ! -r "$ffsfile" then echo "reflash: $ffsfile: root file system image file not found" >&2 exit 1 fi # values override those from the image imgffssize="$(devio "<<$ffsfile" 'pr$')" imgffsoffset=0 else ffsfile="$imgfile" fi # # INPUTS OK, CHECKING THE ENVIRONMENT # ----------------------------------- # basic setup. This could be parameterised to use different partitions! kpart=Kernel ffspart=Flashdisk # kdev= ksize=0 if test -n "$kfile" then # we have a new kernel kdev="$(mtblockdev $kpart)" test -n "$kdev" -a -b "$kdev" || { echo "reflash: $kpart($kdev): cannot find $kpart mtd partition." >&2 echo " check /proc/mtd, either the partition does not exist or there is no" >&2 echo " corresponding block device." >&2 exit 1 } ksize="$(devio "<<$kdev" 'pr$')" # # check the input file size test -n "$imgksize" -a "$imgksize" -gt 0 -a "$imgksize" -le "$ksize" || { echo "reflash: $kfile: bad Kernel size ($imgksize, max $ksize)" >&2 exit 1 } fi # ffsdev= ffssize=0 if test -n "$ffsfile" then ffsdev="$(mtblockdev $ffspart)" test -n "$ffsdev" -a -b "$ffsdev" || { echo "reflash: $ffspart($ffsdev): cannot find $ffspart mtd partition." >&2 echo " check /proc/mtd, either the partition does not exist or there is no" >&2 echo " corresponding block device." >&2 exit 1 } ffssize="$(devio "<<$ffsdev" 'pr$')" # # check the input file size test -n "$imgffssize" -a "$imgffssize" -gt 0 -a "$imgffssize" -le "$ffssize" || { echo "reflash: $ffsfile: bad Flashdisk size ($imgffsize, max $ffssize)" >&2 exit 1 } fi # # INPUTS OK, ENVIRONMENT OK, UMOUNT ANY EXISTING MOUNT OF THE FLASHDISK # --------------------------------------------------------------------- # This is only required if the device is going to be used if test -n "$ffsdev" then # -r causes this to fail if the flash device is mounted on / umountflash -r "$ffsdev" || exit 1 # # Everything is umounted, now remount on a temporary directory. ffsdir="/tmp/flashdisk.$$" mkdir "$ffsdir" || { echo "reflash: $ffsdir: failed to create temporary directory" >&2 exit 1 } # mountflash "$ffsdev" "$ffsdir" -o ro || { rmdir "$ffsdir" exit 1 } # # this is a utility function to make the cleanup easier errorexit() { umount "$ffsdir" && rmdir "$ffsdir" || echo "reflash: $ffsdir: temporary directory cleanup failed" >&2 exit 1 } # test -r "$ffsdir/etc/default/conffiles" || { echo "reflash: [/initrd]/etc/default/conffiles: file not found" >&2 errorexit } else errorexit() { exit 1 } fi # # PRESERVE EXISTING CONFIGURATION # ------------------------------- # Only required if the flash partition will be written if test -n "$ffsdev" then echo "reflash: preserving existing configuration file" >&2 # # This step produces /tmp/preserve.$$ and /tmp/cpio.$$, the former is # a list of the preserved configuration files together with the processing # option, the latter is a directory tree of the preserved files (a directory # tree makes the restore step easier.) saved=/tmp/cpio.$$ list=/tmp/preserve.$$ mkdir "$saved" || { echo "reflash: $saved: could not create save directory" >&2 errorexit } # # sysconf_save_conffiles sysconf_save_conffiles "$ffsdir" "$saved" "$list" || { echo "reflash: $saved: copy of saved configuration files failed" >&2 rm -rf "$saved" rm "$list" errorexit } # # If this umount fails do not try to continue... umount "$ffsdir" || { echo "reflash: $ffsdir: temporary mount point umount failed" >&2 echo " No changes have been made." >&2 rm -rf "$saved" rm "$list" exit 1 } fi # # FLASH THE NEW IMAGES # -------------------- echo "reflash: about to flash new image" >&2 # # There are four possibilities here - kernel only, flashdisk only, then # kernel&flashdisk in either one or two different files. The code used # to attempt to do everything in one step, but this complicates it, # so two steps are used (as required). A failure between the two # steps is a problem, but then so is a failure in a partial write. # Write the flashdisk first because this is larger (most likely to # fail). # # -p causes the progress indicator to be displayed progress=-p do_kernel() { local cmd if test -n "$isnslu2" then # NSLU2: write length,0,0,0 header, then fill cmd="wb L,4; fb 12,0; cpL" else # Other: just write the kernel bytes cmd="cpL" fi devio $progress "$@" "<<$kfile" ">>$kdev" ' # kernel is at imgkoffset[imgksize] ' "<= $imgkoffset" "L=$imgksize" "$cmd" ' # fill with 255 fb #t-,255' } # do_ffs() { devio $progress "$@" "<<$ffsfile" ">>$ffsdev" ' # rootfs is at imgffsoffset[imgffssize] ' "<= $imgffsoffset" "cp $imgffssize" ' # fill with 255 fb #t-,255' } # # check_status $? type file(offset,size) device # check the devio status code (given in $1) check_status() { case "$1" in 0) echo " done" >&2;; 1) echo " failed" >&2 echo "reflash: $3: flash $2 failed, no changes have been made to $4" >&2 if test "$2" = rootfs then rm -rf "$saved" rm "$list" exit 1 else echo "reflash: $2: continuing with rootfs changes" >&2 echo " NOTE: the old kernel is still in $4!" >&2 fi;; 3) echo " failed" >&2 echo "reflash: $3: WARNING: partial flash of $2 to $4 the system is unbootable" >&2 echo " Reflash from RedBoot or correct the problem here." >&2 if test "$2" = rootfs then exit 3 else echo "reflash: $2: continuing with rootfs changes" >&2 echo " NOTE: the kernel in $4 must be reflashed!" >&2 fi;; *) echo " failed" >&2 echo "reflash($1): $3: internal error flashing $2 to $4" >&2 exit $1;; esac } # if test -n "$ffsdev" then echo -n "reflash: writing rootfs to $ffsdev " >&2 do_ffs check_status $? rootfs "$ffsfile($imgffsoffset,$imgffssize)" "$ffsdev" fi # if test -n "$kdev" then echo -n "reflash: writing kernel to $kdev " >&2 do_kernel check_status $? kernel "$kfile($imgkoffset,$imgksize)" "$kdev" fi # # verify - this just produces a warning if test -n "$ffsdev" then echo -n "reflash: verifying new flash image " >&2 if do_ffs -v then echo " done" >&2 else echo " failed" >&2 echo "reflash: WARNING: rootfs flash image verification failed" >&2 echo " The system is probably unbootable." >&2 echo " System configuration files will be restored but this may fail" >&2 echo " Starting a shell for user recovery (exit to continue)" >&2 PS1='badflash$ ' sh -i <>/dev/tty >&0 2>&0 fi fi # if test -n "$kdev" then echo -n "reflash: verifying new kernel image " >&2 if do_kernel -v then echo " done" >&2 else echo " failed" >&2 echo "reflash: WARNING: kernel flash image verification failed" >&2 echo " The system is probably unbootable." >&2 echo " System configuration files will be restored in the rootfs." >&2 fi fi # # RESTORE THE OLD CONFIGURATION # ----------------------------- # If not write the rootfs none of the following is required - exit now. test -n "$ffsdev" || exit 0 # echo "reflash: restoring saved configuration files" >&2 # # the file /etc/.configured is the datestamp file used to ensure that # changed configuration files can be recognised. It is created by # /etc/rcS.d/S99finish on first boot (if it does not exist). We need # a timestamp earlier than any files we create so touch it here, this # also acts as a test on the mounted file system mountflash "$ffsdev" "$ffsdir" && :>"$ffsdir/etc/.configured" || { rmdir "$ffsdir" echo "reflash: mount of new flash root file system failed" >&2 if test -d "$ffsdir/etc" then echo " The file system does not seem to be writeable." >&2 echo " The mounted file system is in $ffsdir" >&2 fi echo " WARNING: the kernel and root file system have been reflashed," >&2 echo " HOWEVER the new root file system seems to be unuseable." >&2 echo " Saved configuration files are in $saved" >&2 echo " The list of saved configuration files is in $list" >&2 echo " You should determine the reason for the failed mount, mount the new" >&2 echo " file system and restore the configuration from $saved - it's just a" >&2 echo " matter of copying the saved files where required." >&2 exit 1 } # # sysconf_restore_conffiles restore="/tmp/restore.$$" sysconf_restore_conffiles "$ffsdir" "$saved" "$restore" <"$list" || { echo "reflash: $saved: restore of saved configuration files failed" >&2 echo " The new flash file system is mounted on $ffsdir" >&2 echo " The saved files are in $saved and the list in $list, the list of" >&2 echo " files selected for restore is in $restore" >&2 echo " You should restore any required configuration from $saved," >&2 echo " then umount $ffsdir and reboot." >&2 exit 1 } # # remove the copied files (i.e. the ones which were preserved) ( cd "$saved" exec rm $(cat "$restore") ) rm "$restore" # # clean up, files left in $saved need to be handled by the user files="$(find "$saved" ! -type d -print)" if test -n "$files" then echo "reflash: the following saved configuration files remain:" >&2 echo "$files" >&2 echo "The full list of preserved files is in $list. To alter the" >&2 echo "new root file system use the command:" >&2 echo "" >&2 echo " mount -t jffs2 $ffsdev /mnt" >&2 echo "" >&2 echo "The saved files are in the temporary directory, they will not" >&2 echo "be retained across a system boot. Copy them elsewhere if you" >&2 echo "are unsure whether they are needed" >&2 else rm -rf "$saved" rm "$list" fi # # now the final umount if umount "$ffsdir" then rmdir "$ffsdir" echo "reflash: system upgrade complete. Reboot to continue." >&2 exit 0 else echo "reflash: $ffsdir: temporary mount point umount failed" >&2 echo " ALL changes have been made successfully, however the umount of" >&2 echo " the new root file system has failed. You should determine the" >&2 echo " cause of the failure, umount $ffsdir, then reboot the system (this" >&2 echo " will use the upgraded kernel and root file system)" >&2 exit 1 fi