merge of 8eaefa4f638dc67e29fb59fca3c6a98e4049eb09
[vuplus_openembedded] / packages / slugos-init / files / reflash
1 #!/bin/sh
2 # reflash
3 #  ensure the flash disk is not mounted
4 #  save configuration files
5 #  update the kernel
6 #  update the flashdisk
7 #  restore the saved configuration files
8 # the set of configuration files is described by
9 # /etc/default/conffiles.
10 #
11 # /etc/default/functions contains useful utility functions
12 . /etc/default/functions
13 load_functions sysconf
14 #
15 # NSLU2 flash layout is non-standard.
16 case "$(machine)" in
17 nslu2)
18         isnslu2=1
19         isdsmg600=
20         imageok=1
21         usrpart=
22         kpart="Kernel"
23         ffspart="Flashdisk";;
24 dsmg600)
25         isnslu2=
26         isdsmg600=1
27         imageok=1
28         usrpart="usr"
29         kpart="kernel"
30         ffspart="filesystem";;
31 *)
32         isnslu2=
33         isdsmg600=
34         imageok=
35         usrpart=
36         kpart="kernel"
37         ffspart="filesystem";;
38 esac
39 #
40 # CHECKING FOR INPUT (ARGUMENTS ETC)
41 # ----------------------------------
42 #
43 # find the kernel and the new flash file system, an image file can
44 # be used to specify both images.
45 ffsfile=
46 kfile=
47 imgfile=
48 preserve_config=1
49 while test $# -gt 0
50 do
51         case "$1" in
52         -n)     preserve_config=
53                 shift;;
54         -k)     shift
55                 test $# -gt 0 || {
56                         echo "reflash: -k: give the file containing the kernel image" >&2
57                         exit 1
58                 }
59                 kfile="$1"
60                 shift;;
61         -[jr])  shift
62                 test $# -gt 0 || {
63                         echo "reflash: -j: give the file containing the root jffs2 image" >&2
64                         exit 1
65                 }
66                 ffsfile="$1"
67                 shift;;
68         -i)     shift
69                 test -n "$imageok" || {
70                         echo "reflash: -i: only supported on the LinkSys NSLU2"     >&2
71                         echo " and the D-Link DSM-G600 systems; use -k and -j"      >&2
72                         echo " to specify the kernel and root file system instead." >&2
73                         exit 1
74                 }
75                 test $# -gt 0 || {
76                         echo "reflash: -i: give the file containing the complete flash image" >&2
77                         exit 1
78                 }
79                 imgfile="$1"
80                 shift;;
81         *)      if test -n "$imageok"
82                 then
83                         echo "reflash: usage: $0 [-n] [-k kernel] [-j rootfs] [-i image]" >&2
84                 else
85                         echo "reflash: usage: $0 [-n] [-k kernel] [-j rootfs]" >&2
86                 fi
87                 echo "  -n: do not attempt to preserve the configuration" >&2
88                 echo "  -k file: the new compressed kernel image ('zImage')" >&2
89                 echo "  -j file: the new root file system (jffs2)" >&2
90                 test -n "$imageok" &&
91                         echo "  -i file: a complete flash image (gives both kernel and jffs2)" >&2
92                 echo " The current jffs2 will be umounted if mounted." >&2
93                 exit 1;;
94         esac
95 done
96 #
97 # Sanity check on the arguments (note that the first case can only fire
98 # on NSLU2 or DSM-G600 because of the check for -i above.)
99 if test -n "$imgfile" -a -n "$ffsfile" -a -n "$kfile"
100 then
101         echo "reflash: specify at most two files" >&2
102         echo "  -i has both a kernel and rootfs, the kernel from -k and" >&2
103         echo "  the rootfs from -j override the one in the image (if given)" >&2
104         exit 1
105 elif test -z "$imgfile" -a -z "$ffsfile" -a -z "$kfile"
106 then
107         echo "reflash: specify at least one file to flash" >&2
108         exit 1
109 fi
110 #
111 # Perform basic checks on the input (must exist, size must be ok).
112 if test -n "$imgfile"
113 then
114         if test -r "$imgfile" -a -n "$isnslu2"
115         then
116                 # read the partition table and from this find the offset
117                 # and size of $kpart and $ffspart partitions.  The following
118                 # devio command just dumps the partition table in a format
119                 # similar to /proc/mtd (but it outputs decimal values!)
120                 #NOTE: this uses a here document because this allows the while
121                 # loop to set the variables, a pipe would put the while in
122                 # a sub-shell and the variable settings would be lost.  This
123                 # works in ash, no guarantees about other shells!
124                 while read size base name
125                 do
126                         if test "$name" = "$kpart"
127                         then
128                                 imgksize="$size"
129                                 imgkoffset="$base"
130                         elif test "$name" = "$ffspart"
131                         then
132                                 imgffssize="$size"
133                                 imgffsoffset="$base"
134                         fi
135                 done <<EOI
136 $(devio "<<$imgfile" '
137         <= $ 0x20000 -
138         L= 0x1000
139         $( 1
140                 # 0xff byte in name[0] ends the partition table
141                 $? @ 255 =
142                 # output size base name
143                 <= f15+
144                 .= b 0xfffffff &
145                 <= f4+
146                 .= b
147                 pf "%lu %lu "
148                 <= f28-
149                 cp 16
150                 pn
151                 <= f240+
152                 L= L256-
153         $) L255>')
154 EOI
155                 # check the result
156                 test "$imgksize" -gt 0 -a "$imgkoffset" -ge 0 || {
157                         echo "reflash: $imgfile: failed to find $kpart partition in image" >&2
158                         exit 1
159                 }
160                 # the kernel is after a 16 byte header which holds the
161                 # values length,0,0,0  Get the true size.
162                 ktmp="$(devio "<<$imgfile" "L=$imgksize" "O=$imgkoffset" '
163                         $( OL+$>!
164                                 <= O
165                                 A= b
166                                 $( AL>!
167                                         pr A
168                                 $) 0
169                         $) 0')"
170                 test "$ktmp" -gt 0 || {
171                         echo "reflash: $imgfile($imgkoffset,$imgksize): invalid kernel offset/size" >&2
172                         exit 1
173                 }
174                 # update the size and offset to these values (the offset is 16+ because
175                 # of the header).
176                 imgksize="$ktmp"
177                 imgkoffset="$(devio "O=$imgkoffset" 'pr O16+')"
178                 # just test the size for the rootfs
179                 test "$imgffssize" -gt 0 -a "$imgffsoffset" -ge 0 || {
180                         echo "reflash: $imgfile: failed to find $ffspart" >&2
181                         exit 1
182                 }
183         elif test -r "$imgfile" -a -n "$isdsmg600"
184         then
185                 #
186                 # For the DSM-G600, this is really easy - the image is just
187                 # a tar file.  So, extract the contents of the tar file, and
188                 # set the kernel and filesystem variables (if not already set)
189                 # to point to the extracted content.  Content will look like:
190                 # 
191                 # drwxr-xr-x 500/500         0 2006-11-25 23:47:59 firmupgrade
192                 # -rw-r--r-- 500/500   4718592 2006-12-02 16:32:51 firmupgrade/rootfs.gz
193                 # -rw-r--r-- 500/500        40 2006-11-25 22:15:41 firmupgrade/version.msg
194                 # -rw-r--r-- 500/500         0 2006-11-25 23:47:59 firmupgrade/usr.cramfs
195                 # -rw-rw-r-- 500/500   1306872 2006-12-02 16:33:37 firmupgrade/ip-ramdisk
196                 # 
197                 # Heuristic: if the size of usr.cramfs is zero, the firmware
198                 # is not a D-Link firmware for the device.  (The version.msg
199                 # file is not useful for this purpose; it describes the hardware,
200                 # not the firmware version in the image!)
201                 # 
202                 # TODO: If usr.cramfs is non-zero, we should flash that, too, just
203                 #       to make sure that it matches the native firmware's kernel
204                 #       and rootfs that we're now flashing back onto the device.
205
206                 echo "reflash: unpacking DSM-G600 image file" >&2
207                 tar -x -f "$imgfile" -C /var/tmp || {
208                         echo "reflash: unable to unpack image file to be flashed" >&2
209                         exit 1
210                 }
211
212                 if test -z "$kfile"
213                 then
214                         kfile="/var/tmp/firmupgrade/ip-ramdisk"
215                 fi
216
217                 if test -z "$ffsfile"
218                 then
219                         ffsfile="/var/tmp/firmupgrade/rootfs.gz"
220                 fi
221
222                 if test -s "/var/tmp/firmupgrade/usr.cramfs"
223                 then
224                         echo "reflash: Native flash being restored" >&2
225                         usrfile="/var/tmp/firmupgrade/usr.cramfs"
226                         preserve_config=
227                         dsmg600_native_flash=1
228                 fi
229
230         else
231                 echo "reflash: $imgfile: image file not found" >&2
232                 exit 1
233         fi
234 fi
235 if test -n "$kfile"
236 then
237         if test ! -r "$kfile"
238         then
239                 echo "reflash: $kfile: kernel file not found" >&2
240                 exit 1
241         fi
242         # the file values override anything from the image.
243         imgksize="$(devio "<<$kfile" 'pr$')"
244         imgkoffset=0
245 else
246         kfile="$imgfile"
247 fi
248 if test -n "$ffsfile"
249 then
250         if test ! -r "$ffsfile"
251         then
252                 echo "reflash: $ffsfile: root file system image file not found" >&2
253                 exit 1
254         fi
255         # values override those from the image
256         imgffssize="$(devio "<<$ffsfile" 'pr$')"
257         imgffsoffset=0
258 else
259         ffsfile="$imgfile"
260 fi
261 if test -n "$usrfile"
262 then
263         if test ! -r "$usrfile"
264         then
265                 echo "reflash: $usrfile: usr file system image file not found" >&2
266                 exit 1
267         fi
268         # values override those from the image
269         imgusrsize="$(devio "<<$usrfile" 'pr$')"
270         imgusroffset=0
271 else
272         usrfile=
273 fi
274 #
275 # INPUTS OK, CHECKING THE ENVIRONMENT
276 # -----------------------------------
277 # basic setup.  This could be parameterised to use different partitions!
278 #
279 kdev=
280 ksize=0
281 if test -n "$kfile"
282 then
283         # we have a new kernel
284         kdev="$(mtblockdev $kpart)"
285         test -n "$kdev" -a -b "$kdev" || {
286                 echo "reflash: $kpart($kdev): cannot find $kpart mtd partition." >&2
287                 echo "  check /proc/mtd, either the partition does not exist or there is no" >&2
288                 echo "  corresponding block device." >&2
289                 exit 1
290         }
291         ksize="$(devio "<<$kdev" 'pr$')"
292         #
293         # check the input file size
294         test -n "$imgksize" -a "$imgksize" -gt 0 -a "$imgksize" -le "$ksize" || {
295                 echo "reflash: $kfile: bad $kpart size ($imgksize, max $ksize)" >&2
296                 exit 1
297         }
298 fi
299 #
300 ffsdev=
301 ffssize=0
302 if test -n "$ffsfile"
303 then
304         ffsdev="$(mtblockdev $ffspart)"
305         test -n "$ffsdev" -a -b "$ffsdev" || {
306                 echo "reflash: $ffspart($ffsdev): cannot find $ffspart mtd partition." >&2
307                 echo "  check /proc/mtd, either the partition does not exist or there is no" >&2
308                 echo "  corresponding block device." >&2
309                 exit 1
310         }
311         ffssize="$(devio "<<$ffsdev" 'pr$')"
312         #
313         # check the input file size
314         test -n "$imgffssize" -a "$imgffssize" -gt 0 -a "$imgffssize" -le "$ffssize" || {
315                 echo "reflash: $ffsfile: bad $ffspart size ($imgffsize, max $ffssize)" >&2
316                 exit 1
317         }
318 fi
319 #
320 usrdev=
321 usrsize=0
322 if test -n "$usrfile"
323 then
324         usrdev="$(mtblockdev $usrpart)"
325         test -n "$usrdev" -a -b "$usrdev" || {
326                 echo "reflash: $usrpart($usrdev): cannot find $usrpart mtd partition." >&2
327                 echo "  check /proc/mtd, either the partition does not exist or there is no" >&2
328                 echo "  corresponding block device." >&2
329                 exit 1
330         }
331         usrsize="$(devio "<<$usrdev" 'pr$')"
332         #
333         # check the input file size
334         test -n "$imgusrsize" -a "$imgusrsize" -gt 0 -a "$imgusrsize" -le "$usrsize" || {
335                 echo "reflash: $usrfile: bad $usrpart size ($imgusrsize, max $usrsize)" >&2
336                 exit 1
337         }
338 fi
339
340 #
341 # INPUTS OK, ENVIRONMENT OK, UMOUNT ANY EXISTING MOUNT OF THE FLASHDISK
342 # ---------------------------------------------------------------------
343 # This is only required if the device is going to be used
344 if test -n "$ffsdev"
345 then
346         # -r causes this to fail if the flash device is mounted on /
347         umountflash -r "$ffsdev" || exit 1
348         #
349         # Everything is umounted, now remount on a temporary directory.
350         ffsdir="/tmp/flashdisk.$$"
351         mkdir "$ffsdir" || {
352                 echo "reflash: $ffsdir: failed to create temporary directory" >&2
353                 exit 1
354         }
355         #
356         mountflash "$ffsdev" "$ffsdir" -o ro || {
357                 rmdir "$ffsdir"
358                 exit 1
359         }
360         #
361         # this is a utility function to make the cleanup easier
362         errorexit() {
363                 umount "$ffsdir" && rmdir "$ffsdir" ||
364                         echo "reflash: $ffsdir: temporary directory cleanup failed" >&2
365                 exit 1
366         }
367         #
368         test -r "$ffsdir/etc/default/conffiles" || {
369                 echo "reflash: [/initrd]/etc/default/conffiles: file not found" >&2
370                 errorexit
371         }
372 else
373         errorexit() {
374                 exit 1
375         }
376 fi
377 #
378 # PRESERVE EXISTING CONFIGURATION
379 # -------------------------------
380 # Only required if the flash partition will be written
381 if test -n "$ffsdev" -a -n "$preserve_config"
382 then
383         echo "reflash: preserving existing configuration file" >&2
384         #
385         # This step produces /tmp/preserve.$$ and /tmp/cpio.$$, the former is
386         # a list of the preserved configuration files together with the processing
387         # option, the latter is a directory tree of the preserved files (a directory
388         # tree makes the restore step easier.)
389         saved=/tmp/cpio.$$
390         list=/tmp/preserve.$$
391         mkdir "$saved" || {
392                 echo "reflash: $saved: could not create save directory" >&2
393                 errorexit
394         }
395         #
396         # sysconf_save_conffiles <flash-directory> <dest> <list>
397         sysconf_save_conffiles "$ffsdir" "$saved" "$list" || {
398                 echo "reflash: $saved: copy of saved configuration files failed" >&2
399                 rm -rf "$saved"
400                 rm "$list"
401                 errorexit
402         }
403         #
404         # If this umount fails do not try to continue...
405         umount "$ffsdir" || {
406                 echo "reflash: $ffsdir: temporary mount point umount failed" >&2
407                 echo "  No changes have been made." >&2
408                 rm -rf "$saved"
409                 rm "$list"
410                 exit 1
411         }
412 fi
413 #
414 # FLASH THE NEW IMAGES
415 # --------------------
416 echo "reflash: about to flash new image" >&2
417 #
418 # There are four possibilities here - kernel only, flashdisk only, then
419 # kernel&flashdisk in either one or two different files.  The code used
420 # to attempt to do everything in one step, but this complicates it,
421 # so two steps are used (as required).  A failure between the two
422 # steps is a problem, but then so is a failure in a partial write.
423 # Write the flashdisk first because this is larger (most likely to
424 # fail).
425 #
426 # -p causes the progress indicator to be displayed
427 progress=-p
428 do_kernel() {
429         local cmd
430         if test -n "$isnslu2"
431         then
432                 # NSLU2: write length,0,0,0 header, then fill
433                 cmd="wb L,4; fb 12,0; cpL"
434         else
435                 # Other: just write the kernel bytes
436                 cmd="cpL"
437         fi
438         devio $progress "$@" "<<$kfile" ">>$kdev" '
439                 # kernel is at imgkoffset[imgksize]
440                 ' "<= $imgkoffset" "L=$imgksize" "$cmd" '
441                 # fill with 255
442                 fb #t-,255'
443 }
444 #
445 do_ffs() {
446         devio $progress "$@" "<<$ffsfile" ">>$ffsdev" '
447                 # rootfs is at imgffsoffset[imgffssize]
448                 ' "<= $imgffsoffset" "cp $imgffssize" '
449                 # fill with 255
450                 fb #t-,255'
451 }
452 #
453 do_usr() {
454         devio $progress "$@" "<<$usrfile" ">>$usrdev" '
455                 # usrfs is at imgusroffset[imgusrsize]
456                 ' "<= $imgusroffset" "cp $imgusrsize" '
457                 # fill with 255
458                 fb #t-,255'
459 }
460 #
461 # check_status $? type file(offset,size) device
462 #  check the devio status code (given in $1)
463 check_status() {
464         case "$1" in
465         0)      echo " done" >&2;;
466         1)      echo " failed" >&2
467                 echo "reflash: $3: flash $2 failed, no changes have been made to $4" >&2
468                 if test "$2" = rootfs
469                 then
470                         rm -rf "$saved"
471                         rm "$list"
472                         exit 1
473                 else
474                         echo "reflash: $2: continuing with rootfs changes" >&2
475                         echo "  NOTE: the old kernel is still in $4!" >&2
476                 fi;;
477         3)      echo " failed" >&2
478                 echo "reflash: $3: WARNING: partial flash of $2 to $4 the system is unbootable" >&2
479                 echo "  Reflash from RedBoot or correct the problem here." >&2
480                 if test "$2" = rootfs
481                 then
482                         exit 3
483                 else
484                         echo "reflash: $2: continuing with rootfs changes" >&2
485                         echo "  NOTE: the kernel in $4 must be reflashed!" >&2
486                 fi;;
487         *)      echo " failed" >&2
488                 echo "reflash($1): $3: internal error flashing $2 to $4" >&2
489                 exit $1;;
490         esac
491 }
492 #
493 if test -n "$usrdev"
494 then
495         echo -n "reflash: writing usrfs to $usrdev " >&2
496         do_usr
497         check_status $? usrfs "$usrfile($imgusroffset,$imgusrsize)" "$usrdev"
498 fi
499 #
500 if test -n "$ffsdev"
501 then
502         echo -n "reflash: writing rootfs to $ffsdev " >&2
503         do_ffs
504         check_status $? rootfs "$ffsfile($imgffsoffset,$imgffssize)" "$ffsdev"
505 fi
506 #
507 if test -n "$kdev"
508 then
509         echo -n "reflash: writing kernel to $kdev " >&2
510         do_kernel
511         check_status $? kernel "$kfile($imgkoffset,$imgksize)" "$kdev"
512 fi
513 #
514 # verify - this just produces a warning
515 if test -n "$usrdev"
516 then
517         echo -n "reflash: verifying new usr image " >&2
518         if do_usr -v
519         then
520                 echo " done" >&2
521         else
522                 echo " failed" >&2
523                 echo "reflash: WARNING: usrfs flash image verification failed" >&2
524                 echo "  The system is may be bootable." >&2
525         fi
526 fi
527 #
528 if test -n "$ffsdev"
529 then
530         echo -n "reflash: verifying new flash image " >&2
531         if do_ffs -v
532         then
533                 echo " done" >&2
534         else
535                 echo " failed" >&2
536                 echo "reflash: WARNING: rootfs flash image verification failed" >&2
537                 echo "  The system is probably unbootable." >&2
538                 echo "  System configuration files will be restored but this may fail" >&2
539                 echo "  Starting a shell for user recovery (exit to continue)" >&2
540                 PS1='badflash$ ' sh -i <>/dev/tty >&0 2>&0
541         fi
542 fi
543 #
544 if test -n "$kdev"
545 then
546         echo -n "reflash: verifying new kernel image " >&2
547         if do_kernel -v
548         then
549                 echo " done" >&2
550         else
551                 echo " failed" >&2
552                 echo "reflash: WARNING: kernel flash image verification failed" >&2
553                 echo "  The system is probably unbootable." >&2
554                 echo "  System configuration files will be restored in the rootfs." >&2
555         fi
556 fi
557 #
558 # RESTORE THE OLD CONFIGURATION
559 # -----------------------------
560 # If not write the rootfs none of the following is required - exit now.
561 test -n "$ffsdev" -a -n "$preserve_config" || exit 0
562 #
563 echo "reflash: restoring saved configuration files" >&2
564 #
565 # the file /etc/.configured is the datestamp file used to ensure that
566 # changed configuration files can be recognised.  It is created by
567 # /etc/rcS.d/S99finish on first boot (if it does not exist).  We need
568 # a timestamp earlier than any files we create so touch it here, this
569 # also acts as a test on the mounted file system
570 mountflash "$ffsdev" "$ffsdir" && :>"$ffsdir/etc/.configured" || {
571         rmdir "$ffsdir"
572         echo "reflash: mount of new flash root file system failed" >&2
573         if test -d "$ffsdir/etc"
574         then
575                 echo "    The file system does not seem to be writeable." >&2
576                 echo "    The mounted file system is in $ffsdir" >&2
577         fi
578         echo "  WARNING: the kernel and root file system have been reflashed," >&2
579         echo "  HOWEVER the new root file system seems to be unuseable." >&2
580         echo "  Saved configuration files are in $saved" >&2
581         echo "  The list of saved configuration files is in $list" >&2
582         echo " You should determine the reason for the failed mount, mount the new" >&2
583         echo " file system and restore the configuration from $saved - it's just a" >&2
584         echo " matter of copying the saved files where required." >&2
585         exit 1
586 }
587 #
588 # sysconf_restore_conffiles <flash-directory> <source-dir> <restore>
589 restore="/tmp/restore.$$"
590 sysconf_restore_conffiles "$ffsdir" "$saved" "$restore" <"$list" || {
591         echo "reflash: $saved: restore of saved configuration files failed" >&2
592         echo "  The new flash file system is mounted on $ffsdir" >&2
593         echo "  The saved files are in $saved and the list in $list, the list of" >&2
594         echo "  files selected for restore is in $restore" >&2
595         echo "  You should restore any required configuration from $saved," >&2
596         echo "  then umount $ffsdir and reboot." >&2
597         exit 1
598 }
599 #
600 # remove the copied files (i.e. the ones which were preserved)
601 (       cd "$saved"
602         exec rm $(cat "$restore")
603 )
604 rm "$restore"
605 #
606 # clean up, files left in $saved need to be handled by the user
607 files="$(find "$saved" ! -type d -print)"
608 if test -n "$files"
609 then
610         echo "reflash: the following saved configuration files remain:" >&2
611         echo "$files" >&2
612         echo "The full list of preserved files is in $list.  To alter the" >&2
613         echo "new root file system use the command:" >&2
614         echo "" >&2
615         echo "  mount -t jffs2 $ffsdev /mnt" >&2
616         echo "" >&2
617         echo "The saved files are in the temporary directory, they will not" >&2
618         echo "be retained across a system boot.  Copy them elsewhere if you" >&2
619         echo "are unsure whether they are needed" >&2
620 else
621         rm -rf "$saved"
622         rm "$list"
623 fi
624 #
625 # now the final umount
626 if umount "$ffsdir"
627 then
628         rmdir "$ffsdir"
629         echo "reflash: system upgrade complete.  Reboot to continue." >&2
630         exit 0
631 else
632         echo "reflash: $ffsdir: temporary mount point umount failed" >&2
633         echo "  ALL changes have been made successfully, however the umount of" >&2
634         echo "  the new root file system has failed.  You should determine the" >&2
635         echo "  cause of the failure, umount $ffsdir, then reboot the system (this" >&2
636         echo "  will use the upgraded kernel and root file system)" >&2
637         exit 1
638 fi