1#!/bin/bash
2#
3# Copyright (C) 2010 The Android Open Source Project
4# 
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8
9#      http://www.apache.org/licenses/LICENSE-2.0
10# 
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# mkobb.sh - Creates OBB files on Linux machines
19
20# Directory where we should temporarily mount the OBB loopback to copy files
21MOUNTDIR=/tmp
22
23# Presets. Changing these will probably break your OBB on the device
24CRYPTO=twofish
25FS=vfat
26MKFS=mkfs.vfat
27LOSETUP=losetup
28BLOCK_SIZE=512
29SLOP=512 # Amount of filesystem slop in ${BLOCK_SIZE} blocks
30
31find_binaries() {
32    MKFSBIN=`which ${MKFS}`
33    LOSETUPBIN=`which ${LOSETUP}`
34    MOUNTBIN=`which mount`
35    UMOUNTBIN=`which umount`
36    DDBIN=`which dd`
37    RSYNCBIN=`which rsync`
38    PBKDF2GEN=`which pbkdf2gen`
39}
40
41check_prereqs() {
42    if [ "`uname -s`x" != "Linuxx" ]; then \
43        echo "ERROR: This script only works on Linux!"
44        exit 1
45    fi
46
47    if ! egrep -q "^cryptoloop " /proc/modules; then \
48        echo "ERROR: Could not find cryptoloop in the kernel."
49        echo "Perhaps you need to: modprobe cryptoloop"
50        exit 1
51    fi
52
53    if ! egrep -q "name\s*:\s*${CRYPTO}$" /proc/crypto; then \
54        echo "ERROR: Could not find crypto \`${CRYPTO}' in the kernel."
55        echo "Perhaps you need to: modprobe ${CRYPTO}"
56        exit 1
57    fi
58
59    if ! egrep -q "^\s*${FS}$" /proc/filesystems; then \
60        echo "ERROR: Could not find filesystem \`${FS}' in the kernel."
61        echo "Perhaps you need to: modprobe ${FS}"
62        exit 1
63    fi
64
65    if [ "${MKFSBIN}x" = "x" ]; then \
66        echo "ERROR: Could not find ${MKFS} in your path!"
67        exit 1
68    elif [ ! -x "${MKFSBIN}" ]; then \
69        echo "ERROR: ${MKFSBIN} is not executable!"
70        exit 1
71    fi
72
73    if [ "${LOSETUPBIN}x" = "x" ]; then \
74        echo "ERROR: Could not find ${LOSETUP} in your path!"
75        exit 1
76    elif [ ! -x "${LOSETUPBIN}" ]; then \
77        echo "ERROR: ${LOSETUPBIN} is not executable!"
78        exit 1
79    fi
80
81    if [ "${PBKDF2GEN}x" = "x" ]; then \
82        echo "ERROR: Could not find pbkdf2gen in your path!"
83        exit 1
84    fi
85}
86
87cleanup() {
88    if [ "${loopdev}x" != "x" ]; then \
89        ${LOSETUPBIN} -d ${loopdev}
90    fi
91}
92
93hidden_prompt() {
94    unset output
95    prompt="$1"
96    outvar="$2"
97    while read -s -n 1 -p "$prompt" c; do \
98        if [ "x$c" = "x" ]; then \
99            break
100        fi
101        prompt='*'
102        output="${output}${c}"
103    done
104    echo
105    eval $outvar="$output"
106    unset output
107}
108
109read_key() {
110    hidden_prompt "        Encryption key: " key
111
112    if [ "${key}x" = "x" ]; then \
113        echo "ERROR: An empty key is not allowed!"
114        exit 1
115    fi
116
117    hidden_prompt "Encryption key (again): " key2
118
119    if [ "${key}x" != "${key2}x" ]; then \
120        echo "ERROR: Encryption keys do not match!"
121        exit 1
122    fi
123}
124
125onexit() {
126    if [ "x${temp_mount}" != "x" ]; then \
127        ${UMOUNTBIN} ${temp_mount}
128        rmdir ${temp_mount}
129    fi
130    if [ "x${loop_dev}" != "x" ]; then \
131        if [ ${use_crypto} -eq 1 ]; then \
132            dmsetup remove -f ${loop_dev}
133            ${LOSETUPBIN} -d ${old_loop_dev}
134        else \
135            ${LOSETUPBIN} -d ${loop_dev}
136        fi
137    fi
138    if [ "x${tempfile}" != "x" -a -f "${tempfile}" ]; then \
139        rm -f ${tempfile}
140    fi
141    if [ "x${keyfile}" != "x" -a -f "${keyfile}" ]; then \
142        rm -f ${keyfile}
143    fi
144    echo "Fatal error."
145    exit 1
146}
147
148usage() {
149    echo "mkobb.sh -- Create OBB files for use on Android"
150    echo ""
151    echo " -d <directory> Use <directory> as input for OBB files"
152    echo " -k <key>       Use <key> to encrypt OBB file"
153    echo " -K             Prompt for key to encrypt OBB file"
154    echo " -o <filename>  Write OBB file out to <filename>"
155    echo " -v             Verbose mode"
156    echo " -h             Help; this usage screen"
157}
158
159find_binaries
160check_prereqs
161
162use_crypto=0
163
164args=`getopt -o d:hk:Ko:v -- "$@"`
165eval set -- "$args"
166
167while true; do \
168    case "$1" in
169        -d) directory=$2; shift 2;;
170        -h) usage; exit 1;;
171        -k) key=$2; use_crypto=1; shift 2;;
172        -K) prompt_key=1; use_crypto=1; shift;;
173        -v) verbose=1; shift;;
174        -o) filename=$2; shift 2;;
175        --) shift; break;;
176        *) echo "ERROR: Invalid argument in option parsing! Cannot recover. Ever."; exit 1;;
177    esac
178done
179
180if [ "${directory}x" = "x" -o ! -d "${directory}" ]; then \
181    echo "ERROR: Must specify valid input directory"
182    echo ""
183    usage
184    exit 1;
185fi
186
187if [ "${filename}x" = "x" ]; then \
188    echo "ERROR: Must specify filename"
189    echo ""
190    usage
191    exit 1;
192fi
193
194if [ ${use_crypto} -eq 1 -a "${key}x" = "x" -a 0${prompt_key} -eq 0 ]; then \
195    echo "ERROR: Crypto desired, but no key supplied or requested to prompt for."
196    exit 1
197fi
198
199if [ 0${prompt_key} -eq 1 ]; then \
200    read_key
201fi
202
203outdir=`dirname ${filename}`
204if [ ! -d "${outdir}" ]; then \
205    echo "ERROR: Output directory does not exist: ${outdir}"
206    exit 1
207fi
208
209# Make sure we clean up any stuff we create from here on during error conditions
210trap onexit ERR
211
212tempfile=$(tempfile -d ${outdir}) || ( echo "ERROR: couldn't create temporary file in ${outdir}"; exit 1 )
213
214block_count=`du -s --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
215if [ $? -ne 0 ]; then \
216    echo "ERROR: Couldn't read size of input directory ${directory}"
217    exit 1
218fi
219
220echo "Creating temporary file..."
221${DDBIN} if=/dev/zero of=${tempfile} bs=${BLOCK_SIZE} count=$((${block_count} + ${SLOP})) > /dev/null 2>&1
222if [ $? -ne 0 ]; then \
223    echo "ERROR: creating temporary file: $?"
224fi
225
226loop_dev=$(${LOSETUPBIN} -f) || ( echo "ERROR: losetup wouldn't tell us the next unused device"; exit 1 )
227
228${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
229
230if [ ${use_crypto} -eq 1 ]; then \
231    eval `${PBKDF2GEN} ${key}`
232    unique_dm_name=`basename ${tempfile}`
233    echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
234    old_loop_dev=${loop_dev}
235    loop_dev=/dev/mapper/${unique_dm_name}
236fi
237
238#
239# Create the filesystem
240#
241echo ""
242${MKFSBIN} -I ${loop_dev}
243echo ""
244
245#
246# Make the temporary mount point and mount it
247#
248temp_mount="${MOUNTDIR}/${RANDOM}"
249mkdir ${temp_mount}
250${MOUNTBIN} -t ${FS} -o loop ${loop_dev} ${temp_mount}
251
252#
253# rsync the files!
254#
255echo "Copying files:"
256${RSYNCBIN} -av --no-owner --no-group ${directory}/ ${temp_mount}/
257echo ""
258
259echo "Successfully created \`${filename}'"
260
261if [ ${use_crypto} -eq 1 ]; then \
262    echo "salt for use with obbtool is:"
263    echo "${salt}"
264fi
265
266#
267# Undo all the temporaries
268#
269umount ${temp_mount}
270rmdir ${temp_mount}
271if [ ${use_crypto} -eq 1 ]; then \
272    dmsetup remove -f ${loop_dev}
273    ${LOSETUPBIN} -d ${old_loop_dev}
274else \
275    ${LOSETUPBIN} -d ${loop_dev}
276fi
277mv ${tempfile} ${filename}
278
279trap - ERR
280
281exit 0
282