Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(600)

Side by Side Diff: user_tools/linux/recovery.sh

Issue 5562003: Initial creation of the end-user recovery tool. (Closed) Base URL: http://git.chromium.org/git/vboot_reference.git@master
Patch Set: Created 10 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « user_tools/README_recovery.txt ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/bin/sh
2 #
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6 #
7 # This attempts to guide linux users through the process of putting a recovery
8 # image onto a removeable USB drive.
9 #
10 # We may not need root privileges if we have the right permissions.
Sumit 2010/12/04 00:26:07 Won't dd require sudo?
11 #
12 set -eu
13
14 ##############################################################################
15 # Configuration goes here
16
17 # Where should we do our work? Use 'WORKDIR=' to make a temporary directory,
18 # but using a persistent location may let us resume interrupted downloads or
19 # run again without needing to download a second time.
20 WORKDIR=/tmp/tmp.crosrec
21
22 # Where do we look for the config file?
23 CONFIGURL='http://www.chromium.org/some/random/place.cfg'
24
25 # What version is this script? It must match the 'recovery_tool_version=' value
26 # in the config file that we'll download.
27 MYVERSION='1.0'
Sumit 2010/12/04 00:26:07 Lets decrease the version here, I will ask Scot to
28
29
30 ##############################################################################
31 # Some temporary filenames
32 debug='debug.log'
33 tmpfile='tmp.txt'
34 config='config.txt'
35 version='verson.txt'
36
37 ##############################################################################
38 # Various warning messages
39
40 DEBUG() {
41 echo "DEBUG: $@" >>"$debug"
42 }
43
44 warn() {
45 echo "$@" 1>&2
46 }
47
48 quit() {
49 warn "quitting..."
50 exit 1
51 }
52
53 fatal() {
54 warn "ERROR: $@"
55 exit 1
56 }
57
58 ufatal() {
59 warn "
60 ERROR: $@
61
62 You may need to run this program as a different user. If that doesn't help, try
63 using a different computer, or ask a knowledgeable friend for help.
64
65 "
66 exit 1
67 }
68
69 gfatal() {
70 warn "
71 ERROR: $@
72
73 You may need to run this program as a different user. If that doesn't help, it
74 may be a networking problem or a problem with the images provided by Google.
75 You might want to check to see if there is a newer version of this tool
76 available, or if someone else has already reported a problem.
77
78 If all else fails, you could try using a different computer, or ask a
79 knowledgeable friend for help.
80
81 "
82 exit 1
83 }
84
85 ##############################################################################
86 # Identify the external utilities that we MUST have available.
87 #
88 # I'd like to keep the set of external *NIX commands to an absolute minimum,
89 # but I have to balance that against producing mysterious errors because the
90 # shell can't always do everything. Let's make sure that these utilities are
91 # all in our $PATH, or die with an error.
92 #
93 # This also sets the following global variables to select alternative utilities
94 # when there is more than one equivalent tool available:
95 #
96 # FETCH = name of utility used to download files from the web
97 # FETCHNEW = command to invoke to download fresh each time
98 # FETCHCONT = command to invoke to download with resume if possible
99 # CHECK = command to invoke to generate checksums on a file
100 #
101 require_utils() {
102 local external
103 local errors
104 local tool
105 local tmp
106
107 external='cat cut dd grep ls mkdir mount readlink sed sync umount unzip wc'
108 if [ -z "$WORKDIR" ]; then
109 external="$external mktemp"
110 fi
111 errors=
112
113 for tool in $external ; do
114 if ! type "$tool" >/dev/null 2>&1 ; then
115 warn "ERROR: can't find \"$tool\""
116 errors=yes
117 fi
118 done
119
120 # We also need to a way to fetch files from teh internets. Note that the args
121 # are different depending on which utility we find. We'll use two variants,
122 # one to fetch fresh every time and one to try again from where we left off.
123 FETCH=
124 if [ -z "$FETCH" ] && tmp=$(type curl 2>/dev/null) ; then
125 FETCH=curl
126 FETCHNEW="curl -f -s -S -o"
127 FETCHCONT="curl -f -C - -o"
128 fi
129 if [ -z "$FETCH" ] && tmp=$(type wget 2>/dev/null) ; then
130 FETCH=wget
131 FETCHNEW="wget -nv -O"
132 FETCHCONT="wget -c -O"
133 fi
134 if [ -z "$FETCH" ]; then
135 warn "ERROR: can't find \"curl\" or \"wget\""
136 errors=yes
137 fi
138
139 # Once we've fetched a file we need to compute its checksum. There are a
140 # couple of possiblities here too.
141 CHECK=
142 if [ -z "$CHECK" ] && tmp=$(type md5sum 2>/dev/null) ; then
143 CHECK="md5sum"
144 fi
145 if [ -z "$CHECK" ] && tmp=$(type sha1sum 2>/dev/null) ; then
146 CHECK="sha1sum"
147 fi
148 if [ -z "$CHECK" ]; then
149 warn "ERROR: can't find \"md5sum\" or \"sha1sum\""
150 errors=yes
151 fi
152
153 if [ -n "$errors" ]; then
154 ufatal "Some required linux utilities are missing."
155 fi
156 }
157
158 ##############################################################################
159 # Helper functions to handle the config file and image tarball.
160
161 # Each paragraph in the config file should describe a new image. Let's make
162 # sure it follows all the rules. This scans the config file and returns success
163 # if it looks valid. As a side-effect, it lists the line numbers of the start
164 # and end of each stanza in the global variables 'start_lines' and 'end_lines'
165 # and saves the total number of images in the global variable 'num_images'.
166 good_config() {
167 local line
168 local key
169 local val
170 local display_name
171 local file
172 local size
173 local url
174 local md5
175 local sha1
176 local skipping
177 local errors
178 local count
179 local line_num
180
181 display_name=
182 file=
183 size=
184 url=
185 md5=
186 sha1=
187 skipping=yes
188 errors=
189 count=0
190 line_num=0
191
192 # global
193 start_lines=
194 end_lines=
195
196 while read line; do
197 line_num=$(( line_num + 1 ))
198
199 # We might have some empty lines before the first stanza. Skip them.
200 if [ -n "$skipping" ] && [ -z "$line" ]; then
201 continue
202 fi
203
204 # Got something...
205 if [ -n "$line" ]; then
206 key=${line%=*}
207 val=${line#*=}
208 if [ -z "$key" ] || [ -z "$val" ] || [ "$key=$val" != "$line" ]; then
209 DEBUG "ignoring $line"
210 continue
211 fi
212
213 # right, looks good
214 if [ -n "$skipping" ]; then
215 skipping=
216 start_lines="$start_lines $line_num"
217 fi
218
219 case $key in
220 display_name)
221 if [ -n "$display_name" ]; then
222 DEBUG "duplicate $key"
223 errors=yes
224 fi
225 display_name="$val"
226 ;;
227 file)
228 if [ -n "$file" ]; then
229 DEBUG "duplicate $key"
230 errors=yes
231 fi
232 file="$val"
233 ;;
234 size)
235 if [ -n "$size" ]; then
236 DEBUG "duplicate $key"
237 errors=yes
238 fi
239 size="$val"
240 ;;
241 url)
242 url="$val"
243 ;;
244 md5)
245 md5="$val"
246 ;;
247 sha1)
248 sha1="$val"
249 ;;
250 esac
251 else
252 # Between paragraphs. Time to check what we've found so far.
253 end_lines="$end_lines $line_num"
254 count=$(( count + 1))
255
256 if [ -z "$display_name" ]; then
257 DEBUG "image $count is missing display_name"
258 errors=yes
259 fi
260 if [ -z "$file" ]; then
261 DEBUG "image $count is missing file"
262 errors=yes
263 fi
264 if [ -z "$size" ]; then
265 DEBUG "image $count is missing size"
266 errors=yes
267 fi
268 if [ -z "$url" ]; then
269 DEBUG "image $count is missing url"
270 errors=yes
271 fi
272 if [ "$CHECK" = "md5sum" ] && [ -z "$md5" ]; then
273 DEBUG "image $count is missing required md5"
274 errors=yes
275 fi
276 if [ "$CHECK" = "sha1sum" ] && [ -z "$sha1" ]; then
277 DEBUG "image $count is missing required sha1"
278 errors=yes
279 fi
280
281 # Prepare for next stanza
282 display_name=
283 file=
284 size=
285 url=
286 md5=
287 sha1=
288 skipping=yes
289 fi
290 done < "$config"
291
292 DEBUG "$count images found"
293 num_images="$count"
294
295 DEBUG "start_lines=($start_lines)"
296 DEBUG "end_lines=($end_lines)"
297
298 # return error status
299 [ "$count" != "0" ] && [ -z "$errors" ]
300 }
301
302
303 # Make the user pick an image to download. On success, it sets the global
304 # variable 'user_choice' to the selected image number.
305 choose_image() {
306 local show
307 local count
308 local line
309 local num
310
311 show=yes
312 while true; do
313 if [ -n "$show" ]; then
314 echo
315 echo "There are $num_images recovery images to choose from:"
316 echo
317 count=0
318 echo "0 - <quit>"
319 grep '^display_name=' "$config" | while read line; do
320 count=$(( count + 1 ))
321 echo "$line" | sed "s/display_name=/$count - /"
322 done
323 echo
324 show=
325 fi
326 echo -n "Please select a recovery image to download: "
327 read num
328 if [ -z "$num" ] || [ "$num" = "?" ]; then
329 show=yes
330 elif echo "$num" | grep -q '[^0-9]'; then
331 echo "Sorry, I didn't understand that."
332 else
333 if [ "$num" -lt "0" ] || [ "$num" -gt "$num_images" ]; then
334 echo "That's not one of the choices."
335 elif [ "$num" -eq 0 ]; then
336 quit
337 else
338 break;
339 fi
340 fi
341 done
342 echo
343
344 # global
345 user_choice="$num"
346 }
347
348 # Fetch and verify the user's chosen image. On success, it sets the global
349 # variable 'image_file' to indicate the local name of the unpacked binary that
350 # should be written to the USB drive.
351 fetch_image() {
352 local start
353 local end
354 local line
355 local key
356 local val
357 local file
358 local size
359 local url
360 local md5
361 local sha1
362 local line_num
363 local tarball
364 local err
365 local sum
366
367 file=
368 size=
369 url=
370 md5=
371 sha1=
372 line_num="0"
373
374 # Convert image number to line numbers within config file.
375 start=$(echo $start_lines | cut -d' ' -f$1)
376 end=$(echo $end_lines | cut -d' ' -f$1)
377
378 while read line; do
379 # Skip to the start of the desired stanza
380 line_num=$(( line_num + 1 ))
381 if [ "$line_num" -lt "$start" ] || [ "$line_num" -ge "$end" ]; then
382 continue;
383 fi
384
385 # Process the stanza.
386 if [ -n "$line" ]; then
387 key=${line%=*}
388 val=${line#*=}
389 if [ -z "$key" ] || [ -z "$val" ] || [ "$key=$val" != "$line" ]; then
390 DEBUG "ignoring $line"
391 continue
392 fi
393
394 case $key in
395 # The descriptive stuff we'll just save for later.
396 file)
397 file="$val"
398 ;;
399 size)
400 size="$val"
401 ;;
402 md5)
403 md5="$val"
404 ;;
405 sha1)
406 sha1="$val"
407 ;;
408 url)
409 # Try to download each url until one works.
410 if [ -n "$url" ]; then
411 # We've already got one (it's very nice).
412 continue;
413 fi
414 warn "Downloading image tarball from $val"
415 warn
416 tarball=${val##*/}
417 if $FETCHCONT "$tarball" "$val"; then
418 # Got it.
419 url="$val"
420 else
421 # If you give curl the '-C -' option but the file you want is
422 # already complete and the server doesn't report the total size
423 # correctly, it will report an error instead of just doing nothing.
424 # We'll try to work around that.
425 err=$?
426 if [ "$FETCH" = "curl" ] && [ "$err" = "18" ]; then
427 warn "Ignoring spurious complaint"
428 url="$val"
429 fi
430 fi
431 ;;
432 esac
433 fi
434 done < "$config"
435
436 if [ -z "$url" ]; then
437 DEBUG "couldn't fetch tarball"
438 return 1
439 fi
440
441 # Verify the tarball
442 if ! ls -l "$tarball" | grep -q "$size"; then
443 DEBUG "size is wrong"
444 return 1
445 fi
446 sum=$($CHECK "$tarball" | cut -d' ' -f1)
447 DEBUG "$CHECK is $sum"
448 if [ "$CHECK" = "md5sum" ] && [ "$sum" != "$md5" ]; then
449 DEBUG "wrong $CHECK"
450 return 1
451 elif [ "$CHECK" = "sha1sum" ] && [ "$sum" != "$sha1" ]; then
452 DEBUG "wrong $CHECK"
453 return 1
454 fi
455
456 # Unpack the file
457 warn "Unpacking the tarball"
458 rm -f "$file"
459 if ! unzip "$tarball" "$file"; then
460 DEBUG "Can't unpack the tarball"
461 return 1
462 fi
463
464 # global
465 image_file="$file"
466 }
467
468 ##############################################################################
469 # Helper functions to manage USB drives.
470
471 # Return a list of base device names ("sda sdb ...") for all USB drives
472 get_devlist() {
473 local dev
474 local t
475 local r
476
477 for dev in $(cat /proc/partitions); do
478 [ -r "/sys/block/$dev/device/type" ] &&
479 t=$(cat "/sys/block/$dev/device/type") &&
480 [ "$t" = "0" ] &&
481 r=$(cat "/sys/block/$dev/removable") &&
482 [ "$r" = "1" ] &&
483 readlink -f "/sys/block/$dev" | grep -q -i usb &&
484 echo "$dev" || true
485 done
486 }
487
488 # Return descriptions for each provided base device name ("sda sdb ...")
489 get_devinfo() {
490 local dev
491 local v
492 local m
493 local s
494 local ss
495
496 for dev in $1; do
497 v=$(cat "/sys/block/$dev/device/vendor") &&
498 m=$(cat "/sys/block/$dev/device/model") &&
499 s=$(cat "/sys/block/$dev/size") && ss=$(( $s * 512 / 1000000 )) &&
500 echo "/dev/$dev ${ss}MB $v $m"
501 done
502 }
503
504 # Enumerate and descript the specified base device names ("sda sdb ...")
505 get_choices() {
506 local dev
507 local desc
508 local count
509
510 count=1
511 echo "0 - <quit>"
512 for dev in $1; do
513 desc=$(get_devinfo "$dev")
514 echo "$count - Use $desc"
515 count=$(( count + 1 ))
516 done
517 }
518
519 # Make the user pick a USB drive to write to. On success, it sets the global
520 # variable 'user_choice' to the selected device name ("sda", "sdb", etc.)
521 choose_drive() {
522 local show
523 local devlist
524 local choices
525 local num_drives
526 local msg
527 local num
528
529 show=yes
530 while true; do
531 if [ -n "$show" ]; then
532 devlist=$(get_devlist)
533 choices=$(get_choices "$devlist")
534 if [ -z "$devlist" ]; then
535 num_drives="0"
536 msg="I can't seem to find a valid USB drive."
537 else
538 num_drives=$(echo "$devlist" | wc -l)
539 if [ "$num_drives" != "1" ]; then
540 msg="I found $num_drives USB drives"
541 else
542 msg="I found $num_drives USB drive"
543 fi
544 fi
545 echo -n "
546
547 $msg
548
549 $choices
550
551 "
552 show=
553 fi
554 echo -n "Tell me what to do (or just press Enter to scan again): "
555 read num
556 if [ -z "$num" ] || [ "$num" = "?" ]; then
557 show=yes
558 elif echo "$num" | grep -q '[^0-9]'; then
559 echo "Sorry, I didn't understand that."
560 else
561 if [ "$num" -lt "0" ] || [ "$num" -gt "$num_drives" ]; then
562 echo "That's not one of the choices."
563 elif [ "$num" -eq 0 ]; then
564 quit
565 else
566 break;
567 fi
568 fi
569 done
570
571 # global
572 user_choice=$(echo $devlist | cut -d' ' -f$num)
573 }
574
575
576 ##############################################################################
577 # Okay, do something...
578
579 # Make sure we have the tools we need
580 require_utils
581
582 # Need a place to work. We prefer a fixed location so we can try to resume any
583 # interrupted downloads.
584 if [ -n "$WORKDIR" ]; then
585 if [ ! -d "$WORKDIR" ] && ! mkdir "$WORKDIR" ; then
586 warn "Using temporary directory"
587 WORKDIR=
588 fi
589 fi
590 if [ -z "$WORKDIR" ]; then
591 WORKDIR=$(mktemp -d)
592 # Clean up temporary directory afterwards
593 trap "cd; rm -rf ${WORKDIR}" EXIT
594 fi
595
596 cd "$WORKDIR"
597 warn "Working in $WORKDIR/"
598 rm -f "$debug"
599
600 # Download the config file to see what choices we have.
601 warn "Downloading config file from $CONFIGURL"
602 $FETCHNEW "$tmpfile" "$CONFIGURL" || \
603 gfatal "Unable to download the config file"
604
605 # Un-DOS-ify the config file and separate the version info from the images
606 sed 's/\r//g' "$tmpfile" | grep '^recovery_tool' > "$version"
607 sed 's/\r//g' "$tmpfile" | grep -v '^#' | grep -v '^recovery_tool' > "$config"
608 # Add one empty line to the config file to terminate the last stanza
609 echo >> "$config"
610
611 # Make sure that the config file version matches this script version
612 tmp=$(grep '^recovery_tool_version=' "$version") || \
613 gfatal "The config file doesn't contain a version string."
614 filevers=${tmp#*=}
615 if [ "$filevers" != "$MYVERSION" ]; then
616 tmp=$(grep '^recovery_tool_update=' "$version");
617 msg=${tmp#*=}
618 warn "This tool is version $MYVERSION." \
619 "The config file is for version $filevers."
620 fatal ${msg:-Please download a matching version of the tool and try again.}
621 fi
622
623 # Check the config file to be sure it's valid. As a side-effect, this sets the
624 # global variable 'num_images' with the number of image stanzas read, but
625 # that's independent of whether the config is valid.
626 good_config || gfatal "The config file isn't valid."
627
628 # Make the user pick an image to download, or exit.
629 choose_image
630
631 # Download the user's choice
632 fetch_image "$user_choice" || \
633 gfatal "Unable to download a valid recovery image."
634
635 # Make the user pick a USB drive, or exit.
636 choose_drive
637
638 # Be sure
639 dev_desc=$(get_devinfo "$user_choice")
640 echo "
641 Is this the device you want to put the recovery image on?
642
643 $dev_desc
644 "
645 echo -n "You must enter 'YES' to continue: "
646 read tmp
647 if [ "$tmp" != "YES" ]; then
648 quit
649 fi
650
651 # Be very sure
652 echo "
653
654 I'm really going to erase this device. This will permanently ERASE
655 whatever you may have on that drive. You won't be able to undo it.
656
657 $dev_desc
658 "
659
660 echo -n "If you're sure that's the device to use, enter 'DoIt' now: "
661 read tmp
662 if [ "$tmp" != "DoIt" ]; then
663 quit
664 fi
665 echo "
666
667 Installing the recovery image
668
669 "
670
671 # Unmount anything on that device.
672 echo "unmounting..."
673 for tmp in $(mount | grep ^"/dev/${user_choice}" | sed 's/[ \t].*//'); do
674 umount $tmp || ufatal "Unable to unmount $tmp."
675 done
676
677 # Write it.
678 echo "copying... (this may take several minutes)"
679 dd of=/dev/${user_choice} if="$image_file" ||
680 ufatal "Unable to write the image."
681 sync
682
683 echo "
684
685 Done. Remove the USB drive and insert it in your Chrome OS netbook.
686
687 "
688
689 exit 0
OLDNEW
« no previous file with comments | « user_tools/README_recovery.txt ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698