mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-11-24 02:05:01 +00:00
Change-Id: Ib41cdd22d542004bd776828a43ae687942bc5ccc BUG=chromium-os:781 TEST=manual, as before. Review URL: http://codereview.chromium.org/5516010
745 lines
18 KiB
Bash
Executable File
745 lines
18 KiB
Bash
Executable File
#!/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.
|
|
#
|
|
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? Note that we can override this by just
|
|
# specifying the config file URL on the command line.
|
|
CONFIGURL="${1:-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'
|
|
|
|
|
|
##############################################################################
|
|
# 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
|
|
# CHECK = command to invoke to generate checksums on a file
|
|
# CHECKTYPE = type of checksum generated
|
|
#
|
|
require_utils() {
|
|
local external
|
|
local errors
|
|
local tool
|
|
local tmp
|
|
|
|
external='cat cut dd grep ls mkdir mount readlink sed sync tr 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: need \"$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
|
|
fi
|
|
if [ -z "$FETCH" ] && tmp=$(type wget 2>/dev/null) ; then
|
|
FETCH=wget
|
|
fi
|
|
if [ -z "$FETCH" ]; then
|
|
warn "ERROR: need \"curl\" or \"wget\""
|
|
errors=yes
|
|
fi
|
|
|
|
# Once we've fetched a file we need to compute its checksum. There are
|
|
# multiple possiblities here too.
|
|
CHECK=
|
|
if [ -z "$CHECK" ] && tmp=$(type md5sum 2>/dev/null) ; then
|
|
CHECK="md5sum"
|
|
CHECKTYPE="md5"
|
|
fi
|
|
if [ -z "$CHECK" ] && tmp=$(type sha1sum 2>/dev/null) ; then
|
|
CHECK="sha1sum"
|
|
CHECKTYPE="sha1"
|
|
fi
|
|
if [ -z "$CHECK" ] && tmp=$(type openssl 2>/dev/null) ; then
|
|
CHECK="openssl"
|
|
CHECKTYPE="md5"
|
|
fi
|
|
if [ -z "$CHECK" ]; then
|
|
warn "ERROR: need \"md5sum\" or \"sha1sum\" or \"openssl\""
|
|
errors=yes
|
|
fi
|
|
|
|
if [ -n "$errors" ]; then
|
|
ufatal "Some required linux utilities are missing."
|
|
fi
|
|
}
|
|
|
|
# This retrieves a URL and stores it locally. It uses the global variable
|
|
# 'FETCH' to determine the utility (and args) to invoke.
|
|
# Args: URL FILENAME [RESUME]
|
|
fetch_url() {
|
|
local url
|
|
local filename
|
|
local resume
|
|
local err
|
|
|
|
url="$1"
|
|
filename="$2"
|
|
resume="${3:-}"
|
|
|
|
DEBUG "FETCH=($FETCH) url=($url) filename=($filename) resume=($resume)"
|
|
|
|
if [ "$FETCH" = "curl" ]; then
|
|
if [ -z "$resume" ]; then
|
|
# quietly fetch a new copy each time
|
|
rm -f "$filename"
|
|
curl -f -s -S -o "$filename" "$url"
|
|
else
|
|
# continue where we left off, if possible
|
|
curl -f -C - -o "$filename" "$url"
|
|
# 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 [ "$err" = "18" ]; then
|
|
warn "Ignoring spurious complaint"
|
|
true
|
|
fi
|
|
fi
|
|
elif [ "$FETCH" = "wget" ]; then
|
|
if [ -z "$resume" ]; then
|
|
# quietly fetch a new copy each time
|
|
rm -f "$filename"
|
|
wget -nv -q -O "$filename" "$url"
|
|
else
|
|
# continue where we left off, if possible
|
|
wget -c -O "$filename" "$url"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# This returns a checksum on a file. It uses the global variable 'CHECK' to
|
|
# determine the utility (and args) to invoke.
|
|
# Args: FILENAME
|
|
compute_checksum() {
|
|
local filename
|
|
|
|
filename="$1"
|
|
|
|
DEBUG "CHECK=($CHECK) CHECKTYPE=($CHECKTYPE)"
|
|
|
|
if [ "$CHECK" = "openssl" ]; then
|
|
openssl md5 < "$filename"
|
|
else
|
|
$CHECK "$tarball" | cut -d' ' -f1
|
|
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 [ "$CHECKTYPE" = "md5" ] && [ -z "$md5" ]; then
|
|
DEBUG "image $count is missing required md5"
|
|
errors=yes
|
|
fi
|
|
if [ "$CHECKTYPE" = "sha1" ] && [ -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 fetch_url "$val" "$tarball" "resumeok"; then
|
|
# Got it.
|
|
url="$val"
|
|
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=$(compute_checksum "$tarball")
|
|
DEBUG "checksum is $sum"
|
|
if [ "$CHECKTYPE" = "md5" ] && [ "$sum" != "$md5" ]; then
|
|
DEBUG "wrong $CHECK"
|
|
return 1
|
|
elif [ "$CHECKTYPE" = "sha1" ] && [ "$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"
|
|
fetch_url "$CONFIGURL" "$tmpfile" || \
|
|
gfatal "Unable to download the config file"
|
|
|
|
# Un-DOS-ify the config file and separate the version info from the images
|
|
tr -d '\015' < "$tmpfile" | grep '^recovery_tool' > "$version"
|
|
tr -d '\015' < "$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}" | cut -d' ' -f1); 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
|