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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « user_tools/README_recovery.txt ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: user_tools/linux/recovery.sh
diff --git a/user_tools/linux/recovery.sh b/user_tools/linux/recovery.sh
new file mode 100755
index 0000000000000000000000000000000000000000..eae0eb6dbf5be781b2cdbf76d1ba35cce0a705c9
--- /dev/null
+++ b/user_tools/linux/recovery.sh
@@ -0,0 +1,689 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This attempts to guide linux users through the process of putting a recovery
+# image onto a removeable USB drive.
+#
+# We may not need root privileges if we have the right permissions.
Sumit 2010/12/04 00:26:07 Won't dd require sudo?
+#
+set -eu
+
+##############################################################################
+# Configuration goes here
+
+# Where should we do our work? Use 'WORKDIR=' to make a temporary directory,
+# but using a persistent location may let us resume interrupted downloads or
+# run again without needing to download a second time.
+WORKDIR=/tmp/tmp.crosrec
+
+# Where do we look for the config file?
+CONFIGURL='http://www.chromium.org/some/random/place.cfg'
+
+# What version is this script? It must match the 'recovery_tool_version=' value
+# in the config file that we'll download.
+MYVERSION='1.0'
Sumit 2010/12/04 00:26:07 Lets decrease the version here, I will ask Scot to
+
+
+##############################################################################
+# Some temporary filenames
+debug='debug.log'
+tmpfile='tmp.txt'
+config='config.txt'
+version='verson.txt'
+
+##############################################################################
+# Various warning messages
+
+DEBUG() {
+ echo "DEBUG: $@" >>"$debug"
+}
+
+warn() {
+ echo "$@" 1>&2
+}
+
+quit() {
+ warn "quitting..."
+ exit 1
+}
+
+fatal() {
+ warn "ERROR: $@"
+ exit 1
+}
+
+ufatal() {
+ warn "
+ERROR: $@
+
+You may need to run this program as a different user. If that doesn't help, try
+using a different computer, or ask a knowledgeable friend for help.
+
+"
+ exit 1
+}
+
+gfatal() {
+ warn "
+ERROR: $@
+
+You may need to run this program as a different user. If that doesn't help, it
+may be a networking problem or a problem with the images provided by Google.
+You might want to check to see if there is a newer version of this tool
+available, or if someone else has already reported a problem.
+
+If all else fails, you could try using a different computer, or ask a
+knowledgeable friend for help.
+
+"
+ exit 1
+}
+
+##############################################################################
+# Identify the external utilities that we MUST have available.
+#
+# I'd like to keep the set of external *NIX commands to an absolute minimum,
+# but I have to balance that against producing mysterious errors because the
+# shell can't always do everything. Let's make sure that these utilities are
+# all in our $PATH, or die with an error.
+#
+# This also sets the following global variables to select alternative utilities
+# when there is more than one equivalent tool available:
+#
+# FETCH = name of utility used to download files from the web
+# FETCHNEW = command to invoke to download fresh each time
+# FETCHCONT = command to invoke to download with resume if possible
+# CHECK = command to invoke to generate checksums on a file
+#
+require_utils() {
+ local external
+ local errors
+ local tool
+ local tmp
+
+ external='cat cut dd grep ls mkdir mount readlink sed sync umount unzip wc'
+ if [ -z "$WORKDIR" ]; then
+ external="$external mktemp"
+ fi
+ errors=
+
+ for tool in $external ; do
+ if ! type "$tool" >/dev/null 2>&1 ; then
+ warn "ERROR: can't find \"$tool\""
+ errors=yes
+ fi
+ done
+
+ # We also need to a way to fetch files from teh internets. Note that the args
+ # are different depending on which utility we find. We'll use two variants,
+ # one to fetch fresh every time and one to try again from where we left off.
+ FETCH=
+ if [ -z "$FETCH" ] && tmp=$(type curl 2>/dev/null) ; then
+ FETCH=curl
+ FETCHNEW="curl -f -s -S -o"
+ FETCHCONT="curl -f -C - -o"
+ fi
+ if [ -z "$FETCH" ] && tmp=$(type wget 2>/dev/null) ; then
+ FETCH=wget
+ FETCHNEW="wget -nv -O"
+ FETCHCONT="wget -c -O"
+ fi
+ if [ -z "$FETCH" ]; then
+ warn "ERROR: can't find \"curl\" or \"wget\""
+ errors=yes
+ fi
+
+ # Once we've fetched a file we need to compute its checksum. There are a
+ # couple of possiblities here too.
+ CHECK=
+ if [ -z "$CHECK" ] && tmp=$(type md5sum 2>/dev/null) ; then
+ CHECK="md5sum"
+ fi
+ if [ -z "$CHECK" ] && tmp=$(type sha1sum 2>/dev/null) ; then
+ CHECK="sha1sum"
+ fi
+ if [ -z "$CHECK" ]; then
+ warn "ERROR: can't find \"md5sum\" or \"sha1sum\""
+ errors=yes
+ fi
+
+ if [ -n "$errors" ]; then
+ ufatal "Some required linux utilities are missing."
+ fi
+}
+
+##############################################################################
+# Helper functions to handle the config file and image tarball.
+
+# Each paragraph in the config file should describe a new image. Let's make
+# sure it follows all the rules. This scans the config file and returns success
+# if it looks valid. As a side-effect, it lists the line numbers of the start
+# and end of each stanza in the global variables 'start_lines' and 'end_lines'
+# and saves the total number of images in the global variable 'num_images'.
+good_config() {
+ local line
+ local key
+ local val
+ local display_name
+ local file
+ local size
+ local url
+ local md5
+ local sha1
+ local skipping
+ local errors
+ local count
+ local line_num
+
+ display_name=
+ file=
+ size=
+ url=
+ md5=
+ sha1=
+ skipping=yes
+ errors=
+ count=0
+ line_num=0
+
+ # global
+ start_lines=
+ end_lines=
+
+ while read line; do
+ line_num=$(( line_num + 1 ))
+
+ # We might have some empty lines before the first stanza. Skip them.
+ if [ -n "$skipping" ] && [ -z "$line" ]; then
+ continue
+ fi
+
+ # Got something...
+ if [ -n "$line" ]; then
+ key=${line%=*}
+ val=${line#*=}
+ if [ -z "$key" ] || [ -z "$val" ] || [ "$key=$val" != "$line" ]; then
+ DEBUG "ignoring $line"
+ continue
+ fi
+
+ # right, looks good
+ if [ -n "$skipping" ]; then
+ skipping=
+ start_lines="$start_lines $line_num"
+ fi
+
+ case $key in
+ display_name)
+ if [ -n "$display_name" ]; then
+ DEBUG "duplicate $key"
+ errors=yes
+ fi
+ display_name="$val"
+ ;;
+ file)
+ if [ -n "$file" ]; then
+ DEBUG "duplicate $key"
+ errors=yes
+ fi
+ file="$val"
+ ;;
+ size)
+ if [ -n "$size" ]; then
+ DEBUG "duplicate $key"
+ errors=yes
+ fi
+ size="$val"
+ ;;
+ url)
+ url="$val"
+ ;;
+ md5)
+ md5="$val"
+ ;;
+ sha1)
+ sha1="$val"
+ ;;
+ esac
+ else
+ # Between paragraphs. Time to check what we've found so far.
+ end_lines="$end_lines $line_num"
+ count=$(( count + 1))
+
+ if [ -z "$display_name" ]; then
+ DEBUG "image $count is missing display_name"
+ errors=yes
+ fi
+ if [ -z "$file" ]; then
+ DEBUG "image $count is missing file"
+ errors=yes
+ fi
+ if [ -z "$size" ]; then
+ DEBUG "image $count is missing size"
+ errors=yes
+ fi
+ if [ -z "$url" ]; then
+ DEBUG "image $count is missing url"
+ errors=yes
+ fi
+ if [ "$CHECK" = "md5sum" ] && [ -z "$md5" ]; then
+ DEBUG "image $count is missing required md5"
+ errors=yes
+ fi
+ if [ "$CHECK" = "sha1sum" ] && [ -z "$sha1" ]; then
+ DEBUG "image $count is missing required sha1"
+ errors=yes
+ fi
+
+ # Prepare for next stanza
+ display_name=
+ file=
+ size=
+ url=
+ md5=
+ sha1=
+ skipping=yes
+ fi
+ done < "$config"
+
+ DEBUG "$count images found"
+ num_images="$count"
+
+ DEBUG "start_lines=($start_lines)"
+ DEBUG "end_lines=($end_lines)"
+
+ # return error status
+ [ "$count" != "0" ] && [ -z "$errors" ]
+}
+
+
+# Make the user pick an image to download. On success, it sets the global
+# variable 'user_choice' to the selected image number.
+choose_image() {
+ local show
+ local count
+ local line
+ local num
+
+ show=yes
+ while true; do
+ if [ -n "$show" ]; then
+ echo
+ echo "There are $num_images recovery images to choose from:"
+ echo
+ count=0
+ echo "0 - <quit>"
+ grep '^display_name=' "$config" | while read line; do
+ count=$(( count + 1 ))
+ echo "$line" | sed "s/display_name=/$count - /"
+ done
+ echo
+ show=
+ fi
+ echo -n "Please select a recovery image to download: "
+ read num
+ if [ -z "$num" ] || [ "$num" = "?" ]; then
+ show=yes
+ elif echo "$num" | grep -q '[^0-9]'; then
+ echo "Sorry, I didn't understand that."
+ else
+ if [ "$num" -lt "0" ] || [ "$num" -gt "$num_images" ]; then
+ echo "That's not one of the choices."
+ elif [ "$num" -eq 0 ]; then
+ quit
+ else
+ break;
+ fi
+ fi
+ done
+ echo
+
+ # global
+ user_choice="$num"
+}
+
+# Fetch and verify the user's chosen image. On success, it sets the global
+# variable 'image_file' to indicate the local name of the unpacked binary that
+# should be written to the USB drive.
+fetch_image() {
+ local start
+ local end
+ local line
+ local key
+ local val
+ local file
+ local size
+ local url
+ local md5
+ local sha1
+ local line_num
+ local tarball
+ local err
+ local sum
+
+ file=
+ size=
+ url=
+ md5=
+ sha1=
+ line_num="0"
+
+ # Convert image number to line numbers within config file.
+ start=$(echo $start_lines | cut -d' ' -f$1)
+ end=$(echo $end_lines | cut -d' ' -f$1)
+
+ while read line; do
+ # Skip to the start of the desired stanza
+ line_num=$(( line_num + 1 ))
+ if [ "$line_num" -lt "$start" ] || [ "$line_num" -ge "$end" ]; then
+ continue;
+ fi
+
+ # Process the stanza.
+ if [ -n "$line" ]; then
+ key=${line%=*}
+ val=${line#*=}
+ if [ -z "$key" ] || [ -z "$val" ] || [ "$key=$val" != "$line" ]; then
+ DEBUG "ignoring $line"
+ continue
+ fi
+
+ case $key in
+ # The descriptive stuff we'll just save for later.
+ file)
+ file="$val"
+ ;;
+ size)
+ size="$val"
+ ;;
+ md5)
+ md5="$val"
+ ;;
+ sha1)
+ sha1="$val"
+ ;;
+ url)
+ # Try to download each url until one works.
+ if [ -n "$url" ]; then
+ # We've already got one (it's very nice).
+ continue;
+ fi
+ warn "Downloading image tarball from $val"
+ warn
+ tarball=${val##*/}
+ if $FETCHCONT "$tarball" "$val"; then
+ # Got it.
+ url="$val"
+ else
+ # If you give curl the '-C -' option but the file you want is
+ # already complete and the server doesn't report the total size
+ # correctly, it will report an error instead of just doing nothing.
+ # We'll try to work around that.
+ err=$?
+ if [ "$FETCH" = "curl" ] && [ "$err" = "18" ]; then
+ warn "Ignoring spurious complaint"
+ url="$val"
+ fi
+ fi
+ ;;
+ esac
+ fi
+ done < "$config"
+
+ if [ -z "$url" ]; then
+ DEBUG "couldn't fetch tarball"
+ return 1
+ fi
+
+ # Verify the tarball
+ if ! ls -l "$tarball" | grep -q "$size"; then
+ DEBUG "size is wrong"
+ return 1
+ fi
+ sum=$($CHECK "$tarball" | cut -d' ' -f1)
+ DEBUG "$CHECK is $sum"
+ if [ "$CHECK" = "md5sum" ] && [ "$sum" != "$md5" ]; then
+ DEBUG "wrong $CHECK"
+ return 1
+ elif [ "$CHECK" = "sha1sum" ] && [ "$sum" != "$sha1" ]; then
+ DEBUG "wrong $CHECK"
+ return 1
+ fi
+
+ # Unpack the file
+ warn "Unpacking the tarball"
+ rm -f "$file"
+ if ! unzip "$tarball" "$file"; then
+ DEBUG "Can't unpack the tarball"
+ return 1
+ fi
+
+ # global
+ image_file="$file"
+}
+
+##############################################################################
+# Helper functions to manage USB drives.
+
+# Return a list of base device names ("sda sdb ...") for all USB drives
+get_devlist() {
+ local dev
+ local t
+ local r
+
+ for dev in $(cat /proc/partitions); do
+ [ -r "/sys/block/$dev/device/type" ] &&
+ t=$(cat "/sys/block/$dev/device/type") &&
+ [ "$t" = "0" ] &&
+ r=$(cat "/sys/block/$dev/removable") &&
+ [ "$r" = "1" ] &&
+ readlink -f "/sys/block/$dev" | grep -q -i usb &&
+ echo "$dev" || true
+ done
+}
+
+# Return descriptions for each provided base device name ("sda sdb ...")
+get_devinfo() {
+ local dev
+ local v
+ local m
+ local s
+ local ss
+
+ for dev in $1; do
+ v=$(cat "/sys/block/$dev/device/vendor") &&
+ m=$(cat "/sys/block/$dev/device/model") &&
+ s=$(cat "/sys/block/$dev/size") && ss=$(( $s * 512 / 1000000 )) &&
+ echo "/dev/$dev ${ss}MB $v $m"
+ done
+}
+
+# Enumerate and descript the specified base device names ("sda sdb ...")
+get_choices() {
+ local dev
+ local desc
+ local count
+
+ count=1
+ echo "0 - <quit>"
+ for dev in $1; do
+ desc=$(get_devinfo "$dev")
+ echo "$count - Use $desc"
+ count=$(( count + 1 ))
+ done
+}
+
+# Make the user pick a USB drive to write to. On success, it sets the global
+# variable 'user_choice' to the selected device name ("sda", "sdb", etc.)
+choose_drive() {
+ local show
+ local devlist
+ local choices
+ local num_drives
+ local msg
+ local num
+
+ show=yes
+ while true; do
+ if [ -n "$show" ]; then
+ devlist=$(get_devlist)
+ choices=$(get_choices "$devlist")
+ if [ -z "$devlist" ]; then
+ num_drives="0"
+ msg="I can't seem to find a valid USB drive."
+ else
+ num_drives=$(echo "$devlist" | wc -l)
+ if [ "$num_drives" != "1" ]; then
+ msg="I found $num_drives USB drives"
+ else
+ msg="I found $num_drives USB drive"
+ fi
+ fi
+ echo -n "
+
+$msg
+
+$choices
+
+"
+ show=
+ fi
+ echo -n "Tell me what to do (or just press Enter to scan again): "
+ read num
+ if [ -z "$num" ] || [ "$num" = "?" ]; then
+ show=yes
+ elif echo "$num" | grep -q '[^0-9]'; then
+ echo "Sorry, I didn't understand that."
+ else
+ if [ "$num" -lt "0" ] || [ "$num" -gt "$num_drives" ]; then
+ echo "That's not one of the choices."
+ elif [ "$num" -eq 0 ]; then
+ quit
+ else
+ break;
+ fi
+ fi
+ done
+
+ # global
+ user_choice=$(echo $devlist | cut -d' ' -f$num)
+}
+
+
+##############################################################################
+# Okay, do something...
+
+# Make sure we have the tools we need
+require_utils
+
+# Need a place to work. We prefer a fixed location so we can try to resume any
+# interrupted downloads.
+if [ -n "$WORKDIR" ]; then
+ if [ ! -d "$WORKDIR" ] && ! mkdir "$WORKDIR" ; then
+ warn "Using temporary directory"
+ WORKDIR=
+ fi
+fi
+if [ -z "$WORKDIR" ]; then
+ WORKDIR=$(mktemp -d)
+ # Clean up temporary directory afterwards
+ trap "cd; rm -rf ${WORKDIR}" EXIT
+fi
+
+cd "$WORKDIR"
+warn "Working in $WORKDIR/"
+rm -f "$debug"
+
+# Download the config file to see what choices we have.
+warn "Downloading config file from $CONFIGURL"
+$FETCHNEW "$tmpfile" "$CONFIGURL" || \
+ gfatal "Unable to download the config file"
+
+# Un-DOS-ify the config file and separate the version info from the images
+sed 's/\r//g' "$tmpfile" | grep '^recovery_tool' > "$version"
+sed 's/\r//g' "$tmpfile" | grep -v '^#' | grep -v '^recovery_tool' > "$config"
+# Add one empty line to the config file to terminate the last stanza
+echo >> "$config"
+
+# Make sure that the config file version matches this script version
+tmp=$(grep '^recovery_tool_version=' "$version") || \
+ gfatal "The config file doesn't contain a version string."
+filevers=${tmp#*=}
+if [ "$filevers" != "$MYVERSION" ]; then
+ tmp=$(grep '^recovery_tool_update=' "$version");
+ msg=${tmp#*=}
+ warn "This tool is version $MYVERSION." \
+ "The config file is for version $filevers."
+ fatal ${msg:-Please download a matching version of the tool and try again.}
+fi
+
+# Check the config file to be sure it's valid. As a side-effect, this sets the
+# global variable 'num_images' with the number of image stanzas read, but
+# that's independent of whether the config is valid.
+good_config || gfatal "The config file isn't valid."
+
+# Make the user pick an image to download, or exit.
+choose_image
+
+# Download the user's choice
+fetch_image "$user_choice" || \
+ gfatal "Unable to download a valid recovery image."
+
+# Make the user pick a USB drive, or exit.
+choose_drive
+
+# Be sure
+dev_desc=$(get_devinfo "$user_choice")
+echo "
+Is this the device you want to put the recovery image on?
+
+ $dev_desc
+"
+echo -n "You must enter 'YES' to continue: "
+read tmp
+if [ "$tmp" != "YES" ]; then
+ quit
+fi
+
+# Be very sure
+echo "
+
+I'm really going to erase this device. This will permanently ERASE
+whatever you may have on that drive. You won't be able to undo it.
+
+ $dev_desc
+"
+
+echo -n "If you're sure that's the device to use, enter 'DoIt' now: "
+read tmp
+if [ "$tmp" != "DoIt" ]; then
+ quit
+fi
+echo "
+
+Installing the recovery image
+
+"
+
+# Unmount anything on that device.
+echo "unmounting..."
+for tmp in $(mount | grep ^"/dev/${user_choice}" | sed 's/[ \t].*//'); do
+ umount $tmp || ufatal "Unable to unmount $tmp."
+done
+
+# Write it.
+echo "copying... (this may take several minutes)"
+dd of=/dev/${user_choice} if="$image_file" ||
+ ufatal "Unable to write the image."
+sync
+
+echo "
+
+Done. Remove the USB drive and insert it in your Chrome OS netbook.
+
+"
+
+exit 0
« 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