Initial tool for OBB manipulation

Add "obbtool" host command for adding, removing, and querying Opaque
Binary Blob (OBB) information from a file.

Change-Id: Id2ac41e687ad2a500c362616d6738a8ae7e8f5c3
diff --git a/include/utils/ObbFile.h b/include/utils/ObbFile.h
index 075927c..d2ca82e 100644
--- a/include/utils/ObbFile.h
+++ b/include/utils/ObbFile.h
@@ -35,6 +35,8 @@
     bool readFrom(int fd);
     bool writeTo(const char* filename);
     bool writeTo(int fd);
+    bool removeFrom(const char* filename);
+    bool removeFrom(int fd);
 
     const char* getFileName() const {
         return mFileName;
@@ -78,6 +80,8 @@
 
     size_t mFileSize;
 
+    size_t mFooterStart;
+
     unsigned char* mReadBuf;
 
     bool parseObbFile(int fd);
diff --git a/libs/utils/ObbFile.cpp b/libs/utils/ObbFile.cpp
index fe49300..adedf0c 100644
--- a/libs/utils/ObbFile.cpp
+++ b/libs/utils/ObbFile.cpp
@@ -156,9 +156,9 @@
             return false;
         }
 
-        if (footerSize < kFooterMinSize) {
-            LOGW("claimed footer size is too small (%08zx; minimum size is 0x%x)\n",
-                    footerSize, kFooterMinSize);
+        if (footerSize < (kFooterMinSize - kFooterTagSize)) {
+            LOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n",
+                    footerSize, kFooterMinSize - kFooterTagSize);
             return false;
         }
     }
@@ -169,6 +169,8 @@
         return false;
     }
 
+    mFooterStart = fileOffset;
+
     char* scanBuf = (char*)malloc(footerSize);
     if (scanBuf == NULL) {
         LOGW("couldn't allocate scanBuf: %s\n", strerror(errno));
@@ -293,4 +295,38 @@
     return true;
 }
 
+bool ObbFile::removeFrom(const char* filename)
+{
+    int fd;
+    bool success = false;
+
+    fd = ::open(filename, O_RDWR);
+    if (fd < 0) {
+        goto out;
+    }
+    success = removeFrom(fd);
+    close(fd);
+
+out:
+    if (!success) {
+        LOGW("failed to remove signature from %s: %s\n", filename, strerror(errno));
+    }
+    return success;
+}
+
+bool ObbFile::removeFrom(int fd)
+{
+    if (fd < 0) {
+        return false;
+    }
+
+    if (!readFrom(fd)) {
+        return false;
+    }
+
+    ftruncate(fd, mFooterStart);
+
+    return true;
+}
+
 }
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
new file mode 100644
index 0000000..b02c1cb
--- /dev/null
+++ b/tools/obbtool/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright 2010 The Android Open Source Project
+#
+# Opaque Binary Blob (OBB) Tool
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Main.cpp
+
+#LOCAL_C_INCLUDES +=
+
+LOCAL_STATIC_LIBRARIES := \
+	libutils \
+	libcutils
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lpthread
+endif
+
+LOCAL_MODULE := obbtool
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
new file mode 100644
index 0000000..2a9bf04
--- /dev/null
+++ b/tools/obbtool/Main.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010 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 <utils/ObbFile.h>
+#include <utils/String8.h>
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace android;
+
+static const char* gProgName = "obbtool";
+static const char* gProgVersion = "1.0";
+
+static int wantUsage = 0;
+static int wantVersion = 0;
+
+#define ADD_OPTS "n:v:f:c:"
+static const struct option longopts[] = {
+    {"help",       no_argument, &wantUsage,   1},
+    {"version",    no_argument, &wantVersion, 1},
+
+    /* Args for "add" */
+    {"name",       required_argument, NULL, 'n'},
+    {"version",    required_argument, NULL, 'v'},
+    {"filesystem", required_argument, NULL, 'f'},
+    {"crypto",     required_argument, NULL, 'c'},
+
+    {NULL, 0, NULL, '\0'}
+};
+
+struct package_info_t {
+    char* packageName;
+    int packageVersion;
+    char* filesystem;
+    char* crypto;
+};
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+    fprintf(stderr, "Opaque Binary Blob (OBB) Tool\n\n");
+    fprintf(stderr, "Usage:\n");
+    fprintf(stderr,
+        " %s a[dd] [ OPTIONS ] FILENAME\n"
+        "   Adds an OBB signature to the file.\n\n", gProgName);
+    fprintf(stderr,
+        " %s r[emove] FILENAME\n"
+        "   Removes the OBB signature from the file.\n\n", gProgName);
+    fprintf(stderr,
+        " %s i[nfo] FILENAME\n"
+        "   Prints the OBB signature information of a file.\n\n", gProgName);
+}
+
+void doAdd(const char* filename, struct package_info_t* info) {
+    ObbFile *obb = new ObbFile();
+    if (obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename);
+        return;
+    }
+
+    obb->setPackageName(String8(info->packageName));
+    obb->setVersion(info->packageVersion);
+
+    if (!obb->writeTo(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n",
+                filename, strerror(errno));
+        return;
+    }
+
+    fprintf(stderr, "OBB signature successfully written\n");
+}
+
+void doRemove(const char* filename) {
+    ObbFile *obb = new ObbFile();
+    if (!obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: no OBB signature present\n", filename);
+        return;
+    }
+
+    if (!obb->removeFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't remove OBB signature\n", filename);
+        return;
+    }
+
+    fprintf(stderr, "OBB signature successfully removed\n");
+}
+
+void doInfo(const char* filename) {
+    ObbFile *obb = new ObbFile();
+    if (!obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't read OBB signature\n", filename);
+        return;
+    }
+
+    printf("OBB info for '%s':\n", filename);
+    printf("Package name: %s\n", obb->getPackageName().string());
+    printf("     Version: %d\n", obb->getVersion());
+}
+
+/*
+ * Parse args.
+ */
+int main(int argc, char* const argv[])
+{
+    const char *prog = argv[0];
+    struct options *options;
+    int opt;
+    int option_index = 0;
+    struct package_info_t package_info;
+
+    int result = 1;    // pessimistically assume an error.
+
+    if (argc < 2) {
+        wantUsage = 1;
+        goto bail;
+    }
+
+    while ((opt = getopt_long(argc, argv, ADD_OPTS, longopts, &option_index)) != -1) {
+        switch (opt) {
+        case 0:
+            if (longopts[option_index].flag)
+                break;
+            fprintf(stderr, "'%s' requires an argument\n", longopts[option_index].name);
+            wantUsage = 1;
+            goto bail;
+        case 'n':
+            package_info.packageName = optarg;
+            break;
+        case 'v':
+            char *end;
+            package_info.packageVersion = strtol(optarg, &end, 10);
+            if (*optarg == '\0' || *end != '\0') {
+                fprintf(stderr, "ERROR: invalid version; should be integer!\n\n");
+                wantUsage = 1;
+                goto bail;
+            }
+            break;
+        case 'f':
+            package_info.filesystem = optarg;
+            break;
+        case 'c':
+            package_info.crypto = optarg;
+            break;
+        case '?':
+            wantUsage = 1;
+            goto bail;
+        }
+    }
+
+    if (wantVersion) {
+        fprintf(stderr, "%s %s\n", gProgName, gProgVersion);
+    }
+
+    if (wantUsage) {
+        goto bail;
+    }
+
+#define CHECK_OP(name) \
+    if (strncmp(op, name, opsize)) { \
+        fprintf(stderr, "ERROR: unknown function '%s'!\n\n", op); \
+        wantUsage = 1; \
+        goto bail; \
+    }
+
+    if (optind < argc) {
+        const char* op = argv[optind++];
+        const int opsize = strlen(op);
+
+        if (optind >= argc) {
+            fprintf(stderr, "ERROR: filename required!\n\n");
+            wantUsage = 1;
+            goto bail;
+        }
+
+        const char* filename = argv[optind++];
+
+        switch (op[0]) {
+        case 'a':
+            CHECK_OP("add");
+            if (package_info.packageName == NULL) {
+                fprintf(stderr, "ERROR: arguments required 'packageName' and 'version'\n");
+                goto bail;
+            }
+            doAdd(filename, &package_info);
+            break;
+        case 'r':
+            CHECK_OP("remove");
+            doRemove(filename);
+            break;
+        case 'i':
+            CHECK_OP("info");
+            doInfo(filename);
+            break;
+        default:
+            fprintf(stderr, "ERROR: unknown command '%s'!\n\n", op);
+            wantUsage = 1;
+            goto bail;
+        }
+    }
+
+bail:
+    if (wantUsage) {
+        usage();
+        result = 2;
+    }
+
+    return result;
+}