Home | History | Annotate | Download | only in obbtool
      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
     21 MOUNTDIR=/tmp
     22 
     23 # Presets. Changing these will probably break your OBB on the device
     24 CRYPTO=twofish
     25 FS=vfat
     26 MKFS=mkfs.vfat
     27 LOSETUP=losetup
     28 BLOCK_SIZE=512
     29 SLOP=512 # Amount of filesystem slop in ${BLOCK_SIZE} blocks
     30 
     31 find_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 
     41 check_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 
     87 cleanup() {
     88     if [ "${loopdev}x" != "x" ]; then \
     89         ${LOSETUPBIN} -d ${loopdev}
     90     fi
     91 }
     92 
     93 hidden_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 
    109 read_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 
    125 onexit() {
    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 
    148 usage() {
    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 
    159 find_binaries
    160 check_prereqs
    161 
    162 use_crypto=0
    163 
    164 args=`getopt -o d:hk:Ko:v -- "$@"`
    165 eval set -- "$args"
    166 
    167 while 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
    178 done
    179 
    180 if [ "${directory}x" = "x" -o ! -d "${directory}" ]; then \
    181     echo "ERROR: Must specify valid input directory"
    182     echo ""
    183     usage
    184     exit 1;
    185 fi
    186 
    187 if [ "${filename}x" = "x" ]; then \
    188     echo "ERROR: Must specify filename"
    189     echo ""
    190     usage
    191     exit 1;
    192 fi
    193 
    194 if [ ${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
    197 fi
    198 
    199 if [ 0${prompt_key} -eq 1 ]; then \
    200     read_key
    201 fi
    202 
    203 outdir=`dirname ${filename}`
    204 if [ ! -d "${outdir}" ]; then \
    205     echo "ERROR: Output directory does not exist: ${outdir}"
    206     exit 1
    207 fi
    208 
    209 # Make sure we clean up any stuff we create from here on during error conditions
    210 trap onexit ERR
    211 
    212 tempfile=$(tempfile -d ${outdir}) || ( echo "ERROR: couldn't create temporary file in ${outdir}"; exit 1 )
    213 
    214 block_count=`du -s --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
    215 if [ $? -ne 0 ]; then \
    216     echo "ERROR: Couldn't read size of input directory ${directory}"
    217     exit 1
    218 fi
    219 
    220 echo "Creating temporary file..."
    221 ${DDBIN} if=/dev/zero of=${tempfile} bs=${BLOCK_SIZE} count=$((${block_count} + ${SLOP})) > /dev/null 2>&1
    222 if [ $? -ne 0 ]; then \
    223     echo "ERROR: creating temporary file: $?"
    224 fi
    225 
    226 loop_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 
    230 if [ ${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}
    236 fi
    237 
    238 #
    239 # Create the filesystem
    240 #
    241 echo ""
    242 ${MKFSBIN} -I ${loop_dev}
    243 echo ""
    244 
    245 #
    246 # Make the temporary mount point and mount it
    247 #
    248 temp_mount="${MOUNTDIR}/${RANDOM}"
    249 mkdir ${temp_mount}
    250 ${MOUNTBIN} -t ${FS} -o loop ${loop_dev} ${temp_mount}
    251 
    252 #
    253 # rsync the files!
    254 #
    255 echo "Copying files:"
    256 ${RSYNCBIN} -av --no-owner --no-group ${directory}/ ${temp_mount}/
    257 echo ""
    258 
    259 echo "Successfully created \`${filename}'"
    260 
    261 if [ ${use_crypto} -eq 1 ]; then \
    262     echo "salt for use with obbtool is:"
    263     echo "${salt}"
    264 fi
    265 
    266 #
    267 # Undo all the temporaries
    268 #
    269 umount ${temp_mount}
    270 rmdir ${temp_mount}
    271 if [ ${use_crypto} -eq 1 ]; then \
    272     dmsetup remove -f ${loop_dev}
    273     ${LOSETUPBIN} -d ${old_loop_dev}
    274 else \
    275     ${LOSETUPBIN} -d ${loop_dev}
    276 fi
    277 mv ${tempfile} ${filename}
    278 
    279 trap - ERR
    280 
    281 exit 0
    282