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 11 SCRIPT_DIR=$(dirname $0) 12 PROG=$(basename $0) 13 GPT=${GPT:-"cgpt"} 14 15 # The tag when the rootfs is changed. 16 TAG_NEEDS_TO_BE_SIGNED="/root/.need_to_be_signed" 17 18 # List of Temporary files and mount points. 19 TEMP_FILE_LIST=$(mktemp) 20 TEMP_DIR_LIST=$(mktemp) 21 22 # Finds and loads the 'shflags' library, or return as failed. 23 load_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 43 err_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. 54 is_debug_mode() { 55 [ "${FLAGS_debug:-not$FLAGS_TRUE}" = "$FLAGS_TRUE" ] 56 } 57 58 # Prints messages (in parameters) in debug mode 59 # Args: debug message 60 debug_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. 71 make_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. 80 make_temp_dir() { 81 local tempdir=$(mktemp -d) 82 echo "$tempdir" >> $TEMP_DIR_LIST 83 echo $tempdir 84 } 85 86 cleanup_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 104 trap "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). 113 make_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 128 partoffset() { 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 135 partsize() { 136 sudo $GPT show -s -i $2 $1 137 } 138 139 # Tags a file system as "needs to be resigned". 140 # Args: MOUNTDIRECTORY 141 tag_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 149 has_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 157 is_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 209 mount_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 215 mount_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 225 extract_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 237 replace_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 248 enable_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 268 is_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 283 disable_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 302 rw_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 326 ensure_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 340 no_chronos_password() { 341 local rootfs=$1 342 sudo grep -q '^chronos:\*:' "$rootfs/etc/shadow" 343 } 344 345 trap "cleanup_temps_and_mounts" EXIT 346