From 23580ca27a0a8109312fdd36cc363ad1f4719889 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- tools/ota/Android.mk | 20 ++++ tools/ota/add-data-wipe | 118 +++++++++++++++++++++ tools/ota/convert-to-bmp.py | 79 ++++++++++++++ tools/ota/make-update-script.c | 228 +++++++++++++++++++++++++++++++++++++++++ tools/ota/otatool | 225 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 670 insertions(+) create mode 100644 tools/ota/Android.mk create mode 100755 tools/ota/add-data-wipe create mode 100644 tools/ota/convert-to-bmp.py create mode 100644 tools/ota/make-update-script.c create mode 100755 tools/ota/otatool (limited to 'tools') diff --git a/tools/ota/Android.mk b/tools/ota/Android.mk new file mode 100644 index 0000000..43e2135 --- /dev/null +++ b/tools/ota/Android.mk @@ -0,0 +1,20 @@ +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := make-update-script +LOCAL_SRC_FILES := make-update-script.c +include $(BUILD_HOST_EXECUTABLE) diff --git a/tools/ota/add-data-wipe b/tools/ota/add-data-wipe new file mode 100755 index 0000000..8d2626f --- /dev/null +++ b/tools/ota/add-data-wipe @@ -0,0 +1,118 @@ +#!/bin/bash +# +# Copyright (C) 2007 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +PROGNAME=`basename $0` + +function cleantmp +{ + if [ ! -z "$TMPDIR" ] + then + rm -rf "$TMPDIR" + TMPDIR= + fi +} + +function println +{ + if [ $# -gt 0 ] + then + echo "$PROGNAME: $@" + fi +} + +function fail +{ + println "$@" + cleantmp + exit 1 +} + +function usage +{ + println "$@" + echo "Usage: $PROGNAME " + fail +} + +OTATOOL=`which otatool` +if [ -z "$OTATOOL" ] +then + OTATOOL="`dirname $0`/otatool" + if [ ! -x "$OTATOOL" ] + then + fail "Can't find otatool" + fi +fi + + +if [ $# -ne 2 ] +then + usage +fi + +INFILE="$1" +OUTFILE="$2" + +if [ ! -f "$INFILE" ] +then + fail "$INFILE doesn't exist or isn't a file" +fi + +if [ -z "$OUTFILE" ] +then + usage "Output file not specified" +fi + +if [ -d "$OUTFILE" ] +then + usage "Output file may not be a directory" +fi + +if [ "$INFILE" -ef "$OUTFILE" ] +then + fail "Refusing to use the input file as the output file" +fi + +TMPDIR=`mktemp -d "/tmp/$PROGNAME.XXXXXX"` +if [ $? -ne 0 ] +then + TMPDIR= + fail "Can't create temporary directory" +fi + +ORIGSCRIPT="$TMPDIR/orig" +NEWSCRIPT="$TMPDIR/new" + +"$OTATOOL" --dump-script "$INFILE" | +awk ' + { print } + /^format SYSTEM:$/ { + print "delete_recursive DATA:" + } +' > "$NEWSCRIPT" +if [ $? -ne 0 ] +then + fail "Couldn't modify script" +fi + +"$OTATOOL" --replace-script "$NEWSCRIPT" -o "$OUTFILE" "$INFILE" +if [ $? -ne 0 ] +then + fail "Couldn't replace script" +fi + +cleantmp diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py new file mode 100644 index 0000000..446c09d --- /dev/null +++ b/tools/ota/convert-to-bmp.py @@ -0,0 +1,79 @@ +#!/usr/bin/python2.4 + +"""A simple script to convert asset images to BMP files, that supports +RGBA image.""" + +import struct +import Image +import sys + +infile = sys.argv[1] +outfile = sys.argv[2] + +if not outfile.endswith(".bmp"): + print >> sys.stderr, "Warning: I'm expecting to write BMP files." + +im = Image.open(infile) +if im.mode == 'RGB': + im.save(outfile) +elif im.mode == 'RGBA': + # Python Imaging Library doesn't write RGBA BMP files, so we roll + # our own. + + BMP_HEADER_FMT = ("<" # little-endian + "H" # signature + "L" # file size + "HH" # reserved (set to 0) + "L" # offset to start of bitmap data) + ) + + BITMAPINFO_HEADER_FMT= ("<" # little-endian + "L" # size of this struct + "L" # width + "L" # height + "H" # planes (set to 1) + "H" # bit count + "L" # compression (set to 0 for minui) + "L" # size of image data (0 if uncompressed) + "L" # x pixels per meter (1) + "L" # y pixels per meter (1) + "L" # colors used (0) + "L" # important colors (0) + ) + + fileheadersize = struct.calcsize(BMP_HEADER_FMT) + infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT) + + header = struct.pack(BMP_HEADER_FMT, + 0x4d42, # "BM" in little-endian + (fileheadersize + infoheadersize + + im.size[0] * im.size[1] * 4), + 0, 0, + fileheadersize + infoheadersize) + + info = struct.pack(BITMAPINFO_HEADER_FMT, + infoheadersize, + im.size[0], + im.size[1], + 1, + 32, + 0, + 0, + 1, + 1, + 0, + 0) + + f = open(outfile, "wb") + f.write(header) + f.write(info) + data = im.tostring() + for j in range(im.size[1]-1, -1, -1): # rows bottom-to-top + for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4): + f.write(data[i+2]) # B + f.write(data[i+1]) # G + f.write(data[i+0]) # R + f.write(data[i+3]) # A + f.close() +else: + print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,) diff --git a/tools/ota/make-update-script.c b/tools/ota/make-update-script.c new file mode 100644 index 0000000..0fb7ed0 --- /dev/null +++ b/tools/ota/make-update-script.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "private/android_filesystem_config.h" + +#include +#include +#include +#include +#include +#include + +/* + * Recursively walk the directory tree at /, writing + * script commands to set permissions and create symlinks. + * Assume the contents already have the specified default permissions, + * so only output commands if they need to be changed from the defaults. + * + * Note that permissions are set by fs_config(), which uses a lookup table of + * Android permissions. They are not drawn from the build host filesystem. + */ +static void walk_files( + const char *sysdir, const char *subdir, + unsigned default_uid, unsigned default_gid, + unsigned default_dir_mode, unsigned default_file_mode) { + const char *sep = strcmp(subdir, "") ? "/" : ""; + + char fn[PATH_MAX]; + unsigned dir_uid = 0, dir_gid = 0, dir_mode = 0; + snprintf(fn, PATH_MAX, "system%s%s", sep, subdir); + fs_config(fn, 1, &dir_uid, &dir_gid, &dir_mode); + + snprintf(fn, PATH_MAX, "%s%s%s", sysdir, sep, subdir); + DIR *dir = opendir(fn); + if (dir == NULL) { + perror(fn); + exit(1); + } + + /* + * We can use "set_perm" and "set_perm_recursive" to set file permissions + * (owner, group, and file mode) for individual files and entire subtrees. + * We want to use set_perm_recursive efficiently to avoid setting the + * permissions of every single file in the system image individually. + * + * What we do is recursively set our entire subtree to the permissions + * used by the first file we encounter, and then use "set_perm" to adjust + * the permissions of subsequent files which don't match the first one. + * This is bad if the first file is an outlier, but it generally works. + * Subdirectories can do the same thing recursively if they're different. + */ + + int is_first = 1; + const struct dirent *e; + while ((e = readdir(dir))) { + // Skip over "." and ".." entries + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue; + + if (e->d_type == DT_LNK) { // Symlink + + // Symlinks don't really have permissions, so this is orthogonal. + snprintf(fn, PATH_MAX, "%s/%s%s%s", sysdir, subdir, sep, e->d_name); + int len = readlink(fn, fn, PATH_MAX - 1); + if (len <= 0) { + perror(fn); + exit(1); + } + fn[len] = '\0'; + printf("symlink %s SYSTEM:%s%s%s\n", fn, subdir, sep, e->d_name); + + } else if (e->d_type == DT_DIR) { // Subdirectory + + // Use the parent directory as the model for default permissions. + // We haven't seen a file, so just make up some file defaults. + if (is_first && ( + dir_mode != default_dir_mode || + dir_uid != default_uid || dir_gid != default_gid)) { + default_uid = dir_uid; + default_gid = dir_gid; + default_dir_mode = dir_mode; + default_file_mode = dir_mode & default_file_mode & 0666; + printf("set_perm_recursive %d %d 0%o 0%o SYSTEM:%s\n", + default_uid, default_gid, + default_dir_mode, default_file_mode, + subdir); + } + + is_first = 0; + + // Recursively handle the subdirectory. + // Note, the recursive call handles the directory's own permissions. + snprintf(fn, PATH_MAX, "%s%s%s", subdir, sep, e->d_name); + walk_files(sysdir, fn, + default_uid, default_gid, + default_dir_mode, default_file_mode); + + } else { // Ordinary file + + // Get the file's desired permissions. + unsigned file_uid = 0, file_gid = 0, file_mode = 0; + snprintf(fn, PATH_MAX, "system/%s%s%s", subdir, sep, e->d_name); + fs_config(fn, 0, &file_uid, &file_gid, &file_mode); + + // If this is the first file, its mode gets to become the default. + if (is_first && ( + dir_mode != default_dir_mode || + file_mode != default_file_mode || + dir_uid != default_uid || file_uid != default_uid || + dir_gid != default_gid || file_gid != default_gid)) { + default_uid = dir_uid; + default_gid = dir_gid; + default_dir_mode = dir_mode; + default_file_mode = file_mode; + printf("set_perm_recursive %d %d 0%o 0%o SYSTEM:%s\n", + default_uid, default_gid, + default_dir_mode, default_file_mode, + subdir); + } + + is_first = 0; + + // Otherwise, override this file if it doesn't match the defaults. + if (file_mode != default_file_mode || + file_uid != default_uid || file_gid != default_gid) { + printf("set_perm %d %d 0%o SYSTEM:%s%s%s\n", + file_uid, file_gid, file_mode, + subdir, sep, e->d_name); + } + + } + } + + // Set the directory's permissions directly, if they never got set. + if (dir_mode != default_dir_mode || + dir_uid != default_uid || dir_gid != default_gid) { + printf("set_perm %d %d 0%o SYSTEM:%s\n", + dir_uid, dir_gid, dir_mode, subdir); + } + + closedir(dir); +} + +/* + * Generate the update script (in "Amend", see commands/recovery/commands.c) + * for the complete-reinstall OTA update packages the build system makes. + * + * The generated script makes a variety of sanity checks about the device, + * erases and reinstalls system files, and sets file permissions appropriately. + */ +int main(int argc, char *argv[]) { + if (argc != 3) { + fprintf(stderr, "usage: %s systemdir android-info.txt >update-script\n", + argv[0]); + return 2; + } + + // ensure basic recovery script language compatibility + printf("assert compatible_with(\"0.2\") == \"true\"\n"); + + // if known, make sure the device name is correct + const char *device = getenv("TARGET_PRODUCT"); + if (device != NULL) { + printf("assert getprop(\"ro.product.device\") == \"%s\" || " + "getprop(\"ro.build.product\") == \"%s\"\n", device, device); + } + + // scan android-info.txt to enforce compatibility with the target system + FILE *fp = fopen(argv[2], "r"); + if (fp == NULL) { + perror(argv[2]); + return 1; + } + + // The lines we're looking for look like: + // version-bootloader=x.yy.zzzz + // or: + // require version-bootloader=x.yy.zzzz + char line[256]; + while (fgets(line, sizeof(line), fp)) { + const char *name = strtok(line, "="), *value = strtok(NULL, "\n"); + if (value != NULL && + (!strcmp(name, "version-bootloader") || + !strcmp(name, "require version-bootloader"))) { + printf("assert getprop(\"ro.bootloader\") == \"%s\"\n", value); + } + // We also used to check version-baseband, but we update radio.img + // ourselves, so there's no need. + } + + // erase the boot sector first, so if the update gets interrupted, + // the system will reboot into the recovery partition and start over. + printf("format BOOT:\n"); + + // write the radio image (actually just loads it into RAM for now) + printf("show_progress 0.1 0\n"); + printf("write_radio_image PACKAGE:radio.img\n"); + + // erase and reinstall the system image + printf("show_progress 0.5 0\n"); + printf("format SYSTEM:\n"); + printf("copy_dir PACKAGE:system SYSTEM:\n"); + + // walk the files in the system image, set their permissions, etc. + // use -1 for default values to force permissions to be set explicitly. + walk_files(argv[1], "", -1, -1, -1, -1); + + // as the last step, write the boot sector. + printf("show_progress 0.2 0\n"); + printf("write_raw_image PACKAGE:boot.img BOOT:\n"); + + // after the end of the script, the radio will be written to cache + // leave some space in the progress bar for this operation + printf("show_progress 0.2 10\n"); + return 0; +} diff --git a/tools/ota/otatool b/tools/ota/otatool new file mode 100755 index 0000000..4b02629 --- /dev/null +++ b/tools/ota/otatool @@ -0,0 +1,225 @@ +#!/bin/bash +# +# Copyright (C) 2007 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +PROGNAME=`basename "$0"` + +INSTALL_SCRIPT_NAME=META-INF/com/android/update-script + +function cleantmp +{ + if [ ! -z "$TMPDIR" ] + then + rm -rf "$TMPDIR" + TMPDIR= + fi +} + +function println +{ + if [ $# -gt 0 ] + then + echo "$PROGNAME: $@" + fi +} + +function fail +{ + println "$@" + cleantmp + exit 1 +} + +function usage +{ + println "$@" + echo "Usage: $PROGNAME [command-options] " + echo " Where is one of:" + echo " --dump" + echo " Dump a description of the ota file" + echo " --dump-script" + echo " Dumps the install script to standard out" + echo " --append-script -o|--output " + echo " Append the contents of to the install script" + echo " --replace-script -o|--output " + echo " Replace the install script with the contents of " + fail +} + +if [ $# -lt 2 ] +then + usage +fi +CMD="$1" +shift +if [ "$CMD" = --dump ] +then + CMD_DUMP=1 + UNPACK_FILE=1 +elif [ "$CMD" = --dump-script ] +then + CMD_DUMP_SCRIPT=1 +elif [ "$CMD" = --append-script ] +then + CMD_APPEND_SCRIPT=1 + SCRIPT_FILE=$1 + shift + NEEDS_SCRIPT_FILE=1 + NEEDS_OUTPUT=1 +elif [ "$CMD" = --replace-script ] +then + CMD_REPLACE_SCRIPT=1 + SCRIPT_FILE=$1 + shift + NEEDS_SCRIPT_FILE=1 + NEEDS_OUTPUT=1 +else + usage "Unknown command $CMD" +fi + +if [ ! -z "$NEED_SCRIPT_FILE" ] +then + if [ -z "$SCRIPT_FILE" -o ! -f "$SCRIPT_FILE" ] + then + usage "$CMD requires a valid script file" + fi +fi + +if [ ! -z "$NEEDS_OUTPUT" ] +then + if [ "x$1" != "x-o" -a "x$1" != "x--output" ] + then + usage "$CMD requires \"-o \" or \"--output \"" + fi + shift + + OUTFILE="$1" + shift + if [ -z "$OUTFILE" ] + then + usage "$CMD requires \"-o \" or \"--output \"" + fi + if [ -d "$OUTFILE" ] + then + fail "Output file \"$OUTFILE\" is a directory" + fi +fi + +FILE="$1" +if [ ! -f "$FILE" ] +then + fail "$FILE doesn't exist or isn't a file" +fi +if [ ! -z "$OUTFILE" -a "$FILE" -ef "$OUTFILE" ] +then + fail "Refusing to use the input file as the output file" +fi + +if [ $CMD_DUMP_SCRIPT ] +then + unzip -p "$FILE" "$INSTALL_SCRIPT_NAME" + exit 0 +fi + +# Create a temporary directory for scratch files. +# +TMPDIR=`mktemp -d /tmp/$PROGNAME.XXXXXX` +if [ $? -ne 0 ] +then + TMPDIR= + fail "Can't create temporary directory" +fi + + +if [ $UNPACK_FILE ] +then + ROOTDIR="$TMPDIR/root" + mkdir -p "$ROOTDIR" + + println "Unpacking `basename $FILE`..." + + unzip -q -d "$ROOTDIR" "$FILE" + if [ $? -ne 0 ] + then + fail "Couldn't unpack $FILE" + fi +fi + + +if [ $CMD_DUMP ] +then + function dumpfile + { + echo "BEGIN `basename $1`" + cat "$1" | sed -e 's/^/ /' + echo "END `basename $1`" + } + + echo Contents of root: + ls -1 "$ROOTDIR" | sed -e 's/^/ /' + echo + echo Contents of META-INF: + (cd "$ROOTDIR" && find META-INF -type f) | sed -e 's/^/ /' + + echo + dumpfile "$ROOTDIR/META-INF/MANIFEST.MF" + echo + dumpfile "$ROOTDIR/$INSTALL_SCRIPT_NAME" + echo + dumpfile "$ROOTDIR/android-product.txt" + echo + dumpfile "$ROOTDIR/system/build.prop" +fi + +if [ $CMD_APPEND_SCRIPT ] +then + TMPSCRIPT="$TMPDIR/script" + NEWSCRIPT="$TMPDIR/$INSTALL_SCRIPT_NAME" + unzip -p "$FILE" "$INSTALL_SCRIPT_NAME" > "$TMPSCRIPT" + if [ $? -ne 0 ] + then + fail "Couldn't extract $INSTALL_SCRIPT_NAME from $FILE" + fi + mkdir -p `dirname "$NEWSCRIPT"` + cat "$TMPSCRIPT" "$SCRIPT_FILE" > "$NEWSCRIPT" + + OVERWRITE_SCRIPT=1 +fi + +if [ $CMD_REPLACE_SCRIPT ] +then + NEWSCRIPT="$TMPDIR/$INSTALL_SCRIPT_NAME" + mkdir -p `dirname "$NEWSCRIPT"` + cp "$SCRIPT_FILE" "$NEWSCRIPT" + + OVERWRITE_SCRIPT=1 +fi + +if [ $OVERWRITE_SCRIPT ] +then + cp "$FILE" "$TMPDIR/outfile.zip" + (cd "$TMPDIR" && zip -qu outfile.zip "$INSTALL_SCRIPT_NAME") + if [ $? -ne 0 ] + then + fail "Couldn't add new $INSTALL_SCRIPT_NAME to output file" + fi + + rm -f "$OUTFILE" + mkdir -p `dirname "$OUTFILE"` + mv "$TMPDIR/outfile.zip" "$OUTFILE" +fi + +cleantmp -- cgit v1.1