summaryrefslogtreecommitdiff
path: root/packages/slugos-init/files/reflash
blob: 7ba60e6933c20dd2d1915a370e71db68e45c6e03 (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
#!/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
	kpart="Kernel"
	ffspart="Flashdisk";;
*)
	isnslu2=
	kpart="kernel"
	ffspart="filesystem";;
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 $kpart and $ffspart 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
			if test "$name" = "$kpart"
			then
				imgksize="$size"
				imgkoffset="$base"
			elif test "$name" = "$ffspart"
			then
				imgffssize="$size"
				imgffsoffset="$base"
			fi
		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 $kpart 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 $ffspart" >&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!
#
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 $kpart 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 $ffspart 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 <flash-directory> <dest> <list>
	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 <flash-directory> <source-dir> <restore>
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