Home | History | Annotate | Download | only in image_signing
      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