1#!/bin/sh 2# 3# Copyright (c) 2011 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# Note: This file must be written in dash compatible way as scripts that use 8# this may run in the Chrome OS client enviornment. 9 10# Determine script directory 11SCRIPT_DIR=$(dirname $0) 12PROG=$(basename $0) 13GPT=${GPT:-"cgpt"} 14 15# The tag when the rootfs is changed. 16TAG_NEEDS_TO_BE_SIGNED="/root/.need_to_be_signed" 17 18# List of Temporary files and mount points. 19TEMP_FILE_LIST=$(mktemp) 20TEMP_DIR_LIST=$(mktemp) 21 22# Finds and loads the 'shflags' library, or return as failed. 23load_shflags() { 24 # Load shflags 25 if [ -f /usr/share/misc/shflags ]; then 26 . /usr/share/misc/shflags 27 elif [ -f "${SCRIPT_DIR}/lib/shflags/shflags" ]; then 28 . "${SCRIPT_DIR}/lib/shflags/shflags" 29 else 30 echo "ERROR: Cannot find the required shflags library." 31 return 1 32 fi 33 34 # Add debug option for debug output below 35 DEFINE_boolean debug $FLAGS_FALSE "Provide debug messages" "d" 36} 37 38# Functions for debug output 39# ---------------------------------------------------------------------------- 40 41# Reports error message and exit(1) 42# Args: error message 43err_die() { 44 echo "ERROR: $*" 1>&2 45 exit 1 46} 47 48# Returns true if we're running in debug mode. 49# 50# Note that if you don't set up shflags by calling load_shflags(), you 51# must set $FLAGS_debug and $FLAGS_TRUE yourself. The default 52# behavior is that debug will be off if you define neither $FLAGS_TRUE 53# nor $FLAGS_debug. 54is_debug_mode() { 55 [ "${FLAGS_debug:-not$FLAGS_TRUE}" = "$FLAGS_TRUE" ] 56} 57 58# Prints messages (in parameters) in debug mode 59# Args: debug message 60debug_msg() { 61 if is_debug_mode; then 62 echo "DEBUG: $*" 1>&2 63 fi 64} 65 66# Functions for temporary files and directories 67# ---------------------------------------------------------------------------- 68 69# Create a new temporary file and return its name. 70# File is automatically cleaned when cleanup_temps_and_mounts() is called. 71make_temp_file() { 72 local tempfile=$(mktemp) 73 echo "$tempfile" >> $TEMP_FILE_LIST 74 echo $tempfile 75} 76 77# Create a new temporary directory and return its name. 78# Directory is automatically deleted and any filesystem mounted on it unmounted 79# when cleanup_temps_and_mounts() is called. 80make_temp_dir() { 81 local tempdir=$(mktemp -d) 82 echo "$tempdir" >> $TEMP_DIR_LIST 83 echo $tempdir 84} 85 86cleanup_temps_and_mounts() { 87 for i in $(cat $TEMP_FILE_LIST); do 88 rm -f $i 89 done 90 set +e # umount may fail for unmounted directories 91 for i in $(cat $TEMP_DIR_LIST); do 92 if [ -n "$i" ]; then 93 if has_needs_to_be_resigned_tag "$i"; then 94 echo "Warning: image may be modified. Please resign image." 95 fi 96 sudo umount $i 2>/dev/null 97 rm -rf $i 98 fi 99 done 100 set -e 101 rm -rf $TEMP_DIR_LIST $TEMP_FILE_LIST 102} 103 104trap "cleanup_temps_and_mounts" EXIT 105 106# Functions for partition management 107# ---------------------------------------------------------------------------- 108 109# Construct a partition device name from a whole disk block device and a 110# partition number. 111# This works for [/dev/sda, 3] (-> /dev/sda3) as well as [/dev/mmcblk0, 2] 112# (-> /dev/mmcblk0p2). 113make_partition_dev() { 114 local block="$1" 115 local num="$2" 116 # If the disk block device ends with a number, we add a 'p' before the 117 # partition number. 118 if [ "${block%[0-9]}" = "${block}" ]; then 119 echo "${block}${num}" 120 else 121 echo "${block}p${num}" 122 fi 123} 124 125# Read GPT table to find the starting location of a specific partition. 126# Args: DEVICE PARTNUM 127# Returns: offset (in sectors) of partition PARTNUM 128partoffset() { 129 sudo $GPT show -b -i $2 $1 130} 131 132# Read GPT table to find the size of a specific partition. 133# Args: DEVICE PARTNUM 134# Returns: size (in sectors) of partition PARTNUM 135partsize() { 136 sudo $GPT show -s -i $2 $1 137} 138 139# Tags a file system as "needs to be resigned". 140# Args: MOUNTDIRECTORY 141tag_as_needs_to_be_resigned() { 142 local mount_dir="$1" 143 sudo touch "$mount_dir/$TAG_NEEDS_TO_BE_SIGNED" 144} 145 146# Determines if the target file system has the tag for resign 147# Args: MOUNTDIRECTORY 148# Returns: true if the tag is there otherwise false 149has_needs_to_be_resigned_tag() { 150 local mount_dir="$1" 151 [ -f "$mount_dir/$TAG_NEEDS_TO_BE_SIGNED" ] 152} 153 154# Determines if the target file system is a Chrome OS root fs 155# Args: MOUNTDIRECTORY 156# Returns: true if MOUNTDIRECTORY looks like root fs, otherwise false 157is_rootfs_partition() { 158 local mount_dir="$1" 159 [ -f "$mount_dir/$(dirname "$TAG_NEEDS_TO_BE_SIGNED")" ] 160} 161 162# If the kernel is buggy and is unable to loop+mount quickly, 163# retry the operation a few times. 164# Args: IMAGE PARTNUM MOUNTDIRECTORY [ro] 165_mount_image_partition_retry() { 166 local image=$1 167 local partnum=$2 168 local mount_dir=$3 169 local ro=$4 170 local offset=$(( $(partoffset "$image" "$partnum") * 512 )) 171 local out try 172 173 if [ "$ro" != "ro" ]; then 174 # Forcibly call enable_rw_mount. It should fail on unsupported 175 # filesystems and be idempotent on ext*. 176 enable_rw_mount "$image" ${offset} 2> /dev/null 177 fi 178 179 set -- sudo LC_ALL=C mount -o loop,offset=${offset},${ro} \ 180 "${image}" "${mount_dir}" 181 try=1 182 while [ ${try} -le 5 ]; do 183 if ! out=$("$@" 2>&1); then 184 if [ "${out}" = "mount: you must specify the filesystem type" ]; then 185 printf 'WARNING: mounting %s at %s failed (try %i); retrying\n' \ 186 "${image}" "${mount_dir}" "${try}" 187 # Try to "quiet" the disks and sleep a little to reduce contention. 188 sync 189 sleep $(( try * 5 )) 190 else 191 # Failed for a different reason; abort! 192 break 193 fi 194 else 195 # It worked! 196 return 0 197 fi 198 : $(( try += 1 )) 199 done 200 echo "ERROR: mounting ${image} at ${mount_dir} failed:" 201 echo "${out}" 202 # We don't preserve the exact exit code of `mount`, but since 203 # no one in this code base seems to check it, it's a moot point. 204 return 1 205} 206 207# Mount a partition read-only from an image into a local directory 208# Args: IMAGE PARTNUM MOUNTDIRECTORY 209mount_image_partition_ro() { 210 _mount_image_partition_retry "$@" "ro" 211} 212 213# Mount a partition from an image into a local directory 214# Args: IMAGE PARTNUM MOUNTDIRECTORY 215mount_image_partition() { 216 local mount_dir=$3 217 _mount_image_partition_retry "$@" 218 if is_rootfs_partition "$mount_dir"; then 219 tag_as_needs_to_be_resigned "$mount_dir" 220 fi 221} 222 223# Extract a partition to a file 224# Args: IMAGE PARTNUM OUTPUTFILE 225extract_image_partition() { 226 local image=$1 227 local partnum=$2 228 local output_file=$3 229 local offset=$(partoffset "$image" "$partnum") 230 local size=$(partsize "$image" "$partnum") 231 dd if=$image of=$output_file bs=512 skip=$offset count=$size \ 232 conv=notrunc 2>/dev/null 233} 234 235# Replace a partition in an image from file 236# Args: IMAGE PARTNUM INPUTFILE 237replace_image_partition() { 238 local image=$1 239 local partnum=$2 240 local input_file=$3 241 local offset=$(partoffset "$image" "$partnum") 242 local size=$(partsize "$image" "$partnum") 243 dd if=$input_file of=$image bs=512 seek=$offset count=$size \ 244 conv=notrunc 2>/dev/null 245} 246 247# For details, see crosutils.git/common.sh 248enable_rw_mount() { 249 local rootfs="$1" 250 local offset="${2-0}" 251 252 # Make sure we're checking an ext2 image 253 if ! is_ext2 "$rootfs" $offset; then 254 echo "enable_rw_mount called on non-ext2 filesystem: $rootfs $offset" 1>&2 255 return 1 256 fi 257 258 local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte 259 # Dash can't do echo -ne, but it can do printf "\NNN" 260 # We could use /dev/zero here, but this matches what would be 261 # needed for disable_rw_mount (printf '\377'). 262 printf '\000' | 263 sudo dd of="$rootfs" seek=$((offset + ro_compat_offset)) \ 264 conv=notrunc count=1 bs=1 2>/dev/null 265} 266 267# For details, see crosutils.git/common.sh 268is_ext2() { 269 local rootfs="$1" 270 local offset="${2-0}" 271 272 # Make sure we're checking an ext2 image 273 local sb_magic_offset=$((0x438)) 274 local sb_value=$(sudo dd if="$rootfs" skip=$((offset + sb_magic_offset)) \ 275 count=2 bs=1 2>/dev/null) 276 local expected_sb_value=$(printf '\123\357') 277 if [ "$sb_value" = "$expected_sb_value" ]; then 278 return 0 279 fi 280 return 1 281} 282 283disable_rw_mount() { 284 local rootfs="$1" 285 local offset="${2-0}" 286 287 # Make sure we're checking an ext2 image 288 if ! is_ext2 "$rootfs" $offset; then 289 echo "disable_rw_mount called on non-ext2 filesystem: $rootfs $offset" 1>&2 290 return 1 291 fi 292 293 local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte 294 # Dash can't do echo -ne, but it can do printf "\NNN" 295 # We could use /dev/zero here, but this matches what would be 296 # needed for disable_rw_mount (printf '\377'). 297 printf '\377' | 298 sudo dd of="$rootfs" seek=$((offset + ro_compat_offset)) \ 299 conv=notrunc count=1 bs=1 2>/dev/null 300} 301 302rw_mount_disabled() { 303 local rootfs="$1" 304 local offset="${2-0}" 305 306 # Make sure we're checking an ext2 image 307 if ! is_ext2 "$rootfs" $offset; then 308 return 2 309 fi 310 311 local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte 312 local ro_value=$(sudo dd if="$rootfs" skip=$((offset + ro_compat_offset)) \ 313 count=1 bs=1 2>/dev/null) 314 local expected_ro_value=$(printf '\377') 315 if [ "$ro_value" = "$expected_ro_value" ]; then 316 return 0 317 fi 318 return 1 319} 320 321# Misc functions 322# ---------------------------------------------------------------------------- 323 324# Returns true if all files in parameters exist. 325# Args: List of files 326ensure_files_exist() { 327 local filename return_value=0 328 for filename in "$@"; do 329 if [ ! -f "$filename" -a ! -b "$filename" ]; then 330 echo "ERROR: Cannot find required file: $filename" 331 return_value=1 332 fi 333 done 334 335 return $return_value 336} 337 338# Check if the 'chronos' user already has a password 339# Args: rootfs 340no_chronos_password() { 341 local rootfs=$1 342 sudo grep -q '^chronos:\*:' "$rootfs/etc/shadow" 343} 344 345trap "cleanup_temps_and_mounts" EXIT 346