summaryrefslogtreecommitdiff
path: root/packages/openslug-init/openslug-init-0.10/reflash
blob: bcf51fc606e5580169031608cea75aea4af0dedc (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
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
#!/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 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 $# -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 file: the new compressed kernel image ('zImage')" >&2
		echo "  -j file: the new root file system (jffs2)" >&2
		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
if test -n "$imgfile" -a -n "$ffsfile" -a -n "$kfile"
then
	echo "reflash: -k,-j,-i: 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: -k,-j,-i: 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
$(devio "<<$imgfile" '
	<= $ 0x20000 -
	L= 0x1000
	$( 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+
		L= L256-
	$) L255>')
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 ($s, 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 ($s, 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
	}
	#
	(	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
	}
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).
# Temporarily check for devio progress indicator capability this way...
if devio -p '' 2>/dev/null
then
	progress=-p
else
	progress=
fi
do_kernel() {
	devio $progress "$@" "<<$kfile" ">>$kdev" '
		# kernel is at imgkoffset[imgksize]
		' "<= $imgkoffset" "L=$imgksize" '
		# kernel write length,0,0,0 header, then fill
		wb L,4
		fb 12,0
		cp L
		# 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
}
#
# 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