#!/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 # # CHECKING THE ENVIRONMENT # ------------------------ # basic setup. This could be parameterised to use different partitions! ffspart=Flashdisk kpart=Kernel # 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$')" # 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$')" # # find the device number of the flash partition then make sure it isn't # mounted anywhere. ffsno="$(devio "<<$ffsdev" prd)" test -n "$ffsno" -a "$ffsno" -ge 0 || { echo "reflash: $ffsdev: device number $ffsno is not valid, cannot continue." >&2 exit 1 } # # Make sure that Flashdisk isn't mounted on / if test "$(devio "<&2 exit 1 fi # # CHECKING FOR INPUT (ARGUMENTS ETC) # ---------------------------------- # # find the kernel and the new flash file system, they default to /boot/zImage # and /boot/rootfs.jffs2, an image file can be used to specify both images. ffsfile=/boot/rootfs.jffs2 kfile=/boot/zImage 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" imgfile= shift;; -j) shift test $# -gt 0 || { echo "reflash: -j: give the file containing the root jffs2 image" >&2 exit 1 } ffsfile="$1" imgfile= shift;; -i) shift test $# -gt 0 || { echo "reflash: -i: give the file containing the complete flash image" >&2 exit 1 } imgfile="$1" shift;; *) echo "reflash: usage: $0 [-k kernel -j rootfs] | -i image" >&2 echo " -k [$kfile]: the new compressed kernel image ('zImage')" >&2 echo " -j [$ffsfile]: the new root file system (jffs2)" >&2 echo " -i image: a complete flash image (gives both kernel and jffs2)" >&2 echo " The current jffs2 will be umounted if mounted." >&2 exit 1;; esac done # # Perform basic checks on the input (must exist, size must be ok). if test -n "$imgfile" then kfile= ffsfile= 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 <&2 exit 1 } test -n "$imgffssize" -a "$imgffssize" -gt 0 -a "$imgffssize" -le "$ffssize" || { echo "reflash: $imgfile: bad flashdisk size ($imgffssize, max $ffssize)" >&2 exit 1 } else echo "reflash: $imgfile: image file not found" >&2 exit 1 fi else if test -r "$kfile" then # check the size s="$(devio "<<$kfile" 'pr$')" test -n "$s" -a "$s" -gt 0 -a "$s" -le "$ksize" || { echo "reflash: $kfile: bad size ($s, max $ksize)" >&2 exit 1 } else echo "reflash: $kfile: kernel file not found" >&2 exit 1 fi if test -r "$ffsfile" then s="$(devio "<<$ffsfile" 'pr$')" test -n "$s" -a "$s" -gt 0 -a "$s" -le "$ffssize" || { echo "reflash: $ffsfile: bad size ($s, max $ffssize)" >&2 exit 1 } else echo "reflash: $ffsfile: root file system image file not found" >&2 exit 1 fi fi # # INPUTS OK, UMOUNT ANY EXISTING MOUNT OF THE FLASHDISK # ---------------------------------------------------- echo "reflash: umounting any existing mount of $ffsdev" >&2 # # check each mount point, do this last first because otherwise nested # mounts of ffsdev cannot be umounted. ffs_umount() { local device mp type options stuff read device mp type options stuff test -z "$device" && return 0 # handle following entries first ffs_umount || return 1 # handle this entry case "$type" in jffs2) test "$(devio "<<$mp/etc/init.d/sysconfsetup" prd)" -ne "$ffsno" || umount "$mp" || { echo "reflash: $mp: unable to umount $ffsdev" >&2 return 1 };; esac return 0 } # ffs_umount &2 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 "$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 } # # PRESERVE EXISTING CONFIGURATION # ------------------------------- 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 } # ( cd "$ffsdir" find etc/*.conf $(sed 's!^/!!' usr/lib/ipkg/info/*.conffiles) ! -type d -newer etc/.configured -print | sed 's/^/diff /' exec sed 's/#.*$//;/^[ ]*$/d' etc/default/conffiles ) | sed 's!^/*!!' | awk '{ op=$1; $1=""; file[$0]=op } END{ for (f in file) if (file[f] != "ignore") print file[f] f }' | while read op file do if test -e "$ffsdir/$file" then echo "$op $file" >&3 echo "$file" fi done 3>"$list" | (cd "$ffsdir"; exec cpio -p -d -m -u "$saved") || { 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 } # # FLASH THE NEW IMAGES # -------------------- echo "reflash: about to flash new images" >&2 # # If the images are in separate files do this in two steps, kernel then # rootfs (this seems like it might be safer if the first fails). For an # image file use one step but still write the kernel first. do_devio() { if test -r "$imgfile" then # image file case - copy the whole of the image partition, # which already includes headers (etc). devio "$@" "<<$imgfile" ">>$ffsdev" ">>$kdev" ' # kernel is at imgkoffset[imgksize] ' "<= $imgkoffset" "cp $imgksize" ' fb #t-,255 ' "<>$kdev" ">>$ffsdev" ' # rootfs is at imgffsoffset[imgffssize] ' "<= $imgffsoffset" "cp $imgffssize" ' fb #t-,255' elif test -r "$kfile" -a -r "$ffsfile" then # use separate files, do this in one command to be sure # that everything can be opened at the start devio "$@" "<<$ffsfile" ">>$ffsdev" "<<$kfile" ">>$kdev" ' # kernel write length+16,0,0,0 header, then fill wb $16+,4 fb 12,0 cp $ fb #t-,255 ' "<>$kfile" "<>$kdev" "<<$ffsfile" ">>$ffsdev" ' # rootfs, write the whole image, fill if necessary cp $ fb #t-,255' else # oops, my checking was wrong... echo "reflash: internal error: image files not found!" >&2 echo " No changes have been made." >&2 exit 2 fi } # echo "reflash: writing kernel to $kdev and rootfs to $ffsdev..." >&2 do_devio st=$? case "$st" in 0) ;; 1) echo "reflash: flash of new images failed, no changes have been made" >&2 rm -rf "$saved" rm "$list" exit 1;; 3) echo "reflash: WARNING: partial flash, the system is unbootable" >&2 echo " Reflash from RedBoot or correct the problem here." >&2 exit 3;; *) echo "reflash($st): internal error" >&2 exit $st;; esac echo " ... done" >&2 # # verify - this just produces a warning echo "reflash: verifying new flash image..." >&2 do_devio -v || { echo "reflash: WARNING: 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 } echo " ... done" >&2 # # RESTORE THE OLD CONFIGURATION # ----------------------------- 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 "$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 } # # verify file # this is called with the name of a 'diff' file which is, indeed, # different and with all the std streams connected to the tty. It # returns a status code to say whether (0) or not (1) to copy the # file over. # verify_help() { echo "Please specify how to handle this file or link, the options are as follows," echo "two character abbreviations may be used:" echo echo " keep: retain the old file, overwrite the new flash image file" echo " upgrade: retain the new file, the old (saved) file is not used" echo " diff: display the differences between the old and the new using diff -u" echo " shell: temporarily start an interactive shell (sh -i), exit to continue" echo " skip: ignore this file for the moment. The file is left in the directory" echo " $saved and many be handled after this script has completed" } # verify() { local command file file="$1" echo "reflash: $file: configuration file changed." verify_help "$file" while : do echo -n "option: " read command case "$command" in ke*) return 0;; up*) rm "$saved/$file" return 1;; di*) echo "DIFF OLD($saved) NEW($ffsdir)" diff -u "$saved/$file" "$ffsdir/$file";; sh*) PS1="$file: " sh -i;; sk*) return 1;; *) verify_help "$file";; esac done } # the same, but for a link verify_link() { local command link link="$1" echo "reflash: $link: configuration link changed." verify_help "$link" while : do echo -n "option: " read command case "$command" in ke*) return 0;; up*) rm "$saved/$link" return 1;; di*) echo "DIFF:" echo "OLD($saved): $link -> $(readlink "$saved/$link")" echo "NEW($ffsdir): $link -> $(readlink "$ffsdir/$link")";; sh*) PS1="$link: " sh -i;; sk*) return 1;; *) verify_help "$link";; esac done } # while read op file do # handle .configured specially (to preserve the original datestamp) if test "$file" = "etc/.configured" then # this should definately not fail because of the test above! if cp -a "$saved/$file" "$ffsdir/$file" then echo "$file" >&3 else echo "reflash: $file: timestamp copy failed (ignored)" >&2 fi elif test -h "$saved/file" -o -h "$ffsdir/$file" then # new or old symbolic link if test -h "$saved/$file" -a -h "$ffsdir/$file" && test "$(readlink "$saved/$file")" = "$(readlink "$ffsdir/$file")" then # no change echo "$file" >&3 else # assume a change regardless case "$op" in preserve) echo "$file" echo "$file" >&3;; diff) # need user input if verify_link "$file" <>/dev/tty >&0 2>&0 then echo "$file" echo "$file" >&3 fi;; esac fi else # only overwrite if necessary if test -e "$ffsdir/$file" && cmp -s "$saved/$file" "$ffsdir/$file" then # do not overwrite echo "$file" >&3 elif test ! -e "$ffsdir/$file" then # always preserve echo "$file" echo "$file" >&3 else case "$op" in preserve) echo "$file" echo "$file" >&3;; diff) # the files are different, get user input if verify "$file" <>/dev/tty >&0 2>&0 then echo "$file" echo "$file" >&3 fi;; esac fi fi done <"$list" 3>/tmp/restore.$$ | (cd "$saved"; exec cpio -p -d -u "$ffsdir") || { 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 /tmp/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 /tmp/restore.$$) ) rm /tmp/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