Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | SCRIPT_ROOT=$(dirname "$(readlink -f "$0")") |
| 7 | . "${SCRIPT_ROOT}/build_library/build_common.sh" || exit 1 |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 8 | . "${SCRIPT_ROOT}/build_library/filesystem_util.sh" || exit 1 |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 9 | |
| 10 | assert_inside_chroot "$@" |
| 11 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 12 | DEFINE_string arch "amd64" \ |
| 13 | "Architecture of the VM image" |
| 14 | DEFINE_string filesystem "ext4" \ |
| 15 | "Filesystem for the rootfs image" |
| 16 | DEFINE_string image "" \ |
| 17 | "Chromium OS disk image to build the Termina image from" |
| 18 | DEFINE_string output "" \ |
| 19 | "Output directory" |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 20 | DEFINE_boolean test_image ${FLAGS_FALSE} \ |
| 21 | "True if the image is a test image" t |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 22 | |
| 23 | FLAGS_HELP="USAGE: ${SCRIPT_NAME} [flags] |
| 24 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 25 | To build a tatl test image, try: |
| 26 | $ ./build_image --board=tatl test |
| 27 | $ ${SCRIPT_NAME} --image=../build/images/tatl/latest/chromiumos_test_image.bin --output=tatl |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 28 | " |
| 29 | FLAGS "$@" || exit 1 |
| 30 | eval set -- "${FLAGS_ARGV}" |
| 31 | switch_to_strict_mode |
| 32 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 33 | get_version() { |
| 34 | local output_dir="$1" |
| 35 | awk -F'=' -v key='CHROMEOS_RELEASE_VERSION' '$1==key { print $2 }' \ |
| 36 | "${output_dir}"/lsb-release |
| 37 | } |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 38 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 39 | is_test_image() { |
| 40 | local output_dir="$1" |
| 41 | grep -q testimage-channel "${output_dir}"/lsb-release |
| 42 | } |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 43 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 44 | read_le_int() { |
| 45 | local disk="$1" |
| 46 | local offset="$2" |
| 47 | local size="$3" |
| 48 | |
| 49 | case "${size}" in |
| 50 | 1 | 2 | 4 | 8) |
| 51 | ;; |
| 52 | *) die "${size} is not a valid int size to read" |
| 53 | ;; |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 54 | esac |
| 55 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 56 | local raw="$(xxd -g ${size} -e -s ${offset} -l ${size} ${disk} | cut -d' ' -f2)" |
| 57 | local result="$(( 16#${raw} ))" |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 58 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 59 | echo "${result}" |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 60 | } |
| 61 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 62 | extract_squashfs_partition() { |
| 63 | local src_disk="$1" |
| 64 | local src_part="$2" |
| 65 | local dst_file="$3" |
| 66 | |
| 67 | local part_start_blks="$(cgpt show -i "${src_part}" -b "${src_disk}")" |
| 68 | local part_start_bytes="$(( part_start_blks * 512 ))" |
| 69 | local part_size_blks="$(cgpt show -i "${src_part}" -s "${src_disk}")" |
| 70 | local part_size_bytes="$(( part_size_blks * 512 ))" |
| 71 | |
| 72 | # To be sure we're extracting a squashfs partition, verify the magic. |
| 73 | # See fs/squashfs/squashfs_fs.h. |
| 74 | local magic="$(read_le_int ${src_disk} ${part_start_bytes} 4)" |
| 75 | if [[ "${magic}" -ne 0x73717368 ]]; then |
| 76 | die "Partition ${src_part} doesn't look like a squashfs partition" |
| 77 | fi |
| 78 | |
| 79 | dd if="${src_disk}" of="${dst_file}" skip="${part_start_bytes}c" \ |
| 80 | count="${part_size_bytes}c" iflag=skip_bytes,count_bytes |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 81 | } |
| 82 | |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 83 | can_hardlink() { |
| 84 | local file1=$1 |
| 85 | local file2=$2 |
| 86 | |
| 87 | # The file is considered hard-linkable if the access rights, user, and group |
| 88 | # are the same. |
| 89 | [[ "$(sudo stat -c '%a' ${prev_line})" = "$(sudo stat -c '%a' ${line})" ]] && \ |
| 90 | [[ "$(sudo stat -c '%u' ${prev_line})" = "$(sudo stat -c '%u' ${line})" ]] && \ |
| 91 | [[ "$(sudo stat -c '%g' ${prev_line})" = "$(sudo stat -c '%g' ${line})" ]] |
| 92 | } |
| 93 | |
| 94 | do_hardlinks() { |
| 95 | local dir=$1 |
| 96 | |
| 97 | local line prev_line="" |
| 98 | while read line; do |
| 99 | # Treat the first file as the original. |
| 100 | if [[ -n "${prev_line}" && -n "${line}" ]]; then |
| 101 | if can_hardlink "${prev_line}" "${line}"; then |
| 102 | sudo ln -f "${prev_line}" "${line}" |
| 103 | fi |
| 104 | fi |
| 105 | |
| 106 | prev_line="${line}" |
| 107 | done < <(sudo fdupes --recurse ${dir}) |
| 108 | } |
| 109 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 110 | # Repack termina rootfs. |
| 111 | repack_rootfs() { |
| 112 | local output_dir="$1" |
| 113 | local fs_type="$2" |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 114 | local arch="$3" |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 115 | local rootfs_img="${output_dir}/vm_rootfs.img" |
| 116 | local stateful_img="${output_dir}/vm_stateful.img" |
| 117 | |
| 118 | # Create image in a temporary directory to avoid the need for extra space |
| 119 | # on the final rootfs. |
| 120 | local rootfs="${output_dir}/rootfs" |
| 121 | local stateful="${output_dir}/stateful" |
| 122 | |
| 123 | sudo unsquashfs -d "${rootfs}" "${rootfs_img}" |
| 124 | sudo unsquashfs -d "${stateful}" "${stateful_img}" |
| 125 | |
| 126 | # Remove source images. |
| 127 | sudo rm -f "${rootfs_img}" "${stateful_img}" |
| 128 | |
| 129 | # Fix up rootfs. |
| 130 | |
| 131 | # Remove efi cruft. |
| 132 | sudo rm -rf "${rootfs}/boot"/{efi,syslinux} |
| 133 | # Don't need firmware if you don't have hardware! |
| 134 | sudo rm -rf "${rootfs}/lib/firmware" |
| 135 | # Get rid of stateful, it's not needed on termina. |
| 136 | sudo rm -rf "${rootfs}/mnt/stateful_partition" |
Stephen Barber | 8e3381b | 2018-01-30 13:04:44 -0800 | [diff] [blame] | 137 | # Create container stateful and shared dirs. |
| 138 | sudo mkdir "${rootfs}"/mnt/{stateful,shared} |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 139 | # Copy the dev_image into its location at /usr/local. |
| 140 | sudo cp -aT "${stateful}"/dev_image "${rootfs}/usr/local" |
| 141 | |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 142 | if [[ "${arch}" == "arm" ]]; then |
| 143 | cp "${rootfs}"/boot/Image-* "${output_dir}/vm_kernel" |
| 144 | else |
| 145 | "${CHROOT_TRUNK_DIR}"/src/third_party/kernel/v4.4/scripts/extract-vmlinux \ |
| 146 | "${rootfs}"/boot/vmlinuz > "${output_dir}/vm_kernel" |
| 147 | fi |
| 148 | |
| 149 | # Remove vmlinuz from the rootfs; it's not necessary. |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 150 | sudo rm -rf "${rootfs}"/boot/vmlinuz* |
| 151 | |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 152 | do_hardlinks "${rootfs}" |
| 153 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 154 | sudo cp "${rootfs}/etc/lsb-release" "${output_dir}/lsb-release" |
Stephen Barber | 8e3381b | 2018-01-30 13:04:44 -0800 | [diff] [blame] | 155 | sudo cp "${rootfs}/opt/google/chrome/resources/about_os_credits.html" \ |
| 156 | "${output_dir}/about_os_credits.html" |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 157 | |
| 158 | case "${fs_type}" in |
| 159 | squashfs) |
| 160 | sudo mksquashfs "${rootfs}" "${rootfs_img}" -comp lzo |
| 161 | ;; |
| 162 | ext4) |
Chirantan Ekbote | 694afec | 2018-04-23 16:53:19 -0700 | [diff] [blame] | 163 | # Start with 400MB, then shrink. |
| 164 | local image_size=400 |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 165 | truncate --size "${image_size}M" "${rootfs_img}" |
| 166 | /sbin/mkfs.ext4 -F -m 0 -i 16384 -b 1024 -O "^has_journal" "${rootfs_img}" |
| 167 | local rootfs_mnt="$(mktemp -d)" |
| 168 | fs_mount "${rootfs_img}" "${rootfs_mnt}" ext4 rw |
| 169 | sudo cp -aT "${rootfs}" "${rootfs_mnt}" |
| 170 | fs_umount "${rootfs_img}" "${rootfs_mnt}" ext4 rw |
| 171 | # Shrink to minimum size. |
| 172 | /sbin/e2fsck -f "${rootfs_img}" |
| 173 | /sbin/resize2fs -M "${rootfs_img}" |
| 174 | rmdir "${rootfs_mnt}" |
| 175 | ;; |
| 176 | *) |
| 177 | die_notrace "Unsupported fs type ${fs_type}." |
| 178 | ;; |
| 179 | esac |
| 180 | |
| 181 | sudo rm -rf "${rootfs}" "${stateful}" |
| 182 | } |
| 183 | |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 184 | main() { |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 185 | if [[ -z "${FLAGS_image}" ]]; then |
| 186 | die_notrace "Please provide an image using --image" |
| 187 | elif [[ ! -f "${FLAGS_image}" ]]; then |
| 188 | die_notrace "'${FLAGS_image}' does not exist" |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 189 | fi |
| 190 | |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 191 | if [[ "${FLAGS_arch}" != "amd64" && "${FLAGS_arch}" != "arm" ]]; then |
| 192 | die_notrace "Architecture '${FLAGS_arch}' is not valid. Options are 'amd64' and 'arm'" |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 193 | fi |
| 194 | |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 195 | case "${FLAGS_filesystem}" in |
| 196 | squashfs|ext4) |
| 197 | ;; |
| 198 | *) |
| 199 | die_notrace "Filesystem '${FLAGS_filesystem}' is not valid. Options are 'squashfs' and 'ext4'" |
| 200 | ;; |
| 201 | esac |
| 202 | |
| 203 | if [[ -z "${FLAGS_output}" ]]; then |
| 204 | die_notrace "Output directory was not specified" |
| 205 | elif [[ -e "${FLAGS_output}" ]]; then |
| 206 | die_notrace "${FLAGS_output} already exists" |
| 207 | fi |
| 208 | |
| 209 | local output_dir="${FLAGS_output}" |
| 210 | local stateful_img="${output_dir}/vm_stateful.img" |
| 211 | local rootfs_img="${output_dir}/vm_rootfs.img" |
| 212 | local image="${FLAGS_image}" |
| 213 | |
| 214 | mkdir -p "${output_dir}" |
| 215 | extract_squashfs_partition "${image}" "3" "${rootfs_img}" |
| 216 | extract_squashfs_partition "${image}" "1" "${stateful_img}" |
| 217 | |
Stephen Barber | debef58 | 2018-05-19 13:11:54 -0700 | [diff] [blame^] | 218 | repack_rootfs "${output_dir}" "${FLAGS_filesystem}" "${FLAGS_arch}" |
Stephen Barber | f9e93a3 | 2017-05-17 15:41:57 -0700 | [diff] [blame] | 219 | |
| 220 | info "Done! The resulting image is in '${output_dir}'" |
Stephen Barber | 3f7688f | 2017-02-16 11:23:44 -0800 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | main "$@" |