1#!/bin/bash
2
3#
4# Copyright (C) 2015 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19# This script generates some sample images used in unittests and packages them
20# in the sample_images.tar.bz2 file. The list of generated images and their
21# options are described in the main() function. You need to manually run this
22# script to update the generated images whenever you modify this script.
23
24set -e
25
26# cleanup <path>
27# Unmount and remove the mountpoint <path>
28cleanup() {
29  if ! sudo umount "$1" 2>/dev/null; then
30    if mountpoint -q "$1"; then
31      sync && sudo umount "$1"
32    fi
33  fi
34  rmdir "$1"
35}
36
37# add_files_default <mntdir> <block_size>
38# Add several test files to the image mounted in <mntdir>.
39add_files_default() {
40  local mntdir="$1"
41  local block_size="$2"
42
43  ### Generate the files used in unittest with descriptive names.
44  sudo touch "${mntdir}"/empty-file
45
46  # regular: Regular files.
47  echo "small file" | sudo dd of="${mntdir}"/regular-small status=none
48  dd if=/dev/zero bs=1024 count=16 status=none | tr '\0' '\141' |
49    sudo dd of="${mntdir}"/regular-16k status=none
50  sudo dd if=/dev/zero of="${mntdir}"/regular-32k-zeros bs=1024 count=16 \
51    status=none
52
53  echo "with net_cap" | sudo dd of="${mntdir}"/regular-with_net_cap status=none
54  sudo setcap cap_net_raw=ep "${mntdir}"/regular-with_net_cap
55
56  # sparse_empty: Files with no data blocks at all (only sparse holes).
57  sudo truncate --size=10240 "${mntdir}"/sparse_empty-10k
58  sudo truncate --size=$(( block_size * 2 )) "${mntdir}"/sparse_empty-2blocks
59
60  # sparse: Files with some data blocks but also sparse holes.
61  echo -n "foo" |
62    sudo dd of="${mntdir}"/sparse-16k-last_block bs=1 \
63      seek=$(( 16 * 1024 - 3)) status=none
64
65  # ext2 inodes have 12 direct blocks, one indirect, one double indirect and
66  # one triple indirect. 10000 should be enough to have an indirect and double
67  # indirect block.
68  echo -n "foo" |
69    sudo dd of="${mntdir}"/sparse-10000blocks bs=1 \
70      seek=$(( block_size * 10000 )) status=none
71
72  sudo truncate --size=16384 "${mntdir}"/sparse-16k-first_block
73  echo "first block" | sudo dd of="${mntdir}"/sparse-16k-first_block status=none
74
75  sudo truncate --size=16384 "${mntdir}"/sparse-16k-holes
76  echo "a" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=100 status=none
77  echo "b" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=10000 status=none
78
79  # link: symlinks and hardlinks.
80  sudo ln -s "broken-link" "${mntdir}"/link-short_symlink
81  sudo ln -s $(dd if=/dev/zero bs=256 count=1 status=none | tr '\0' '\141') \
82    "${mntdir}"/link-long_symlink
83  sudo ln "${mntdir}"/regular-16k "${mntdir}"/link-hard-regular-16k
84
85  # Directories.
86  sudo mkdir -p "${mntdir}"/dir1/dir2/dir1
87  echo "foo" | sudo tee "${mntdir}"/dir1/dir2/file >/dev/null
88  echo "bar" | sudo tee "${mntdir}"/dir1/file >/dev/null
89
90  # FIFO
91  sudo mkfifo "${mntdir}"/fifo
92
93  # character special file
94  sudo mknod "${mntdir}"/cdev c 2 3
95
96  # removed: removed files that should not be listed.
97  echo "We will remove this file so it's contents will be somewhere in the " \
98    "empty space data but it won't be all zeros." |
99    sudo dd of="${mntdir}"/removed conv=fsync status=none
100  sudo rm "${mntdir}"/removed
101}
102
103# add_files_ue_settings <mntdir> <block_size>
104# Add the update_engine.conf settings file. This file contains the
105add_files_ue_settings() {
106  local mntdir="$1"
107
108  sudo mkdir -p "${mntdir}"/etc >/dev/null
109  sudo tee "${mntdir}"/etc/update_engine.conf >/dev/null <<EOF
110PAYLOAD_MINOR_VERSION=1234
111EOF
112  # Example of a real lsb-release file released on link stable.
113  sudo tee "${mntdir}"/etc/lsb-release >/dev/null <<EOF
114CHROMEOS_AUSERVER=https://tools.google.com/service/update2
115CHROMEOS_BOARD_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
116CHROMEOS_CANARY_APPID={90F229CE-83E2-4FAF-8479-E368A34938B1}
117CHROMEOS_DEVSERVER=
118CHROMEOS_RELEASE_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
119CHROMEOS_RELEASE_BOARD=link-signed-mp-v4keys
120CHROMEOS_RELEASE_BRANCH_NUMBER=63
121CHROMEOS_RELEASE_BUILD_NUMBER=6946
122CHROMEOS_RELEASE_BUILD_TYPE=Official Build
123CHROMEOS_RELEASE_CHROME_MILESTONE=43
124CHROMEOS_RELEASE_DESCRIPTION=6946.63.0 (Official Build) stable-channel link
125CHROMEOS_RELEASE_NAME=Chrome OS
126CHROMEOS_RELEASE_PATCH_NUMBER=0
127CHROMEOS_RELEASE_TRACK=stable-channel
128CHROMEOS_RELEASE_VERSION=6946.63.0
129GOOGLE_RELEASE=6946.63.0
130EOF
131}
132
133add_files_postinstall() {
134  local mntdir="$1"
135
136  sudo mkdir -p "${mntdir}"/bin >/dev/null
137
138  # A postinstall bash program.
139  sudo tee "${mntdir}"/bin/postinst_example >/dev/null <<EOF
140#!/etc/../bin/sh
141echo "I'm a postinstall program and I know how to write to stdout"
142echo "My call was $@"
143exit 0
144EOF
145
146  # A symlink to another program. This should also work.
147  sudo ln -s "postinst_example" "${mntdir}"/bin/postinst_link
148
149  sudo tee "${mntdir}"/bin/postinst_fail3 >/dev/null <<EOF
150#!/etc/../bin/sh
151exit 3
152EOF
153
154  sudo tee "${mntdir}"/bin/postinst_fail1 >/dev/null <<EOF
155#!/etc/../bin/sh
156exit 1
157EOF
158
159  # A program that succeeds if it is suspended during the first 5 minutes.
160  sudo tee "${mntdir}"/bin/postinst_suspend >/dev/null <<EOF
161#!/etc/../bin/sh
162trap "{ echo Got a SIGCONT; exit 0; }" CONT
163# Signal that we are ready to receive the signal by redirecting our stdin to
164# /dev/zero, the test can detect that.
165exec </dev/zero
166# Allow the signal handler to run every 100 ms.
167i=3000
168while [ \$i -ge 0 ]; do
169  sleep 0.1
170  i=\$((i-1))
171done
172exit 1
173EOF
174
175  # A program that reports back progress.
176  sudo tee "${mntdir}"/bin/postinst_progress >/dev/null <<EOF
177#!/etc/../bin/sh
178# These values have exact representation in IEEE 754 so we avoid rounding
179# errors.
180echo global_progress 0.25 >&3
181echo global_progress 0.5 >&3
182echo global_progress 1.0 >&3
183exit 0
184EOF
185
186  # A postinstall bash program.
187  sudo tee "${mntdir}"/bin/self_check_context >/dev/null <<EOF
188#!/etc/../bin/sh
189echo "This is my context:"
190ls -lZ "\$0" | grep -F ' u:object_r:postinstall_file:s0 ' || exit 5
191exit 0
192EOF
193
194  sudo tee "${mntdir}"/postinst >/dev/null <<EOF
195#!/etc/../bin/sh
196echo "postinst"
197exit 0
198EOF
199
200  sudo chmod +x "${mntdir}"/postinst "${mntdir}"/bin/*
201}
202
203# generate_fs <filename> <kind> <size> [block_size] [block_groups]
204generate_fs() {
205  local filename="$1"
206  local kind="$2"
207  local size="$3"
208  local block_size="${4:-4096}"
209  local block_groups="${5:-}"
210
211  local mkfs_opts=( -q -F -b "${block_size}" -L "ROOT-TEST" -t ext2 )
212  if [[ -n "${block_groups}" ]]; then
213    mkfs_opts+=( -G "${block_groups}" )
214  fi
215
216  local mntdir=$(mktemp --tmpdir -d generate_ext2.XXXXXX)
217  trap 'cleanup "${mntdir}"; rm -f "${filename}"' INT TERM EXIT
218
219  # Cleanup old image.
220  if [[ -e "${filename}" ]]; then
221    rm -f "${filename}"
222  fi
223  truncate --size="${size}" "${filename}"
224
225  mkfs.ext2 "${mkfs_opts[@]}" "${filename}"
226  sudo mount "${filename}" "${mntdir}" -o loop
227
228  case "${kind}" in
229    unittest)
230      add_files_ue_settings "${mntdir}" "${block_size}"
231      add_files_postinstall "${mntdir}" "${block_size}"
232      ;;
233    default)
234      add_files_default "${mntdir}" "${block_size}"
235      ;;
236    empty)
237      ;;
238  esac
239
240  cleanup "${mntdir}"
241  trap - INT TERM EXIT
242}
243
244OUTPUT_DIR=$(dirname "$0")
245IMAGES=()
246
247# generate_image <image_name> [<image args> ...]
248generate_image() {
249  echo "Generating image $1.img"
250  IMAGES+=( "$1.img" )
251  generate_fs "${OUTPUT_DIR}/$1.img" "${@:2}"
252}
253
254main() {
255  # Add more sample images here.
256  generate_image disk_ext2_1k default $((1024 * 1024)) 1024
257  generate_image disk_ext2_4k default $((1024 * 4096)) 4096
258  generate_image disk_ext2_4k_empty empty $((1024 * 4096)) 4096
259  generate_image disk_ext2_unittest unittest $((1024 * 4096)) 4096
260
261  # Generate the tarball and delete temporary images.
262  echo "Packing tar file sample_images.tar.bz2"
263  tar -jcf "${OUTPUT_DIR}/sample_images.tar.bz2" -C "${OUTPUT_DIR}" \
264    --sparse "${IMAGES[@]}"
265  cd "${OUTPUT_DIR}"
266  rm "${IMAGES[@]}"
267}
268
269main
270