#!/bin/bash

# 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 a Chromium OS update for use by the update engine.
# If a source .bin is specified, the update is assumed to be a delta update.

# --- 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 thisdir="$(dirname "$(readlink -f "$0")")"
  local common_paths=(/usr/lib/crosutils "${thisdir}")
  local path

  SCRIPT_ROOT=
  for path in "${common_paths[@]}"; do
    if [ -r "${path}/common.sh" ]; then
      SCRIPT_ROOT=${path}
      break
    fi
  done

  # HACK(zbehan): We have to fake GCLIENT_ROOT in case we're running inside
  # au_zip enviroment. GCLIENT_ROOT detection became fatal...
  [ "${SCRIPT_ROOT}" == "${thisdir}" ] && export GCLIENT_ROOT="."
}

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: Needs to be called from outside the chroot.
. "/usr/lib/installer/chromeos-common.sh" &> /dev/null || \
. "${SRC_ROOT}/platform/installer/chromeos-common.sh" &> /dev/null || \
. "./chromeos-common.sh" || \
  die "Unable to load /usr/lib/installer/chromeos-common.sh"

SRC_MNT=""
DST_MNT=""
SRC_KERNEL=""
SRC_ROOT=""
DST_KERNEL=""
DST_ROOT=""
STATE_MNT=""
STATE_LOOP_DEV=""

# Pass an arg to not exit 1 at the end
cleanup() {
  set +e
  if [ -n "$SRC_MNT" ]; then
    sudo umount -d "$SRC_MNT"
    [ -d "$SRC_MNT" ] && rmdir "$SRC_MNT"
    SRC_MNT=""
  fi
  if [ -n "$DST_MNT" ]; then
    sudo umount -d "$DST_MNT"
    [ -d "$DST_MNT" ] && rmdir "$DST_MNT"
    DST_MNT=""
  fi
  if [ -n "$STATE_MNT" ]; then
    sudo umount "$STATE_MNT"
    [ -d "$STATE_MNT" ] && rmdir "$STATE_MNT"
    STATE_MNT=""
  fi
  if [ -n "$STATE_LOOP_DEV" ]; then
    sudo losetup -d "$STATE_LOOP_DEV"
    STATE_LOOP_DEV=""
  fi
  rm -f "$SRC_KERNEL"
  rm -f "$SRC_ROOT"
  rm -f "$DST_KERNEL"
  rm -f "$DST_ROOT"
  [ -n "$1" ] || exit 1
}

extract_partition_to_temp_file() {
  local filename="$1"
  local partition="$2"
  local temp_file="$3"
  if [ -z "$temp_file" ]; then
    temp_file=$(mktemp /tmp/cros_generate_update_payload.XXXXXX)
    echo "$temp_file"
  fi

  local offset=$(partoffset "${filename}" ${partition})  # 512-byte sectors
  local length=$(partsize "${filename}" ${partition})  # 512-byte sectors
  local bs=512
  local sectors_per_two_mib=$((2 * 1024 * 1024 / 512))
  if [ $(( $offset % $sectors_per_two_mib )) -eq 0 -a \
       $(( $length % $sectors_per_two_mib )) -eq 0 ]; then
    bs=$((2 * 1024 * 1024))
    offset=$(($offset / $sectors_per_two_mib))
    length=$(($length / $sectors_per_two_mib))
  else
    warn "partition offset or length not at 2MiB boundary"
  fi
  dd if="$filename" of="$temp_file" bs=$bs count="$length" \
      skip="$offset" 2>/dev/null
}

patch_kernel() {
  local IMAGE="$1"
  local KERN_FILE="$2"

  echo "Patching kernel" $KERN_FILE
  echo "   into" $IMAGE
  STATE_LOOP_DEV=$(sudo losetup -f)
  [ -n "$STATE_LOOP_DEV" ] || die "no free loop device"
  local offset=$(partoffset "${IMAGE}" 1)
  offset=$(($offset * 512))
  sudo losetup -o "$offset" "$STATE_LOOP_DEV" "$IMAGE"
  STATE_MNT=$(mktemp -d /tmp/state.XXXXXX)
  sudo mount --read-only "$STATE_LOOP_DEV" "$STATE_MNT"
  dd if="$STATE_MNT"/vmlinuz_hd.vblock of="$KERN_FILE" conv=notrunc 2>/dev/null
  sudo umount "$STATE_MNT"
  STATE_MNT=""
  sudo losetup -d "$STATE_LOOP_DEV"
  STATE_LOOP_DEV=""
}

extract_kern_root() {
  local bin_file="$1"
  local kern_out="$2"
  local root_out="$3"

  if [ -z "$kern_out" ]; then
    die "missing kernel output filename"
  fi
  if [ -z "$root_out" ]; then
    die "missing root output filename"
  fi

  extract_partition_to_temp_file "$bin_file" 2 "$kern_out"
  if [ "$FLAGS_patch_kernel" -eq "$FLAGS_TRUE" ]; then
    patch_kernel "$bin_file" "$kern_out"
  fi
  extract_partition_to_temp_file "$bin_file" 3 "$root_out"
}

DEFINE_string image "" "The image that should be sent to clients."
DEFINE_string src_image "" "Optional: a source image. If specified, this makes\
 a delta update."
DEFINE_string output "" "Output file"
DEFINE_boolean outside_chroot "$FLAGS_FALSE" "Running outside of chroot."
DEFINE_boolean patch_kernel "$FLAGS_FALSE" "Whether or not to patch the kernel \
with the patch from the stateful partition (default: false)"
DEFINE_string private_key "" "Path to private key in .pem format."
DEFINE_boolean extract "$FLAGS_FALSE" "If set, extract old/new kernel/rootfs \
to [old|new]_[kern|root].dat. Useful for debugging (default: false)"
DEFINE_boolean full_kernel "$FLAGS_FALSE" "Generate a full kernel update even \
if generating a delta update (default: false)"

# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

set -e

if [ -n "$FLAGS_src_image" ] && \
   [ "$FLAGS_outside_chroot" -eq "$FLAGS_FALSE" ]; then
  # We need to be in the chroot for generating delta images.
  # by specifying --outside_chroot you can choose not to assert
  # this will allow us to run this script outside chroot.
  # Running this script outside chroot requires copying delta_generator binary
  # and also copying few shared libraries with it.
  assert_inside_chroot
fi

locate_gpt

if [ "$FLAGS_extract" -eq "$FLAGS_TRUE" ]; then
  if [ -n "$FLAGS_src_image" ]; then
    extract_kern_root "$FLAGS_src_image" old_kern.dat old_root.dat
  fi
  if [ -n "$FLAGS_image" ]; then
    extract_kern_root "$FLAGS_image" new_kern.dat new_root.dat
  fi
  echo Done extracting kernel/root
  exit 0
fi

[ -n "$FLAGS_output" ] ||
  die "Error: you must specify an output filename with --output FILENAME"

DELTA=$FLAGS_TRUE
[ -z "$FLAGS_src_image" ] && DELTA=$FLAGS_FALSE

echo "Generating an update"

# Sanity check that the real generator exists:
GENERATOR="$(which delta_generator)"
[ -x "$GENERATOR" ] || die "can't find delta_generator"

trap cleanup INT TERM EXIT
if [ "$DELTA" -eq "$FLAGS_TRUE" ]; then
  if [ "$FLAGS_full_kernel" -eq "$FLAGS_FALSE" ]; then
    SRC_KERNEL=$(extract_partition_to_temp_file "$FLAGS_src_image" 2)
    if [ "$FLAGS_patch_kernel" -eq "$FLAGS_TRUE" ]; then
      patch_kernel "$FLAGS_src_image" "$SRC_KERNEL"
    fi
    echo md5sum of src kernel:
    md5sum "$SRC_KERNEL"
  else
    echo "Generating a full kernel update."
  fi
  SRC_ROOT=$(extract_partition_to_temp_file "$FLAGS_src_image" 3)

  echo md5sum of src root:
  md5sum "$SRC_ROOT"
fi

DST_KERNEL=$(extract_partition_to_temp_file "$FLAGS_image" 2)
if [ "$FLAGS_patch_kernel" -eq "$FLAGS_TRUE" ]; then
  patch_kernel "$FLAGS_image" "$DST_KERNEL"
fi
DST_ROOT=$(extract_partition_to_temp_file "$FLAGS_image" 3)

if [ "$DELTA" -eq "$FLAGS_TRUE" ]; then
  SRC_MNT=$(mktemp -d /tmp/src_root.XXXXXX)
  sudo mount -o loop,ro "$SRC_ROOT" "$SRC_MNT"

  DST_MNT=$(mktemp -d /tmp/src_root.XXXXXX)
  sudo mount -o loop,ro "$DST_ROOT" "$DST_MNT"

  sudo LD_LIBRARY_PATH=${LD_LIBRARY_PATH}  PATH=${PATH} "$GENERATOR" \
      -new_dir "$DST_MNT" -new_image "$DST_ROOT" -new_kernel "$DST_KERNEL" \
      -old_dir "$SRC_MNT" -old_image "$SRC_ROOT" -old_kernel "$SRC_KERNEL" \
      -out_file "$FLAGS_output" -private_key "$FLAGS_private_key"
else
  "$GENERATOR" \
      -new_image "$DST_ROOT" -new_kernel "$DST_KERNEL" \
      -out_file "$FLAGS_output" -private_key "$FLAGS_private_key"
fi

trap - INT TERM EXIT
cleanup noexit

if [ "$DELTA" -eq "$FLAGS_TRUE" ]; then
  echo "Done generating delta."
else
  echo "Done generating full update."
fi
