(factory-241) Sync factory builder scripts with ToT

We've seen Alex factory bundle corrupted several times, and usually
caused tools modified SSD rootfs partition unexpectedly.

ToT make_factory_package has more safe and solid image type detection
ability so we should port that to R11 factory.

BUG=chrome-os-partner:6299
TEST=./make_factory_package --release PATH_TO_RECOVERY \
     --factory PATH_TO_FACTORY_TEST \
     --firmware PATH_TO_FIRMWARE \
     --hwid none

Change-Id: I9a2aa34b32e22ea21dd8e814b1e7ef53d80bcd04
Reviewed-on: https://gerrit.chromium.org/gerrit/10482
Reviewed-by: Jay Kim <yongjaek@chromium.org>
Tested-by: Hung-Te Lin <hungte@chromium.org>
diff --git a/lib/cros_image_common.sh b/lib/cros_image_common.sh
index b9e2802..d550240 100644
--- a/lib/cros_image_common.sh
+++ b/lib/cros_image_common.sh
@@ -45,6 +45,16 @@
   image_has_command cgpt || image_has_command parted
 }
 
+# Finds if specified tool can be found by current path; updates system path if
+# the tool is available in given folder.
+image_find_tool() {
+  local tool="$1"
+  local alternative_folder="$(readlink -f "$2")"
+  if ! image_has_command "$tool" && [ -x "$alternative_folder/$tool" ]; then
+    PATH="$alternative_folder:$PATH"; export PATH
+  fi
+}
+
 # Finds the best partition tool and print partition offset
 image_part_offset() {
   local file="$1"
@@ -55,6 +65,8 @@
   if image_has_command cgpt; then
     cgpt show -b -i "$partno" "$file"
   elif image_has_command parted; then
+    # First trial-run to make sure image is valid (because awk always return 0)
+    parted -m "$file" unit s print | grep -qs "^$partno:" || exit 1
     parted -m "$file" unit s print | awk -F ':' "/^$partno:/ { print int(\$2) }"
   elif [ -f "$unpack_file" ]; then
     awk "/ $partno  *Label:/ { print \$2 }" "$unpack_file"
@@ -73,6 +85,8 @@
   if image_has_command cgpt; then
     cgpt show -s -i "$partno" "$file"
   elif image_has_command parted; then
+    # First trial-run to make sure image is valid (because awk always return 0)
+    parted -m "$file" unit s print | grep -qs "^$partno:" || exit 1
     parted -m "$file" unit s print | awk -F ':' "/^$partno:/ { print int(\$4) }"
   elif [ -s "$unpack_file" ]; then
     awk "/ $partno  *Label:/ { print \$3 }" "$unpack_file"
@@ -88,15 +102,13 @@
   local sectors="$3"
   local bs=512
 
-  # Try to use larger buffer if offset/size can be re-aligned.
-  # 2M / 512 = 4096
-  local buffer_ratio=4096
-  if [ $((offset % buffer_ratio)) -eq 0 -a \
-       $((sectors % buffer_ratio)) -eq 0 ]; then
-    offset=$((offset / buffer_ratio))
-    sectors=$((sectors / buffer_ratio))
-    bs=$((bs * buffer_ratio))
-  fi
+  # Increase buffer size as much as possible until 8M
+  while [ $((bs < (8 * 1024 * 1024) && sectors > 0 &&
+             offset % 2 == 0 && sectors % 2 == 0)) = "1" ]; do
+    bs=$((bs * 2))
+    offset=$((offset / 2))
+    sectors=$((sectors / 2))
+  done
 
   if image_has_command pv; then
     dd if="$file" bs=$bs skip="$offset" count="$sectors" \
@@ -120,6 +132,47 @@
   image_dump_partial_file "$file" "$offset" "$size"
 }
 
+# Updates a file (from stdin) by given offset and size (in sectors)
+image_update_partial_file() {
+  local file="$1"
+  local offset="$2"
+  local sectors="$3"
+  local bs=512
+  local oflag="oflag=dsync"
+
+  # Improve performance if we're not updating block (Ex, USB) devices
+  [ -b "$file" ] || oflag=""
+
+  # Increase buffer size as much as possible until 8M
+  while [ $((bs < (8 * 1024 * 1024) && sectors > 0 &&
+             offset % 2 == 0 && sectors % 2 == 0)) = "1" ]; do
+    bs=$((bs * 2))
+    offset=$((offset / 2))
+    sectors=$((sectors / 2))
+  done
+
+  if image_has_command pv; then
+    pv -ptreb -B $bs -s $((sectors * bs)) |
+      dd of="$file" bs=$bs seek="$offset" count="$sectors" \
+        iflag=fullblock $oflag conv=notrunc status=noxfer 2>/dev/null
+  else
+    dd of="$file" bs=$bs seek="$offset" count="$sectors" \
+      iflag=fullblock $oflag conv=notrunc status=noxfer 2>/dev/null
+  fi
+}
+
+# Updates a specific partition in given image file (from stdin)
+image_update_partition() {
+  local file="$1"
+  local part_num="$2"
+  local offset="$(image_part_offset "$file" "$part_num")" ||
+    image_die "failed to find partition #$part_num from: $file"
+  local size="$(image_part_size "$file" "$part_num")" ||
+    image_die "failed to find partition #$part_num from: $file"
+
+  image_update_partial_file "$file" "$offset" "$size"
+}
+
 # Maps a specific partition from given image file to a loop device
 image_map_partition() {
   local file="$1"
@@ -129,7 +182,7 @@
   local size="$(image_part_size "$file" "$part_num")" ||
     image_die "failed to find partition #$part_num from: $file"
 
-  losetup --offset $((offset * 512)) --sizelimit=$((size * 512)) \
+  sudo losetup --offset $((offset * 512)) --sizelimit=$((size * 512)) \
     -f --show "$file"
 }
 
@@ -137,7 +190,7 @@
 image_unmap_partition() {
   local map_point="$1"
 
-  losetup -d "$map_point"
+  sudo losetup -d "$map_point"
 }
 
 # Mounts a specific partition inside a given image file
@@ -156,7 +209,7 @@
     mount_opt=",ro"
   fi
 
-  mount \
+  sudo mount \
     -o "loop,offset=$((offset * 512)),sizelimit=$((size * 512)),$mount_opt" \
     "$file" \
     "$mount_point"
@@ -166,35 +219,116 @@
 image_umount_partition() {
   local mount_point="$1"
 
-  umount -d "$mount_point"
+  sudo umount -d "$mount_point"
 }
 
-# Copy a partition from one image to another.
+# Copy a partition from one image to another (size must be equal)
 image_partition_copy() {
-  local src="$1"
-  local srcpart="$2"
-  local dst="$3"
-  local dstpart="$4"
-
-  local srcoffset=$(image_part_offset "${src}" "${srcpart}")
-  local dstoffset=$(image_part_offset "${dst}" "${dstpart}")
-  local length=$(image_part_size "${src}" "${srcpart}")
-  local dstlength=$(image_part_size "${dst}" "${dstpart}")
-
-  if [ "${length}" -gt  "${dstlength}" ]; then
-    exit 1
+  local src="$1" src_part="$2" dst="$3" dst_part="$4"
+  local size1="$(image_part_size "$src" "$src_part")"
+  local size2="$(image_part_size "$dst" "$dst_part")"
+  if [ "$size1" != "$size2" ]; then
+    die "Partition size different: ($size1 != $size2)"
   fi
+  image_dump_partition "$src" "$src_part" 2>/dev/null |
+    image_update_partition "$dst" "$dst_part"
+}
 
-  # Try to use larger buffer if offset/size can be re-aligned.
-  # 2M / 512 = 4096
-  local buffer_ratio=4096
-  local bs=512
-  if [ $((dstoffset % buffer_ratio)) -eq 0 -a \
-       $((length % buffer_ratio)) -eq 0 ]; then
-    dstoffset=$((dstoffset / buffer_ratio))
-    bs=$((bs * buffer_ratio))
+# Copy a partition from one image to another (source <= dest)
+image_partition_overwrite() {
+  local src="$1" src_part="$2" dst="$3" dst_part="$4"
+  local size1="$(image_part_size "$src" "$src_part")"
+  local size2="$(image_part_size "$dst" "$dst_part")"
+  if [ "$size1" -gt "$size2" ]; then
+    die "Destination is too small: ($size1 > $size2)"
   fi
+  image_dump_partition "$src" "$src_part" 2>/dev/null |
+    image_update_partition "$dst" "$dst_part"
+}
 
-  image_dump_partition "${src}" "${srcpart}" |
-      dd of="${dst}" bs="${bs}" seek="${dstoffset}" conv=notrunc oflag=dsync
+# Copy a partition image from file to a full disk image.
+image_partition_copy_from_file() {
+  local src="$1" dst="$2" dst_part="$3"
+  local size1="$(($(stat -c"%s" "$src") / 512))"
+  local size2="$(image_part_size "$dst" "$dst_part")"
+  if [ "$size1" != "$size2" ]; then
+    die "Partition size different: ($size1 != $size2)"
+  fi
+  image_update_partition "$dst" "$dst_part" <"$src"
+}
+
+# Temporary object management
+_IMAGE_TEMP_OBJECTS=""
+
+# Add a temporary object (by mktemp) into list for image_clean_temp to clean
+image_add_temp() {
+  _IMAGE_TEMP_OBJECTS="$_IMAGE_TEMP_OBJECTS $*"
+}
+
+# Cleans objects tracked by image_add_temp.
+image_clean_temp() {
+  local temp_list="$_IMAGE_TEMP_OBJECTS"
+  local object
+  _IMAGE_TEMP_OBJECTS=""
+
+  for object in $temp_list; do
+    if [ -d "$object" ]; then
+      sudo umount -d "$object" >/dev/null 2>&1 || true
+      sudo rmdir "$object" >/dev/null 2>&1 || true
+    else
+      rm -f "$object" >/dev/null 2>&1 || true
+    fi
+  done
+}
+
+# Determines the boot type of a ChromeOS kernel partition.
+# Prints "recovery", "ssd", "usb", "factory_install", "invalid", or "unknown".
+image_cros_kernel_boot_type() {
+  local keyblock="$1"
+  local magic flag skip kernel_config
+
+  # TODO(hungte) use vbutil_keyblock if available
+
+  # Reference: firmware/include/vboot_struct.h
+  local KEY_BLOCK_FLAG_DEVELOPER_0=1  # Developer switch off
+  local KEY_BLOCK_FLAG_DEVELOPER_1=2  # Developer switch on
+  local KEY_BLOCK_FLAG_RECOVERY_0=4  # Not recovery mode
+  local KEY_BLOCK_FLAG_RECOVERY_1=8  # Recovery mode
+  local KEY_BLOCK_MAGIC="CHROMEOS"
+  local KEY_BLOCK_MAGIC_SIZE=8
+  local KEY_BLOCK_FLAG_OFFSET=72  # magic:8 major:4 minor:4 size:8 2*(sig:8*3)
+
+  magic="$(dd if="$keyblock" bs=$KEY_BLOCK_MAGIC_SIZE count=1 2>/dev/null)"
+  if [ "$magic" != "$KEY_BLOCK_MAGIC" ]; then
+    echo "invalid"
+    return
+  fi
+  skip="$KEY_BLOCK_FLAG_OFFSET"
+  flag="$(dd if="$keyblock" bs=1 count=1 skip="$skip" 2>/dev/null |
+          od -t u1 -A n)"
+  if [ "$((flag & KEY_BLOCK_FLAG_RECOVERY_0))" != 0 ]; then
+    echo "ssd"
+  elif [ "$((flag & KEY_BLOCK_FLAG_RECOVERY_1))" != 0 ]; then
+    if [ "$((flag & KEY_BLOCK_FLAG_DEVELOPER_0))" = 0 ]; then
+      echo "factory_install"
+    else
+      # Recovery or USB. Check "cros_recovery" in kernel config.
+      if image_has_command dump_kernel_config; then
+        kernel_config="$(dump_kernel_config "$keyblock")"
+      else
+        # strings is less secure than dump_kernel_config, so let's try more
+        # keywords
+        kernel_config="$(strings "$keyblock" |
+                         grep -w "root=" | grep -w "cros_recovery")"
+      fi
+      if (echo "$kernel_config" | grep -qw "cros_recovery") &&
+         (echo "$kernel_config" | grep -qw "kern_b_hash"); then
+        echo "recovery"
+      else
+        echo "usb"
+      fi
+    fi
+  else
+    echo "unknown"
+  fi
 }
diff --git a/make_factory_package.sh b/make_factory_package.sh
index 04366d7..0fbfa03 100755
--- a/make_factory_package.sh
+++ b/make_factory_package.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2011 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.
 
@@ -10,100 +10,254 @@
 #
 # miniomaha lives in src/platform/dev/ and miniomaha partition sets live
 # in src/platform/dev/static.
+#
+# All internal environment variables used by this script are prefixed with
+# "MFP_".  Please avoid using them for other purposes.
+# "MFP_CONFIG_"* are shell variables that can be used in config file (--config)
 
-# --- BEGIN COMMON.SH BOILERPLATE ---
-# Load common CrOS utilities.  Inside the chroot this file is installed in
-# /usr/lib/crosutils.  Outside the chroot we find it relative to the script's
-# location.
-find_common_sh() {
-  local common_paths=(/usr/lib/crosutils $(dirname "$(readlink -f "$0")"))
-  local path
+# --- BEGIN FACTORY SCRIPTS BOILERPLATE ---
+# This script may be executed in a full CrOS source tree or an extracted factory
+# bundle with limited tools, so we must always load scripts from $SCRIPT_ROOT
+# and search for binary programs in $SCRIPT_ROOT/../bin
 
-  SCRIPT_ROOT=
-  for path in "${common_paths[@]}"; do
-    if [ -r "${path}/common.sh" ]; then
-      SCRIPT_ROOT=${path}
-      break
-    fi
-  done
-}
+SCRIPT="$(readlink -f "$0")"
+SCRIPT_ROOT="$(dirname "$SCRIPT")"
+. "$SCRIPT_ROOT/lib/cros_image_common.sh" || exit 1
+image_find_tool "cgpt" "$SCRIPT_ROOT/../bin"
+# --- END FACTORY SCRIPTS BOILERPLATE ---
 
-find_common_sh
-. "${SCRIPT_ROOT}/common.sh" || (echo "Unable to load common.sh" && exit 1)
-# --- END COMMON.SH BOILERPLATE ---
-
-# Load functions and constants for chromeos-install
-# NOTE: This script needs to be called from outside the chroot.
-. "/usr/lib/installer/chromeos-common.sh" &> /dev/null || \
-. "${SRC_ROOT}/platform/installer/chromeos-common.sh" || \
-  die "Unable to load /usr/lib/installer/chromeos-common.sh"
-
-# Load functions designed for image processing
-. "${SCRIPT_ROOT}/lib/cros_image_common.sh" ||
-  die "Cannot load required library: lib/cros_image_common.sh; Abort."
+if [ -f "$SCRIPT_ROOT/../dev/devserver.py" ]; then
+  # Running within an extracted factory bundle
+  GCLIENT_ROOT="$(readlink -f "$SCRIPT_ROOT/..")"
+fi
+. "$SCRIPT_ROOT/common.sh" || exit 1
+. "$SCRIPT_ROOT/chromeos-common.sh" || exit 1
 
 get_default_board
+FLAGS_NONE='none'
 
 # Flags
 DEFINE_string board "${DEFAULT_BOARD}" "Board for which the image was built"
 DEFINE_string factory "" \
   "Directory and file containing factory image: /path/chromiumos_test_image.bin"
 DEFINE_string firmware_updater "" \
-  "If set, include the firmware shellball into the server configuration"
+  "Firmware updater (shellball) into the server configuration,"\
+" or '$FLAGS_NONE' to prevent running firmware updater."
+DEFINE_string hwid_updater "" \
+  "The component list updater for HWID validation,"\
+" or '$FLAGS_NONE' to prevent updating the component list files."
+DEFINE_string complete_script "" \
+  "If set, include the script for the last-step execution of factory install"
 DEFINE_string release "" \
   "Directory and file containing release image: /path/chromiumos_image.bin"
 DEFINE_string subfolder "" \
   "If set, the name of the subfolder to put the payload items inside"
+DEFINE_string usbimg "" \
+  "If set, the name of the USB installation disk image file to output"
+DEFINE_string install_shim "" \
+  "Directory and file containing factory install shim for --usbimg"
 DEFINE_string diskimg "" \
   "If set, the name of the diskimage file to output"
 DEFINE_boolean preserve ${FLAGS_FALSE} \
   "If set, reuse the diskimage file, if available"
 DEFINE_integer sectors 31277232  "Size of image in sectors"
+DEFINE_boolean detect_release_image ${FLAGS_TRUE} \
+  "If set, try to auto-detect the type of release image and convert if required"
+DEFINE_string config "" \
+  'Configuration file where parameters are read from.  You can use '\
+'\$MFP_CONFIG_PATH and \$MFP_CONFIG_DIR (path and directory to the '\
+'config file itself) in config file to use relative path'
+
+# Usage Help
+FLAGS_HELP="Prepares factory resources (mini-omaha server, RMA/usb/disk images)
+
+USAGE: $0 [flags] args
+Note environment variables with prefix MFP_ are for reserved for internal use.
+"
 
 # Parse command line
 FLAGS "$@" || exit 1
+ORIGINAL_PARAMS="$@"
 eval set -- "${FLAGS_ARGV}"
 
-if [ ! -f "${FLAGS_release}" ]; then
-  echo "Cannot find image file ${FLAGS_release}"
-  exit 1
-fi
+on_exit() {
+  image_clean_temp
+}
 
-if [ ! -f "${FLAGS_factory}" ]; then
-  echo "Cannot find image file ${FLAGS_factory}"
-  exit 1
-fi
+# Param checking and validation
+check_file_param() {
+  local param="$1"
+  local msg="$2"
+  local param_name="${param#FLAGS_}"
+  local param_value="$(eval echo \$$1)"
 
-if [ -n "${FLAGS_firmware_updater}" ] &&
-   [ ! -f "${FLAGS_firmware_updater}" ]; then
-  echo "Cannot find firmware file ${FLAGS_firmware_updater}"
-  exit 1
-fi
+  [ -n "$param_value" ] ||
+    die "You must assign a file for --$param_name $msg"
+  [ -f "$param_value" ] ||
+    die "Cannot find file: $param_value"
+}
 
-# Convert args to paths.  Need eval to un-quote the string so that shell
-# chars like ~ are processed; just doing FOO=`readlink -f ${FOO}` won't work.
-OMAHA_DIR="${SRC_ROOT}/platform/dev"
-OMAHA_CONF="${OMAHA_DIR}/miniomaha.conf"
-OMAHA_DATA_DIR="${OMAHA_DIR}/static/"
+check_file_param_or_none() {
+  local param="$1"
+  local msg="$2"
+  local param_name="${param#FLAGS_}"
+  local param_value="$(eval echo \$$1)"
 
-# Note: The subfolder flag can only append configs.  That means you will need
-# to have unique board IDs for every time you run.  If you delete miniomaha.conf
-# you can still use this flag and it will start fresh.
-if [ -n "${FLAGS_subfolder}" ]; then
-  OMAHA_DATA_DIR="${OMAHA_DIR}/static/${FLAGS_subfolder}/"
-fi
+  if [ "$param_value" = "$FLAGS_NONE" ]; then
+    eval "$param=''"
+    return
+  fi
+  [ -n "$param_value" ] ||
+    die "You must assign either a file or 'none' for --$param_name $msg"
+  [ -f "$param_value" ] ||
+    die "Cannot find file: $param_value"
+}
 
-if [ ${INSIDE_CHROOT} -eq 0 ]; then
-  echo "Caching sudo authentication"
-  sudo -v
-  echo "Done"
-fi
+check_optional_file_param() {
+  local param="$1"
+  local msg="$2"
+  local param_name="${param#FLAGS_}"
+  local param_value="$(eval echo \$$1)"
 
-# Use this image as the source image to copy
-RELEASE_DIR="$(dirname "${FLAGS_release}")"
-FACTORY_DIR="$(dirname "${FLAGS_factory}")"
-RELEASE_IMAGE="$(basename "${FLAGS_release}")"
-FACTORY_IMAGE="$(basename "${FLAGS_factory}")"
+  if [ -n "$param_value" ] && [ ! -f "$param_value" ]; then
+    die "Cannot find file: $param_value"
+  fi
+}
+
+check_empty_param() {
+  local param="$1"
+  local msg="$2"
+  local param_name="${param#FLAGS_}"
+  local param_value="$(eval echo \$$1)"
+
+  [ -z "$param_value" ] || die "Parameter --$param_name is not supported $msg"
+}
+
+check_parameters() {
+  check_file_param FLAGS_release ""
+  check_file_param FLAGS_factory ""
+
+  # All remaining parameters must be checked:
+  # install_shim, firmware, hwid_updater, complete_script.
+
+  if [ -n "${FLAGS_usbimg}" ]; then
+    [ -z "${FLAGS_diskimg}" ] ||
+      die "--usbimg and --diskimg cannot be used at the same time."
+    check_file_param_or_none FLAGS_firmware_updater "in --usbimg mode"
+    check_file_param_or_none FLAGS_hwid_updater "in --usbimg mode"
+    check_empty_param FLAGS_complete_script "in --usbimg mode"
+    check_file_param FLAGS_install_shim "in --usbimg mode"
+  elif [ -n "${FLAGS_diskimg}" ]; then
+    check_empty_param FLAGS_firmware_updater "in --diskimg mode"
+    check_file_param_or_none FLAGS_hwid_updater "in --diskimg mode"
+    check_empty_param FLAGS_complete_script "in --diskimg mode"
+    check_empty_param FLAGS_install_shim "in --diskimg mode"
+    if [ -b "${FLAGS_diskimg}" -a ! -w "${FLAGS_diskimg}" ] &&
+       [ -z "$MFP_SUDO" -a "$(id -u)" != "0" ]; then
+      # Restart the command with original parameters with sudo for writing to
+      # block device that needs root permission.
+      # MFP_SUDO is a internal flag to prevent unexpected recursion.
+      MFP_SUDO=TRUE exec sudo "$0" $ORIGINAL_PARAMS
+    fi
+  else
+    check_file_param_or_none FLAGS_firmware_updater "in mini-omaha mode"
+    check_file_param_or_none FLAGS_hwid_updater "in mini-omaha mode"
+    check_optional_file_param FLAGS_complete_script "in mini-omaha mode"
+    check_empty_param FLAGS_install_shim "in mini-omaha mode"
+  fi
+}
+
+setup_environment() {
+  # Convert args to paths.  Need eval to un-quote the string so that shell
+  # chars like ~ are processed; just doing FOO=`readlink -f ${FOO}` won't work.
+  OMAHA_DIR="${SRC_ROOT}/platform/dev"
+  OMAHA_CONF="${OMAHA_DIR}/miniomaha.conf"
+  OMAHA_DATA_DIR="${OMAHA_DIR}/static/"
+
+  # Note: The subfolder flag can only append configs.  That means you will need
+  # to have unique board IDs for every time you run.  If you delete
+  # miniomaha.conf you can still use this flag and it will start fresh.
+  if [ -n "${FLAGS_subfolder}" ]; then
+    OMAHA_DATA_DIR="${OMAHA_DIR}/static/${FLAGS_subfolder}/"
+  fi
+
+  # When "sudo -v" is executed inside chroot, it prompts for password; however
+  # the user account inside chroot may be using a different password (ex,
+  # "chronos") from the same account outside chroot.  The /etc/sudoers file
+  # inside chroot has explicitly specified "userid ALL=NOPASSWD: ALL" for the
+  # account, so we should do nothing inside chroot.
+  if [ ${INSIDE_CHROOT} -eq 0 ]; then
+    echo "Caching sudo authentication"
+    sudo -v
+    echo "Done"
+  fi
+
+  # Use this image as the source image to copy
+  RELEASE_DIR="$(dirname "${FLAGS_release}")"
+  FACTORY_DIR="$(dirname "${FLAGS_factory}")"
+  RELEASE_IMAGE="$(basename "${FLAGS_release}")"
+  FACTORY_IMAGE="$(basename "${FLAGS_factory}")"
+
+  # Override this with path to modified kernel (for non-SSD images)
+  RELEASE_KERNEL=""
+
+  # Check required tools.
+  if ! image_has_part_tools; then
+    die "Missing partition tools. Please install cgpt/parted, or run in chroot."
+  fi
+}
+
+# Prepares release image source by checking image type, and creates modified
+# partition blob in RELEASE_KERNEL if required.
+prepare_release_image() {
+  local image="$(readlink -f "$1")"
+  local kernel="$(mktemp --tmpdir)"
+  image_add_temp "$kernel"
+
+  # Image Types:
+  # - recovery: kernel in #4 and vmlinuz_hd.vblock in #1
+  # - usb: kernel in #2 and vmlinuz_hd.vblock in #1
+  # - ssd: kernel in #2, no need to change
+  image_dump_partition "$image" "2" >"$kernel" 2>/dev/null ||
+    die "Cannot extract kernel partition from image: $image"
+
+  local image_type="$(image_cros_kernel_boot_type "$kernel")"
+  local need_vmlinuz_hd=""
+  info "Image type is [$image_type]: $image"
+
+  case "$image_type" in
+    "ssd" )
+      true
+      ;;
+    "usb" )
+      RELEASE_KERNEL="$kernel"
+      need_vmlinuz_hd="TRUE"
+      ;;
+    "recovery" )
+      RELEASE_KERNEL="$kernel"
+      image_dump_partition "$image" "4" >"$kernel" 2>/dev/null ||
+        die "Cannot extract real kernel for recovery image: $image"
+      need_vmlinuz_hd="TRUE"
+      ;;
+    * )
+      die "Unexpected release image type: $image_type."
+      ;;
+  esac
+
+  if [ -n "$need_vmlinuz_hd" ]; then
+    local temp_mount="$(mktemp -d --tmpdir)"
+    local vmlinuz_hd_file="vmlinuz_hd.vblock"
+    image_add_temp "$temp_mount"
+    image_mount_partition "$image" "1" "$temp_mount" "ro" ||
+      die "No stateful partition in $image."
+    [ -s "$temp_mount/$vmlinuz_hd_file" ] ||
+      die "Missing $vmlinuz_hd_file in stateful partition: $image"
+    sudo dd if="$temp_mount/$vmlinuz_hd_file" of="$kernel" \
+      bs=512 conv=notrunc >/dev/null 2>&1 ||
+      die "Cannot update kernel with $vmlinuz_hd_file"
+    image_umount_partition "$temp_mount"
+  fi
+}
 
 prepare_img() {
   local outdev="$(readlink -f "$FLAGS_diskimg")"
@@ -112,7 +266,8 @@
 
   # We'll need some code to put in the PMBR, for booting on legacy BIOS.
   echo "Fetch PMBR"
-  local pmbrcode="$(mktemp -d)/gptmbr.bin"
+  local pmbrcode="$(mktemp --tmpdir)"
+  image_add_temp "$pmbrcode"
   sudo dd bs=512 count=1 if="${FLAGS_release}" of="${pmbrcode}" status=noxfer
 
   echo "Prepare base disk image"
@@ -123,8 +278,8 @@
         "$(stat -c %s ${outdev})" != "$(( ${sectors} * 512 ))"  -o \
         "$FLAGS_preserve" = "$FLAGS_FALSE" ]; then
     echo "Generating empty image file"
-    image_dump_partial_file /dev/zero 0 "${sectors}" |
-        dd of="${outdev}" bs=8M
+    truncate -s "0" "$outdev"
+    truncate -s "$((sectors * 512))" "$outdev"
   else
     echo "Reusing $outdev"
   fi
@@ -136,37 +291,33 @@
   sudo "${GPT}" add -i 2 -S 1 -P 1 "${outdev}"
 }
 
-prepare_omaha() {
-  sudo rm -rf "${OMAHA_DATA_DIR}/rootfs-test.gz"
-  sudo rm -rf "${OMAHA_DATA_DIR}/rootfs-release.gz"
-  rm -rf "${OMAHA_DATA_DIR}/efi.gz"
-  rm -rf "${OMAHA_DATA_DIR}/oem.gz"
-  rm -rf "${OMAHA_DATA_DIR}/state.gz"
-  if [ ! -d "${OMAHA_DATA_DIR}" ]; then
-    mkdir -p "${OMAHA_DATA_DIR}"
-  fi
-}
-
 prepare_dir() {
-  sudo rm -rf rootfs-test.gz
-  sudo rm -rf rootfs-release.gz
-  rm -rf efi.gz
-  rm -rf oem.gz
-  rm -rf state.gz
+  local dir="$1"
+
+  # TODO(hungte) the three files were created as root by old mk_memento_images;
+  #  we can prevent the sudo in future.
+  sudo rm -f "${dir}/rootfs-test.gz"
+  sudo rm -f "${dir}/rootfs-release.gz"
+  sudo rm -f "${dir}/update.gz"
+  for filename in efi oem state hwid firmware; do
+    rm -f "${dir}/${filename}.gz"
+  done
+  if [ ! -d "${dir}" ]; then
+    mkdir -p "${dir}"
+  fi
 }
 
+# Compresses kernel and rootfs of an imge file, and output its hash.
+# Usage:compress_and_hash_memento_image kernel rootfs
+# Please see "mk_memento_images --help" for detail of parameter syntax
 compress_and_hash_memento_image() {
-  local input_file="$1"
+  local kern="$1"
+  local rootfs="$2"
+  [ "$#" = "2" ] || die "Internal error: compress_and_hash_memento_image $*"
 
-  if [ -n "${IMAGE_IS_UNPACKED}" ]; then
-    sudo "${SCRIPTS_DIR}/mk_memento_images.sh" part_2 part_3 |
-      grep hash |
-      awk '{print $4}'
-  else
-    sudo "${SCRIPTS_DIR}/mk_memento_images.sh" "$input_file" 2 3 |
-      grep hash |
-      awk '{print $4}'
-  fi
+  "${SCRIPTS_DIR}/mk_memento_images.sh" "$kern" "$rootfs" "." |
+    grep hash |
+    awk '{print $4}'
 }
 
 compress_and_hash_file() {
@@ -192,78 +343,131 @@
   local part_num="$2"
   local output_file="$3"
 
-  if [ -n "${IMAGE_IS_UNPACKED}" ]; then
-    compress_and_hash_file "part_$part_num" "$output_file"
-  else
-    image_dump_partition "$input_file" "$part_num" |
+  image_dump_partition "$input_file" "$part_num" |
     compress_and_hash_file "" "$output_file"
+}
+
+# Applies HWID component list files updater into stateful partition
+apply_hwid_updater() {
+  local hwid_updater="$1"
+  local outdev="$2"
+  local hwid_result="0"
+
+  if [ -n "$hwid_updater" ]; then
+    local state_dev="$(image_map_partition "${outdev}" 1)"
+    sudo sh "$hwid_updater" "$state_dev" || hwid_result="$?"
+    image_unmap_partition "$state_dev" || true
+    [ $hwid_result = "0" ] || die "Failed to update HWID ($hwid_result). abort."
   fi
 }
 
-# Decide if we should unpack partition
-if image_has_part_tools; then
-  IMAGE_IS_UNPACKED=
-else
-  #TODO(hungte) Currently we run unpack_partitions.sh if part_tools are not
-  # found. If the format of unpack_partitions.sh is reliable, we can prevent
-  # creating temporary files. See image_part_offset for more information.
-  echo "WARNING: cannot find partition tools. Using unpack_partitions.sh." >&2
-  IMAGE_IS_UNPACKED=1
-fi
-
-mount_esp() {
-  local image="$1"
-  local esp_mountpoint="$2"
-  offset=$(partoffset "${image}" 12)
-  sudo mount -o loop,offset=$(( offset * 512 )) \
-      "${image}" "${esp_mountpoint}"
-  ESP_MOUNT="${esp_mountpoint}"
-}
-
-umount_esp() {
-  if [ -n "${ESP_MOUNT}" ]; then
-    sudo umount "${ESP_MOUNT}"
+generate_usbimg() {
+  if ! type cgpt >/dev/null 2>&1; then
+    die "Missing 'cgpt'. Please install cgpt, or run inside chroot."
   fi
+  local builder="$(dirname "$SCRIPT")/make_universal_factory_shim.sh"
+  local release_file="$FLAGS_release"
+
+  if [ -n "$RELEASE_KERNEL" ]; then
+    # TODO(hungte) Improve make_universal_factory_shim to support assigning
+    # a modified kernel to prevent creating temporary image here
+    info "Creating temporary SSD-type release image, please wait..."
+    release_file="$(mktemp --tmpdir)"
+    image_add_temp "${release_file}"
+    if image_has_part_tools pv; then
+      pv -B 16M "${FLAGS_release}" >"${release_file}"
+    else
+      cp -f "${FLAGS_release}" "${release_file}"
+    fi
+    image_partition_copy_from_file "${RELEASE_KERNEL}" "${release_file}" 2
+  fi
+
+  "$builder" -m "${FLAGS_factory}" -f "${FLAGS_usbimg}" \
+    "${FLAGS_install_shim}" "${FLAGS_factory}" "${release_file}"
+  apply_hwid_updater "${FLAGS_hwid_updater}" "${FLAGS_usbimg}"
+
+  # Extract and modify lsb-factory from original install shim
+  local lsb_path="/dev_image/etc/lsb-factory"
+  local src_dir="$(mktemp -d --tmpdir)"
+  local src_lsb="${src_dir}${lsb_path}"
+  local new_dir="$(mktemp -d --tmpdir)"
+  local new_lsb="${new_dir}${lsb_path}"
+  image_add_temp "$src_dir" "$new_dir"
+  image_mount_partition "${FLAGS_install_shim}" 1 "${src_dir}" ""
+  image_mount_partition "${FLAGS_usbimg}" 1 "${new_dir}" "rw"
+  # Copy firmware updater, if available
+  local updater_settings=""
+  if [ -n "${FLAGS_firmware_updater}" ]; then
+    local updater_new_path="${new_dir}/chromeos-firmwareupdate"
+    sudo cp -f "${FLAGS_firmware_updater}" "${updater_new_path}"
+    sudo chmod a+rx "${updater_new_path}"
+    updater_settings="FACTORY_INSTALL_FIRMWARE=/mnt/stateful_partition"
+    updater_settings="$updater_settings/$(basename $updater_new_path)"
+  fi
+  # We put the install shim kernel and rootfs into partition #2 and #3, so
+  # the factory and release image partitions must be moved to +2 location.
+  # USB_OFFSET=2 tells factory_installer/factory_install.sh this information.
+  (cat "$src_lsb" &&
+    echo "FACTORY_INSTALL_FROM_USB=1" &&
+    echo "FACTORY_INSTALL_USB_OFFSET=2" &&
+    echo "$updater_settings") |
+    sudo dd of="${new_lsb}"
+  image_umount_partition "$new_dir"
+  image_umount_partition "$src_dir"
+
+  # Deactivate all kernel partitions except installer slot
+  local i=""
+  for i in 4 5 6 7; do
+    cgpt add -P 0 -T 0 -S 0 -t data -i "$i" "${FLAGS_usbimg}"
+  done
+
+  info "Generated Image at ${FLAGS_usbimg}."
+  info "Done"
 }
 
 generate_img() {
   local outdev="$(readlink -f "$FLAGS_diskimg")"
   local sectors="$FLAGS_sectors"
+  local hwid_updater="${FLAGS_hwid_updater}"
+
+  if [ -n "${FLAGS_hwid_updater}" ]; then
+    hwid_updater="$(readlink -f "$FLAGS_hwid_updater")"
+  fi
 
   prepare_img
 
   # Get the release image.
-  pushd "${RELEASE_DIR}" >/dev/null
-
+  local release_image="${RELEASE_DIR}/${RELEASE_IMAGE}"
   echo "Release Kernel"
-  image_partition_copy "${RELEASE_IMAGE}" 2 "${outdev}" 4
+  if [ -n "$RELEASE_KERNEL" ]; then
+    image_partition_copy_from_file "${RELEASE_KERNEL}" "${outdev}" 4
+  else
+    image_partition_copy "${release_image}" 2 "${outdev}" 4
+  fi
   echo "Release Rootfs"
-  image_partition_copy "${RELEASE_IMAGE}" 3 "${outdev}" 5
+  image_partition_overwrite "${release_image}" 3 "${outdev}" 5
   echo "OEM parition"
-  image_partition_copy "${RELEASE_IMAGE}" 8 "${outdev}" 8
-
-  popd >/dev/null
+  image_partition_overwrite "${release_image}" 8 "${outdev}" 8
 
   # Go to retrieve the factory test image.
-  pushd "${FACTORY_DIR}" >/dev/null
-
+  local factory_image="${FACTORY_DIR}/${FACTORY_IMAGE}"
   echo "Factory Kernel"
-  image_partition_copy "${FACTORY_IMAGE}" 2 "${outdev}" 2
+  image_partition_copy "${factory_image}" 2 "${outdev}" 2
   echo "Factory Rootfs"
-  image_partition_copy "${FACTORY_IMAGE}" 3 "${outdev}" 3
+  image_partition_overwrite "${factory_image}" 3 "${outdev}" 3
   echo "Factory Stateful"
-  image_partition_copy "${FACTORY_IMAGE}" 1 "${outdev}" 1
+  image_partition_overwrite "${factory_image}" 1 "${outdev}" 1
   echo "EFI Partition"
-  image_partition_copy "${FACTORY_IMAGE}" 12 "${outdev}" 12
+  image_partition_copy "${factory_image}" 12 "${outdev}" 12
+  apply_hwid_updater "${hwid_updater}" "${outdev}"
 
   # TODO(nsanders, wad): consolidate this code into some common code
   # when cleaning up kernel commandlines. There is code that touches
   # this in postint/chromeos-setimage and build_image. However none
   # of the preexisting code actually does what we want here.
-  local tmpesp="$(mktemp -d)"
-  mount_esp "${outdev}" "${tmpesp}"
-
-  trap "umount_esp" EXIT
+  local tmpesp="$(mktemp -d --tmpdir)"
+  image_add_temp "$tmpesp"
+  image_mount_partition "${outdev}" 12 "$tmpesp" "rw"
 
   # Edit boot device default for legacy.
   # Support both vboot and regular boot.
@@ -276,36 +480,31 @@
   # Somewhat safe as ARM does not support syslinux, I believe.
   sudo sed -i "s'HDROOTA'/dev/sda3'g" "${tmpesp}"/syslinux/root.A.cfg
 
-  trap - EXIT
-
-  umount_esp
-
+  image_umount_partition "$tmpesp"
   echo "Generated Image at $outdev."
   echo "Done"
 }
 
 generate_omaha() {
+  local kernel rootfs
+  [ -n "$FLAGS_board" ] || die "Need --board parameter for mini-omaha server."
   # Clean up stale config and data files.
-  prepare_omaha
+  prepare_dir "${OMAHA_DATA_DIR}"
 
-  # Get the release image.
-  pushd "${RELEASE_DIR}" >/dev/null
   echo "Generating omaha release image from ${FLAGS_release}"
   echo "Generating omaha factory image from ${FLAGS_factory}"
   echo "Output omaha image to ${OMAHA_DATA_DIR}"
   echo "Output omaha config to ${OMAHA_CONF}"
 
-  prepare_dir
+  # Get the release image.
+  # TODO(hungte) deprecate pushd and use temporary folders
+  pushd "${RELEASE_DIR}" >/dev/null
+  prepare_dir "."
 
-  if [ -n "${IMAGE_IS_UNPACKED}" ]; then
-    echo "Unpacking image ${RELEASE_IMAGE} ..." >&2
-    sudo ./unpack_partitions.sh "${RELEASE_IMAGE}" 2>/dev/null
-  fi
-
-  release_hash="$(compress_and_hash_memento_image "${RELEASE_IMAGE}")"
-  sudo chmod a+rw update.gz
-  mv update.gz rootfs-release.gz
-  mv rootfs-release.gz "${OMAHA_DATA_DIR}"
+  kernel="${RELEASE_KERNEL:-${RELEASE_IMAGE}:2}"
+  rootfs="${RELEASE_IMAGE}:3"
+  release_hash="$(compress_and_hash_memento_image "$kernel" "$rootfs")"
+  mv ./update.gz "${OMAHA_DATA_DIR}/rootfs-release.gz"
   echo "release: ${release_hash}"
 
   oem_hash="$(compress_and_hash_partition "${RELEASE_IMAGE}" 8 "oem.gz")"
@@ -316,17 +515,12 @@
 
   # Go to retrieve the factory test image.
   pushd "${FACTORY_DIR}" >/dev/null
-  prepare_dir
+  prepare_dir "."
 
-  if [ -n "${IMAGE_IS_UNPACKED}" ]; then
-    echo "Unpacking image ${FACTORY_IMAGE} ..." >&2
-    sudo ./unpack_partitions.sh "${FACTORY_IMAGE}" 2>/dev/null
-  fi
-
-  test_hash="$(compress_and_hash_memento_image "${FACTORY_IMAGE}")"
-  sudo chmod a+rw update.gz
-  mv update.gz rootfs-test.gz
-  mv rootfs-test.gz "${OMAHA_DATA_DIR}"
+  kernel="${FACTORY_IMAGE}:2"
+  rootfs="${FACTORY_IMAGE}:3"
+  test_hash="$(compress_and_hash_memento_image "$kernel" "$rootfs")"
+  mv ./update.gz "${OMAHA_DATA_DIR}/rootfs-test.gz"
   echo "test: ${test_hash}"
 
   state_hash="$(compress_and_hash_partition "${FACTORY_IMAGE}" 1 "state.gz")"
@@ -340,17 +534,25 @@
   popd >/dev/null
 
   if [ -n "${FLAGS_firmware_updater}" ]; then
-    SHELLBALL="${FLAGS_firmware_updater}"
-    if [ ! -f  "$SHELLBALL" ]; then
-      echo "Failed to find firmware updater: $SHELLBALL."
-      exit 1
-    fi
-
-    firmware_hash="$(compress_and_hash_file "$SHELLBALL" "firmware.gz")"
+    firmware_hash="$(compress_and_hash_file "${FLAGS_firmware_updater}" \
+                     "firmware.gz")"
     mv firmware.gz "${OMAHA_DATA_DIR}"
     echo "firmware: ${firmware_hash}"
   fi
 
+  if [ -n "${FLAGS_hwid_updater}" ]; then
+    hwid_hash="$(compress_and_hash_file "${FLAGS_hwid_updater}" "hwid.gz")"
+    mv hwid.gz "${OMAHA_DATA_DIR}"
+    echo "hwid: ${hwid_hash}"
+  fi
+
+  if [ -n "${FLAGS_complete_script}" ]; then
+    complete_hash="$(compress_and_hash_file "${FLAGS_complete_script}" \
+                     "complete.gz")"
+    mv complete.gz "${OMAHA_DATA_DIR}"
+    echo "complete: ${complete_hash}"
+  fi
+
   # If the file does exist and we are using the subfolder flag we are going to
   # append another config.
   if [ -n "${FLAGS_subfolder}" ] &&
@@ -404,12 +606,24 @@
    'firmware_checksum': '${firmware_hash}'," >>"${OMAHA_CONF}"
   fi
 
+  if [ -n "${FLAGS_hwid_updater}" ]  ; then
+    echo -n "
+   'hwid_image': '${subfolder}hwid.gz',
+   'hwid_checksum': '${hwid_hash}'," >>"${OMAHA_CONF}"
+  fi
+
+  if [ -n "${FLAGS_complete_script}" ]  ; then
+    echo -n "
+   'complete_image': '${subfolder}complete.gz',
+   'complete_checksum': '${complete_hash}'," >>"${OMAHA_CONF}"
+  fi
+
   echo -n "
  },
 ]
 " >>"${OMAHA_CONF}"
 
-  echo "The miniomaha server lives in src/platform/dev.
+  info "The miniomaha server lives in src/platform/dev.
 To validate the configutarion, run:
   python2.6 devserver.py --factory_config miniomaha.conf \
   --validate_factory_config
@@ -417,9 +631,94 @@
   python2.6 devserver.py --factory_config miniomaha.conf"
 }
 
-# Main
-if [ -n "$FLAGS_diskimg" ]; then
-  generate_img
-else
-  generate_omaha
-fi
+parse_and_run_config() {
+  # This function parses parameters from config file. Parameters can be put
+  # in sections and sections of parameters will be run in turn.
+  #
+  # Config file format:
+  # [section1]
+  #  --param value
+  #  --another_param  # comment
+  #
+  # # some more comment
+  # [section2]
+  #  --yet_another_param
+  #
+  # Note that a section header must start at the beginning of a line.
+  # And it's not allowed to read from config file recursively.
+
+  local config_file="$1"
+  local -a cmds
+  local cmd=""
+  local line
+
+  echo "Read parameters from: $config_file"
+  while read line; do
+    if [[ "$line" =~ ^\[.*] ]]; then
+      if [ -n "$cmd" ]; then
+        cmds+=("$cmd")
+        cmd=""
+      fi
+      continue
+    fi
+    line="${line%%#*}"
+    cmd="$cmd $line"
+  done < "$config_file"
+  if [ -n "$cmd" ]; then
+    cmds+=("$cmd")
+  fi
+
+  for cmd in "${cmds[@]}"
+  do
+    info "Executing: $0 $cmd"
+    # Sets internal environment variable MFP_SUBPROCESS to prevent unexpected
+    # recursion.
+    eval "MFP_SUBPROCESS=1 $0 $cmd"
+  done
+}
+
+main() {
+  set -e
+  trap on_exit EXIT
+
+  if [ -n "$FLAGS_config" ]; then
+    [ -z "$MFP_SUBPROCESS" ] ||
+      die "Recursively reading from config file is not allowed"
+
+    check_file_param FLAGS_config ""
+    check_empty_param FLAGS_release "when using config file"
+    check_empty_param FLAGS_factory "when using config file"
+    check_empty_param FLAGS_firmware_updater "when using config file"
+    check_empty_param FLAGS_hwid_updater "when using config file"
+    check_empty_param FLAGS_install_shim "when using config file"
+    check_empty_param FLAGS_complete_script "when using config file"
+    check_empty_param FLAGS_usbimg "when using config file"
+    check_empty_param FLAGS_diskimg "when using config file"
+    check_empty_param FLAGS_subfolder "when using config file"
+
+    # Make the path and folder of config file available when parsing config.
+    # These MFP_CONFIG_* are special shell variables (not environment variables)
+    # that a config file (by --config) can use them.
+    MFP_CONFIG_PATH="$(readlink -f "$FLAGS_config")"
+    MFP_CONFIG_DIR="$(dirname "$MFP_CONFIG_PATH")"
+
+    parse_and_run_config "$FLAGS_config"
+    exit
+  fi
+
+  check_parameters
+  setup_environment
+  if [ "$FLAGS_detect_release_image" = "$FLAGS_TRUE" ]; then
+    prepare_release_image "$FLAGS_release"
+  fi
+
+  if [ -n "$FLAGS_usbimg" ]; then
+    generate_usbimg
+  elif [ -n "$FLAGS_diskimg" ]; then
+    generate_img
+  else
+    generate_omaha
+  fi
+}
+
+main "$@"
diff --git a/make_universal_factory_shim.sh b/make_universal_factory_shim.sh
new file mode 100755
index 0000000..9b03218
--- /dev/null
+++ b/make_universal_factory_shim.sh
@@ -0,0 +1,298 @@
+#!/bin/sh
+
+# Copyright (c) 2011 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.
+
+# Script to generate an universal factory install shim image, by merging
+# multiple images signed by different keys.
+# CAUTION: Recovery shim images are not supported yet because they require the
+# kernel partitions to be laid out in a special way
+
+# --- BEGIN FACTORY SCRIPTS BOILERPLATE ---
+# This script may be executed in a full CrOS source tree or an extracted factory
+# bundle with limited tools, so we must always load scripts from $SCRIPT_ROOT
+# and search for binary programs in $SCRIPT_ROOT/../bin
+
+SCRIPT="$(readlink -f "$0")"
+SCRIPT_ROOT="$(dirname "$SCRIPT")"
+. "$SCRIPT_ROOT/lib/cros_image_common.sh" || exit 1
+image_find_tool "cgpt" "$SCRIPT_ROOT/../bin"
+# --- END FACTORY SCRIPTS BOILERPLATE ---
+
+# CGPT Header: PMBR, header, table; sec_table, sec_header
+CGPT_START_SIZE=$((1 + 1 + 32))
+CGPT_END_SIZE=$((32 + 1))
+CGPT_BS="512"
+
+# Alignment of partition sectors
+PARTITION_SECTOR_ALIGNMENT=256
+
+LAYOUT_FILE="$(mktemp --tmpdir)"
+
+RESERVED_PARTITION="10"
+LEGACY_PARTITIONS="10 11 12"  # RESERVED, RWFW, EFI
+MAX_INPUT_SOURCES=4  # (2~9) / 2
+
+alert() {
+  echo "$*" >&2
+}
+
+die() {
+  alert "ERROR: $*"
+  exit 1
+}
+
+on_exit() {
+  rm -f "$LAYOUT_FILE"
+}
+
+# Returns offset aligned to alignment.
+# If size is given, only align if size >= alignment.
+image_alignment() {
+  local offset="$1"
+  local alignment="$2"
+  local size="$3"
+
+  # If size is assigned, align only if the new size is larger then alignment.
+  if [ "$((offset % alignment))" != "0" ]; then
+    if [ -z "$size" -o "$size" -ge "$alignment" ]; then
+      offset=$((offset + alignment - (offset % alignment)))
+    fi
+  fi
+  echo "$((offset))"
+}
+
+# Processes a logical disk image layout description file.
+# Each entry in layout is a "file:partnum" entry (:partnum is optional),
+# referring to the #partnum partition in file.
+# The index starts at one, referring to the first partition in layout.
+image_process_layout() {
+  local layout_file="$1"
+  local callback="$2"
+  shift
+  shift
+  local param="$@"
+  local index=0
+
+  while read layout; do
+    local image_file="${layout%:*}"
+    local part_num="${layout#*:}"
+    index="$((index + 1))"
+    [ "$image_file" != "$layout" ] || part_num=""
+
+    "$callback" "$image_file" "$part_num" "$index" "$param"
+  done <"$layout_file"
+}
+
+# Processes a list of disk geometry sectors into aligned (offset, sectors) form.
+# The index starts at zero, referring to the partition table object itself.
+image_process_geometry() {
+  local sectors_list="$1"
+  local callback="$2"
+  shift
+  shift
+  local param="$@"
+  local offset=0 sectors
+  local index=0
+
+  for sectors in $sectors_list; do
+    offset="$(image_alignment $offset $PARTITION_SECTOR_ALIGNMENT $sectors)"
+    "$callback" "$offset" "$sectors" "$index" "$param"
+    offset="$((offset + sectors))"
+    index="$((index + 1))"
+  done
+}
+
+# Callback of image_process_layout. Returns the size (in sectors) of given
+# object (partition in image or file).
+layout_get_sectors() {
+  local image_file="$1"
+  local part_num="$2"
+
+  if [ -n "$part_num" ]; then
+    image_part_size "$image_file" "$part_num"
+  else
+    image_alignment "$(stat -c"%s" "$image_file")" $CGPT_BS ""
+  fi
+}
+
+# Callback of image_process_layout. Copies an input source object (file or
+# partition) into specified partition on output file.
+layout_copy_partition() {
+  local input_file="$1"
+  local input_part="$2"
+  local output_part="$3"
+  local output_file="$4"
+  alert "$(basename "$input_file"):$input_part =>" \
+        "$(basename "$output_file"):$output_part"
+
+  if [ -n "$part_num" ]; then
+    # TODO(hungte) update partition type if available
+    image_partition_copy "$input_file" "$input_part" \
+                         "$output_file" "$output_part"
+    # Update partition type information
+    local partition_type="$(cgpt show -q -n -t -i "$input_part" "$input_file")"
+    local partition_attr="$(cgpt show -q -n -A -i "$input_part" "$input_file")"
+    local partition_label="$(cgpt show -q -n -l -i "$input_part" "$input_file")"
+    cgpt add -t "$partition_type" -l "$partition_label" -A "$partition_attr" \
+             -i "$output_part" "$output_file"
+  else
+    image_update_partition "$output_file" "$output_part" <"$input_file"
+  fi
+}
+
+
+# Callback of image_process_geometry. Creates a partition by give offset,
+# size(sectors), and index.
+geometry_create_partition() {
+  local offset="$1"
+  local sectors="$2"
+  local index="$3"
+  local output_file="$4"
+
+  if [ "$offset" = "0" ]; then
+    # first entry is CGPT; ignore.
+    return
+  fi
+  cgpt add -b $offset -s $sectors -i $index -t reserved "$output_file"
+}
+
+# Callback of image_process_geometry. Prints the proper offset of current
+# partition by give offset and size.
+geometry_get_partition_offset() {
+  local offset="$1"
+  local sectors="$2"
+  local index="$3"
+
+  image_alignment "$offset" "$PARTITION_SECTOR_ALIGNMENT" "$sectors"
+}
+
+build_image_file() {
+  local layout_file="$1"
+  local output_file="$2"
+  local output_file_size=0
+  local sectors_list partition_offsets
+
+  # Check and obtain size information from input sources
+  sectors_list="$(image_process_layout "$layout_file" layout_get_sectors)"
+
+  # Calculate output image file size
+  partition_offsets="$(image_process_geometry \
+                       "$CGPT_START_SIZE $sectors_list $CGPT_END_SIZE 1" \
+                       geometry_get_partition_offset)"
+  output_file_size="$(echo "$partition_offsets" | tail -n 1)"
+
+  # Create empty image file
+  truncate -s "0" "$output_file"  # starting with a new file is much faster.
+  truncate -s "$((output_file_size * CGPT_BS))" "$output_file"
+
+  # Initialize partition table (GPT)
+  cgpt create "$output_file"
+  cgpt boot -p "$output_file" >/dev/null
+
+  # Create partition tables
+  image_process_geometry "$CGPT_START_SIZE $sectors_list" \
+                         geometry_create_partition \
+                         "$output_file"
+  # Copy partitions content
+  image_process_layout "$layout_file" layout_copy_partition "$output_file"
+}
+
+# Creates standard multiple image layout
+create_standard_layout() {
+  local main_source="$1"
+  local layout_file="$2"
+  local image index
+  shift
+  shift
+
+  for image in "$main_source" "$@"; do
+    if [ ! -f "$image" ]; then
+      die "Cannot find input file $image."
+    fi
+  done
+
+  echo "$main_source:1" >>"$layout_file"  # stateful partition
+  for index in $(seq 1 $MAX_INPUT_SOURCES); do
+    local kernel_source="$main_source:$RESERVED_PARTITION"
+    local rootfs_source="$main_source:$RESERVED_PARTITION"
+    if [ "$#" -gt 0 ]; then
+      # TODO(hungte) detect if input source is a recovery/USB image
+      kernel_source="$1:2"
+      rootfs_source="$1:3"
+      shift
+    fi
+    echo "$kernel_source" >>"$layout_file"
+    echo "$rootfs_source" >>"$layout_file"
+  done
+  for index in $LEGACY_PARTITIONS; do
+    echo "$main_source:$index" >>"$LAYOUT_FILE"
+  done
+}
+
+usage_die() {
+  alert "Usage: $SCRIPT [-m master] [-f] output shim1 [shim2 ... shim4]"
+  alert "   or  $SCRIPT -l layout [-f] output"
+  exit 1
+}
+
+main() {
+  local force=""
+  local image=""
+  local output=""
+  local main_source=""
+  local index=""
+  local slots="0"
+  local layout_mode=""
+
+  while [ "$#" -gt 1 ]; do
+    case "$1" in
+      "-f" )
+        force="True"
+        shift
+        ;;
+      "-m" )
+        main_source="$2"
+        shift
+        shift
+        ;;
+      "-l" )
+        cat "$2" >"$LAYOUT_FILE"
+        layout_mode="TRUE"
+        shift
+        shift
+        ;;
+      * )
+        break
+    esac
+  done
+
+  if [ -n "$layout_mode" ]; then
+    [ "$#" = 1 ] || usage_die
+  elif [ "$#" -lt 2 -o "$#" -gt "$((MAX_INPUT_SOURCES + 1))" ]; then
+    alert "ERROR: invalid number of parameters ($#)."
+    usage_die
+  fi
+
+  if [ -z "$main_source" ]; then
+    main_source="$2"
+  fi
+  output="$1"
+  shift
+
+  if [ -f "$output" -a -z "$force" ]; then
+    die "Output file $output already exists. To overwrite the file, add -f."
+  fi
+
+  if [ -z "$layout_mode" ]; then
+    create_standard_layout "$main_source" "$LAYOUT_FILE" "$@"
+  fi
+  build_image_file "$LAYOUT_FILE" "$output"
+  echo ""
+  echo "Image created: $output"
+}
+
+set -e
+trap on_exit EXIT
+main "$@"
diff --git a/mk_memento_images.sh b/mk_memento_images.sh
index 9e8b609..4c76715 100755
--- a/mk_memento_images.sh
+++ b/mk_memento_images.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2011 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.
 
@@ -8,49 +8,37 @@
 # build_image.sh and generates an image that can be used for auto
 # update.
 
+# --- BEGIN FACTORY SCRIPTS BOILERPLATE ---
+# This script may be executed in a full CrOS source tree or an extracted factory
+# bundle with limited tools, so we must always load scripts from $SCRIPT_ROOT
+# and search for binary programs in $SCRIPT_ROOT/../bin
+
+SCRIPT="$(readlink -f "$0")"
+SCRIPT_ROOT="$(dirname "$SCRIPT")"
+. "$SCRIPT_ROOT/lib/cros_image_common.sh" || exit 1
+image_find_tool "cgpt" "$SCRIPT_ROOT/../bin"
+# --- END FACTORY SCRIPTS BOILERPLATE ---
+
 set -e
+# We need 2-3 non-zero parameters.
+if [ "$#" -lt 2 ] || [ "$#" -gt 3 ] || [ -z "$1" ] || [ -z "$2" ]; then
+  echo "
+Usage: $0 kernel_partition_img[:index] rootfs_partition_img[:index] [output_dir]
 
-# --- BEGIN COMMON.SH BOILERPLATE ---
-# Load common CrOS utilities.  Inside the chroot this file is installed in
-# /usr/lib/crosutils.  Outside the chroot we find it relative to the script's
-# location.
-find_common_sh() {
-  local common_paths=(/usr/lib/crosutils $(dirname "$(readlink -f "$0")"))
-  local path
+          Input parameters may be either a simple partition image file, or a
+          disk image file name followed by ':' and target partition index number
 
-  SCRIPT_ROOT=
-  for path in "${common_paths[@]}"; do
-    if [ -r "${path}/common.sh" ]; then
-      SCRIPT_ROOT=${path}
-      break
-    fi
-  done
-}
+          If output_dir is omitted, the folder of kernel_partition_img will be
+          use.
 
-find_common_sh
-. "${SCRIPT_ROOT}/common.sh" || (echo "Unable to load common.sh" && exit 1)
-# --- END COMMON.SH BOILERPLATE ---
-
-. "${SCRIPT_ROOT}/common.sh" || (echo "Unable to load common.sh" && exit 1)
-
-# Load functions designed for image processing
-if ! . "${SCRIPT_ROOT}/lib/cros_image_common.sh"; then
-  echo "ERROR: Cannot load required library: lib/cros_image_common.sh; Abort."
+Examples:
+       $0 part_2 part_3
+       $0 chromiumos_image.bin:2 part3
+       $0 chromiumos_image.bin:2 otherimage.bin:3 /tmp/myoutput
+  "
   exit 1
 fi
 
-if [ -z "$2" -o -z "$1" ] || [ "${#@}" -ne 2 -a "${#@}" -ne 3 ]; then
-  echo "usage: $0 path/to/kernel_partition_img path/to/rootfs_partition_img"
-  echo "    or $0 path/to/chromiumos_img kern_part_no rootfs_part_no"
-  exit 1
-fi
-
-if [ "$CROS_GENERATE_UPDATE_PAYLOAD_CALLED" != "1" ]; then
-  echo "WARNING:"
-  echo " This script should only be called from cros_generate_update_payload"
-  echo " Please run that script with --help to see how to use it."
-fi
-
 if ! image_has_command pigz; then
   (echo "WARNING:"
    echo " Your system does not have pigz (parallel gzip) installed."
@@ -66,31 +54,46 @@
   echo "running $0 as root which is unneccessary"
 fi
 
-# Determine the offset size, and file name of parameters
-if [ -z "$3" ]; then
-  # kernnel_img rootfs_img
-  KPART="$1"
-  ROOT_PART="$2"
-  KPART_SIZE=$(stat -c%s "$KPART")
-  ROOT_PART_SIZE=$(stat -c%s "$ROOT_PART")
-  KPART_OFFSET=0
-  KPART_SECTORS=$((KPART_SIZE / 512))
-  ROOT_OFFSET=0
-  ROOT_SECTORS=$((ROOT_PART_SIZE / 512))
-else
-  # chromiumos_img kern_part_no rootfs_part_no
-  KPART="$1"
-  ROOT_PART="$1"
-  KPART_OFFSET="$(image_part_offset "$KPART" "$2")" ||
-    image_die "cannot retieve kernel partition offset"
-  KPART_SECTORS="$(image_part_size "$KPART" "$2")" ||
-    image_die "cannot retieve kernel partition size"
-  ROOT_OFFSET="$(image_part_offset "$ROOT_PART" "$3")" ||
-    image_die "cannot retieve root partition offset"
-  ROOT_SECTORS="$(image_part_size "$ROOT_PART" "$3")" ||
-    image_die "cannot retieve root partition size"
-  KPART_SIZE=$((KPART_SECTORS * 512))
-fi
+# Usage: load_partition_file VARIABLE_NAME_PREFIX partition_file
+# Writes VARIABLE_NAME_PREFIX[, _OFFSE, _SIZE, _SECTORS] by parsing
+# partition_file, which can be a simple file or image:partno.
+load_partition_file() {
+  local var_prefix="$1"
+  local var="$2"
+  local var_offset=""
+  local var_size=""
+  local var_sectors=""
+  local part_no="${var##*:}"
+
+  # test if var is in image:partno format.
+  if [ "$part_no" != "$var" ]; then
+    var="${var%:*}"
+  else
+    part_no=""
+  fi
+
+  if [ -z "$part_no" ]; then
+    var_offset=0
+    var_size="$(stat -c"%s" "$var")" ||
+      image_die "Invalid file: $var"
+    var_sectors="$((var_size / 512))"
+  else
+    var_offset="$(image_part_offset "$var" "$part_no")" ||
+      image_die "Cannot retieve offset for partition $var:$part_no"
+    var_sectors="$(image_part_size "$var" "$part_no")" ||
+      image_die "Cannot retieve size for partition $var:$part_no"
+    var_size=$((var_sectors * 512))
+  fi
+
+  # publish the values
+  eval "${var_prefix}"="$var"
+  eval "${var_prefix}_OFFSET"="$var_offset"
+  eval "${var_prefix}_SIZE"="$var_size"
+  eval "${var_prefix}_SECTORS"="$var_sectors"
+}
+
+load_partition_file KPART "$1"
+load_partition_file ROOTFS "$2"
 
 # Sanity check size.
 if [ "$KPART_SIZE" -gt $((16 * 1024 * 1024)) ]; then
@@ -99,7 +102,13 @@
   exit 1
 fi
 
-FINAL_OUT_FILE=$(dirname "$1")/update.gz
+if [ "$#" = "3" ]; then
+  FINAL_OUT_DIR="$(readlink -f $3)"
+else
+  FINAL_OUT_DIR="$(dirname "$(readlink -f $1)")"
+fi
+FINAL_OUT_FILE="$FINAL_OUT_DIR/update.gz"
+echo "Output: $FINAL_OUT_FILE"
 
 # Update payload format:
 #  [kernel_size: big-endian uint64][kernel_blob][rootfs_blob]
@@ -116,7 +125,7 @@
     echo "Compressing kernel..." >&2
     image_dump_partial_file "$KPART" "$KPART_OFFSET" "$KPART_SECTORS"
     echo "Compressing rootfs..." >&2
-    image_dump_partial_file "$ROOT_PART" "$ROOT_OFFSET" "$ROOT_SECTORS") |
+    image_dump_partial_file "$ROOTFS" "$ROOTFS_OFFSET" "$ROOTFS_SECTORS") |
   image_gzip_compress -c -9 |
   tee "$FINAL_OUT_FILE" |
   openssl sha1 -binary |