summaryrefslogtreecommitdiff
path: root/packages/openslug-init/openslug-init-0.10/reflash
blob: dc6ef0bbd8a05f5c129d6c5f5c6fc4a577d2a317 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
#!/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 "<</etc/init.d/sysconfsetup" prd)" -eq "$ffsno"
then
	echo "reflash: $ffsdev is mounted on /, use turnup ram to reflash" >&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 <<EOI
$(devio "<<$imgfile" '
	<= $ 0x20000 -
	$( 1
		# 0xff byte in name[0] ends the partition table
		$? @ 255 =
		# output size base name
		<= f15+
		.= b 0xfffffff &
		<= f4+
		.= b
		pf "%lu %lu "
		<= f28-
		cp 16
		pn
		<= f240+
	$) 1')
EOI
		# check the result
		test -n "$imgksize" -a "$imgksize" -gt 0 -a "$imgksize" -le "$ksize" || {
			echo "reflash: $imgfile: bad kernel size ($imgksize, max $ksize)" >&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 </proc/mounts || {
	echo "reflash: umount $ffsdev from all mount points then re-run reflash" >&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