blob: e7ae874fd8e31fde0c423f9f30adedcf1a050b4f [file] [log] [blame]
#!/bin/bash
# Copyright 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.
# This script contains common utility function to deal with disk images,
# especially for being redistributed into platforms without complete Chromium OS
# developing environment.
# CGPT Header: PMBR, header, table; sec_table, sec_header
IMAGE_CGPT_START_SIZE=$((1 + 1 + 32))
IMAGE_CGPT_END_SIZE=$((32 + 1))
IMAGE_CGPT_BS="512"
: ${CGPT:=}
: ${SUDO:=}
# Alignment of partition sectors
IMAGE_PARTITION_SECTOR_ALIGNMENT=256
# Checks if given command is available in current system
image_has_command() {
type "$1" >/dev/null 2>&1
}
# Prints error message and exit as 1 (error)
image_die() {
echo "ERROR: $@" >&2
exit 1
}
# Finds and checks if CGPT is available. Similar to image_check_part_tools, but
# this is dedicated for 'cgpt' since it's the only tool (currently) that can
# create and modify ChromeOS style partitions.
image_check_cgpt() {
if [ -z "${CGPT}" ]; then
image_die "Need cgpt utility."
fi
}
# Finds and checks if the system has tools to do partitioning.
image_check_part_tools() {
if image_has_command python || [ -n "${CGPT}" ] || image_has_command parted;
then
return
else
die "Missing partition tools. " \
"Please install Python or parted or cgpt, or run inside chroot."
fi
}
# Finds a tool program in PATH or given folder(s), and prints the found path.
image_find_tool() {
local tool="$1" dir=""
if image_has_command "${tool}"; then
echo "${tool}"
return
fi
shift
for dir in "$@"; do
if [ -x "${dir}/${tool}" ]; then
echo "$(readlink -f "${dir}/${tool}")"
return
fi
done
}
# 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" ] || [ "$size" -ge "$alignment" ]; then
offset=$((offset + alignment - (offset % alignment)))
fi
fi
echo "$((offset))"
}
# Finds the best partition tool and print partition offset
image_part_offset() {
local file="$1"
local partno="$2"
local unpack_file="$(dirname "$file")/unpack_partitions.sh"
if image_has_command python; then
${SUDO} python "${SCRIPT_DIR}/pygpt" show -b -i "${partno}" "${file}"
elif [ -n "${CGPT}" ]; then
${SUDO} "${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)
${SUDO} parted -m "$file" unit s print | grep -qs "^$partno:" || exit 1
${SUDO} parted -m "$file" unit s print | \
awk -F ':' "/^$partno:/ { print int(\$2) }"
elif [ -f "$unpack_file" ]; then
awk "/ $partno *Label:/ { print \$2 }" "$unpack_file"
else
exit 1
fi
}
# Finds the best partition tool and print partition size
image_part_size() {
local file="$1"
local partno="$2"
local unpack_file="$(dirname "$file")/unpack_partitions.sh"
if image_has_command python; then
${SUDO} python "${SCRIPT_DIR}/pygpt" show -s -i "${partno}" "${file}"
elif [ -n "${CGPT}" ]; then
${SUDO} "${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)
${SUDO} parted -m "$file" unit s print | grep -qs "^$partno:" || exit 1
${SUDO} parted -m "$file" unit s print | \
awk -F ':' "/^$partno:/ { print int(\$4) }"
elif [ -s "$unpack_file" ]; then
awk "/ $partno *Label:/ { print \$3 }" "$unpack_file"
else
exit 1
fi
}
# Callback of image_process_geometry. Prints the proper offset of current
# partition by give offset and size.
image_geometry_get_partition_offset() {
local offset="$1"
local sectors="$2"
local index="$3"
image_alignment "$offset" "$IMAGE_PARTITION_SECTOR_ALIGNMENT" "$sectors"
}
# Callback of image_process_geometry. Creates a partition by give offset,
# size (sectors), and index.
image_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
image_check_cgpt
"${CGPT}" add -b "$offset" -s "$sectors" -i "$index" -t reserved "$output_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 $IMAGE_PARTITION_SECTOR_ALIGNMENT $sectors)"
"$callback" "$offset" "$sectors" "$index" $param
offset="$((offset + sectors))"
index="$((index + 1))"
done
}
# Create a cgpt image file based on a list of disk geometry sectors in
# (offset, sectors) form. The form doesn't have to be aligned since this
# funciton will process it. Each partition will be formatted to
# cgpt data partition with $part_filesystem if it's specified.
image_geometry_build_file() {
local sectors_list="$1"
local output_file="$2"
local part_filesystem="$3"
local output_file_size=0
local partition_offsets
image_check_cgpt
# Calculate output image file size
partition_offsets="$(image_process_geometry \
"$IMAGE_CGPT_START_SIZE $sectors_list $IMAGE_CGPT_END_SIZE 1" \
image_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 * IMAGE_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 "$IMAGE_CGPT_START_SIZE $sectors_list" \
image_geometry_create_partition \
"$output_file"
}
# Dumps a file by given offset and size (in sectors)
image_dump_partial_file() {
local file="$1"
local offset="$2"
local sectors="$3"
local bs=512
# 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" \
oflag=sync status=noxfer 2>/dev/null |
pv -ptreb -B $bs -s $((sectors * bs))
else
dd if="$file" bs=$bs skip="$offset" count="$sectors" \
oflag=sync status=noxfer 2>/dev/null
fi
}
# Dumps a specific partition from given image file
image_dump_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_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"
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"
sudo losetup --offset $((offset * 512)) --sizelimit=$((size * 512)) \
-f --show "$file"
}
# Unmaps a loop device created by image_map_partition
image_unmap_partition() {
local map_point="$1"
sudo losetup -d "$map_point"
}
# Mounts a specific partition inside a given image file
image_mount_partition() {
local file="$1"
local part_num="$2"
local mount_point="$3"
local mount_opt="$4"
local mount_ext="$5"
local offset="$(SUDO=sudo image_part_offset "$file" "$part_num")" ||
image_die "failed to find partition #$part_num from: $file"
local size="$(SUDO=sudo image_part_size "$file" "$part_num")" ||
image_die "failed to find partition #$part_num from: $file"
if [ -z "$mount_opt" ]; then
# by default, mount as read-only.
mount_opt=",ro"
else
mount_opt=",$mount_opt"
fi
sudo mount \
-o "loop,offset=$((offset * 512)),sizelimit=$((size * 512))$mount_opt" \
$mount_ext \
"$file" \
"$mount_point"
}
# Unmounts a partition mount point by mount_partition
image_umount_partition() {
local mount_point="$1"
sudo umount "$mount_point"
}
# Copy a partition from one image to another (size must be equal)
image_partition_copy() {
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"
}
# 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"
}
# 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 "$object" >/dev/null 2>&1 || true
sudo rm -rf "$object" >/dev/null 2>&1 || true
else
rm -f "$object" >/dev/null 2>&1 || true
fi
done
}