From f1bde365724b63b0f2b97304167fe60da9fd44cd Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 22 Apr 2011 11:26:03 -0700 Subject: [PATCH 01/16] fix bug where the wrong SD Card block gets partitioned. rely on the ftsab to glean the sdcard mmcblk Change-Id: Idee8d3e147ba91c45a9f9fe91126c7b5a19ee38d --- utilities/sdparted | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/utilities/sdparted b/utilities/sdparted index 13e62f4..74e24a6 100755 --- a/utilities/sdparted +++ b/utilities/sdparted @@ -546,12 +546,29 @@ TTISMSDOS= SDSIZE= SDSIZEMB= -if [ -z "$SDPATH" ] +SDINFO=$(cat /etc/fstab | grep /sdcard | awk '{print $1}') +if [ -L "$SDINFO" ] then - SDPATH="/dev/block/mmcblk0" + SDPATH=$(ls -l $SDINFO | awk '{print $11}') else - echo Found SDPATH=$SDPATH + SDPATH=$SDINFO fi +# we may now have an SDPATH, let's make sure its on mmcblkX or mmcblkXp1 +CHECK_SDPATH1=$(echo $SDPATH | grep mmcblk.$) +CHECK_SDPATH2=$(echo $SDPATH | grep mmcblk.p1$) +if [ -z "$CHECK_SDPATH1" ] +then + if [ -z "$CHECK_SDPATH2" ] + then + echo fail1 + unset SDPATH + else + LEN=${#SDPATH} + BLKLEN=$(expr $LEN - 2) + SDPATH=${SDPATH:0:$BLKLEN} + fi +fi + FATSIZE= FATTYPE="fat32" From f573510b501b65b1be8003f6e9d900df01530f1d Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 22 Apr 2011 12:12:32 -0700 Subject: [PATCH 02/16] bml fixes Change-Id: I442ef3c155bab36db578ca5735215aedda353c29 --- Android.mk | 2 +- flashutils/flashutils.c | 5 +++-- roots.c | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Android.mk b/Android.mk index 3f9f1e8..214f031 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.4 +RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.5 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c index 0b1467e..7504e4a 100644 --- a/flashutils/flashutils.c +++ b/flashutils/flashutils.c @@ -10,7 +10,7 @@ int the_flash_type = UNKNOWN; int device_flash_type() { if (the_flash_type == UNKNOWN) { - if (access("/dev/block/bml1", F_OK) == 0) { + if (access("/dev/block/bml7", F_OK) == 0) { the_flash_type = BML; } else if (access("/proc/emmc", F_OK) == 0) { the_flash_type = MMC; @@ -78,7 +78,7 @@ static int detect_partition(const char *partition) type = MMC; else if (strstr(partition, "/dev/block/bml") != NULL) type = BML; - + return type; } int restore_raw_partition(const char *partition, const char *filename) @@ -107,6 +107,7 @@ int backup_raw_partition(const char *partition, const char *filename) case BML: return cmd_bml_backup_raw_partition(partition, filename); default: + printf("unable to detect device type"); return -1; } } diff --git a/roots.c b/roots.c index a3c4677..a33190a 100644 --- a/roots.c +++ b/roots.c @@ -153,7 +153,6 @@ int try_mount(const char* device, const char* mount_point, const char* fs_type, else { char mount_cmd[PATH_MAX]; sprintf(mount_cmd, "mount -t %s -o%s %s %s", fs_type, fs_options, device, mount_point); - LOGE("%s\n", mount_cmd); ret = __system(mount_cmd); } if (ret == 0) From 1f1a274ecc9b875bec556c5a92a38f13c2ca7014 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sat, 23 Apr 2011 16:35:19 -0700 Subject: [PATCH 03/16] 3.0.2.7 Change-Id: I43e32be031bf7803ca3ef3c8e77e7955218fd798 --- Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android.mk b/Android.mk index 214f031..c4bfe02 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.5 +RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.7 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) From 7364e82a5fc092ec246c4f1a2b70a2d58544f378 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sat, 23 Apr 2011 19:01:16 -0700 Subject: [PATCH 04/16] format .android_secure on RM data wipe. Change-Id: If417334263f577f0f1ca9a8c5bf6b63582dbb09f --- edifyscripting.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edifyscripting.c b/edifyscripting.c index 83f089f..89554d7 100644 --- a/edifyscripting.c +++ b/edifyscripting.c @@ -141,6 +141,10 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { free(path); return StringValue(strdup("")); } + if (0 != format_volume("/sdcard/.android_secure")) { + free(path); + return StringValue(strdup("")); + } } done: From 6a9739657298c49f28ca881d733d1a4870e20540 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sun, 24 Apr 2011 20:16:39 -0700 Subject: [PATCH 05/16] 3.0.2.8 Change-Id: Icce5373f73f349c07bad29763803ba919cf64dc1 --- Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android.mk b/Android.mk index c4bfe02..f70cedc 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.7 +RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.8 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) From 9c4c8e2c6675861e9983a1e40adb7f082de691fb Mon Sep 17 00:00:00 2001 From: codeworkx Date: Mon, 25 Apr 2011 11:16:41 -0700 Subject: [PATCH 06/16] bml_over_mtd: Take care of bad blocks on "boot" partition for Samsung Galaxy S Phones. The Samsung Galaxy S bootloader apparently expects the kernel to be flashed to BML-managed flash - bad erase blocks will be mapped from a reservoir area. CWM however just skips bad blocks, the usual procedure for mtd-accessed flash. Consequently, the bootloader sees a corrupt zImage, and will usually crash when the kernel initializes. This of course will only happen when the "boot" partition has bad blocks. This patch was written by "eifert" and adds a tool called "bml_over_mtd" for flashing boot images which takes care of bad blocks and maps them to a reservoir area, like BML does. Change-Id: If570717a19b879d47d70d937a0751cd85853eacd --- Android.mk | 11 +- flashutils/Android.mk | 1 - mtdutils/Android.mk | 29 +- mtdutils/bml_over_mtd.c | 798 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 830 insertions(+), 9 deletions(-) create mode 100644 mtdutils/bml_over_mtd.c diff --git a/Android.mk b/Android.mk index f70cedc..2d7db14 100644 --- a/Android.mk +++ b/Android.mk @@ -16,8 +16,8 @@ LOCAL_SRC_FILES := \ verifier.c \ encryptedfs_provisioning.c \ mounts.c \ - extendedcommands.c \ - nandroid.c \ + extendedcommands.c \ + nandroid.c \ reboot.c \ edifyscripting.c \ setprop.c @@ -57,7 +57,12 @@ LOCAL_STATIC_LIBRARIES += libext4_utils libz LOCAL_STATIC_LIBRARIES += libminzip libunz libmincrypt LOCAL_STATIC_LIBRARIES += libedify libbusybox libclearsilverregex libmkyaffs2image libunyaffs liberase_image libdump_image libflash_image -LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils + +LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils + +ifeq ($(BOARD_USES_BML_OVER_MTD),true) +LOCAL_STATIC_LIBRARIES += libbml_over_mtd +endif LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils LOCAL_STATIC_LIBRARIES += libstdc++ libc diff --git a/flashutils/Android.mk b/flashutils/Android.mk index 324480a..d503ce7 100644 --- a/flashutils/Android.mk +++ b/flashutils/Android.mk @@ -57,7 +57,6 @@ LOCAL_MODULE_TAGS := eng LOCAL_CFLAGS += -Dmain=erase_image_main include $(BUILD_STATIC_LIBRARY) - include $(CLEAR_VARS) LOCAL_SRC_FILES := dump_image.c LOCAL_MODULE := utility_dump_image diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk index 90e97fd..82284ba 100644 --- a/mtdutils/Android.mk +++ b/mtdutils/Android.mk @@ -2,15 +2,34 @@ ifneq ($(TARGET_SIMULATOR),true) ifeq ($(TARGET_ARCH),arm) LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - mtdutils.c - +LOCAL_SRC_FILES := mtdutils.c LOCAL_MODULE := libmtdutils - include $(BUILD_STATIC_LIBRARY) +ifeq ($(BOARD_USES_BML_OVER_MTD),true) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_C_INCLUDES += bootable/recovery/mtdutils +LOCAL_MODULE := libbml_over_mtd +LOCAL_MODULE_TAGS := eng +LOCAL_CFLAGS += -Dmain=bml_over_mtd_main +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_MODULE := bml_over_mtd +LOCAL_MODULE_TAGS := eng +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := bml_over_mtd +LOCAL_C_INCLUDES += bootable/recovery/mtdutils +LOCAL_STATIC_LIBRARIES := libmtdutils libcutils libc +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) +endif endif # TARGET_ARCH == arm endif # !TARGET_SIMULATOR diff --git a/mtdutils/bml_over_mtd.c b/mtdutils/bml_over_mtd.c new file mode 100644 index 0000000..c401792 --- /dev/null +++ b/mtdutils/bml_over_mtd.c @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2011 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils/log.h" + +#include + +#include "mtdutils.h" + +typedef struct BmlOverMtdReadContext { + const MtdPartition *partition; + char *buffer; + size_t consumed; + int fd; +} BmlOverMtdReadContext; + +typedef struct BmlOverMtdWriteContext { + const MtdPartition *partition; + char *buffer; + size_t stored; + int fd; + + off_t* bad_block_offsets; + int bad_block_alloc; + int bad_block_count; +} BmlOverMtdWriteContext; + + +static BmlOverMtdReadContext *bml_over_mtd_read_partition(const MtdPartition *partition) +{ + BmlOverMtdReadContext *ctx = (BmlOverMtdReadContext*) malloc(sizeof(BmlOverMtdReadContext)); + if (ctx == NULL) return NULL; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDONLY); + if (ctx->fd < 0) { + free(ctx); + free(ctx->buffer); + return NULL; + } + + ctx->partition = partition; + ctx->consumed = partition->erase_size; + return ctx; +} + +static void bml_over_mtd_read_close(BmlOverMtdReadContext *ctx) +{ + close(ctx->fd); + free(ctx->buffer); + free(ctx); +} + +static BmlOverMtdWriteContext *bml_over_mtd_write_partition(const MtdPartition *partition) +{ + BmlOverMtdWriteContext *ctx = (BmlOverMtdWriteContext*) malloc(sizeof(BmlOverMtdWriteContext)); + if (ctx == NULL) return NULL; + + ctx->bad_block_offsets = NULL; + ctx->bad_block_alloc = 0; + ctx->bad_block_count = 0; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDWR); + if (ctx->fd < 0) { + free(ctx->buffer); + free(ctx); + return NULL; + } + + ctx->partition = partition; + ctx->stored = 0; + return ctx; +} + +static int bml_over_mtd_write_close(BmlOverMtdWriteContext *ctx) +{ + int r = 0; + if (close(ctx->fd)) r = -1; + free(ctx->bad_block_offsets); + free(ctx->buffer); + free(ctx); + return r; +} + + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "bml_over_mtd" + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) + +#define EXIT_CODE_BAD_BLOCKS 15 + +static int die(const char *msg, ...) { + int err = errno; + va_list args; + va_start(args, msg); + char buf[1024]; + vsnprintf(buf, sizeof(buf), msg, args); + va_end(args); + + if (err != 0) { + strlcat(buf, ": ", sizeof(buf)); + strlcat(buf, strerror(err), sizeof(buf)); + } + + fprintf(stderr, "%s\n", buf); + return 1; +} + +static unsigned short* CreateEmptyBlockMapping(const MtdPartition* pSrcPart) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + return pMapping; +} + +static const unsigned short* CreateBlockMapping(const MtdPartition* pSrcPart, int srcPartStartBlock, + const MtdPartition *pReservoirPart, int reservoirPartStartBlock) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + + size_t total, erase, write; + if (mtd_partition_info(pReservoirPart, &total, &erase, &write) != 0) + { + fprintf(stderr, "Failed to access reservoir partition.\n"); + free(pMapping); + return NULL; + } + + if (erase != srcErase || write != srcWrite) + { + fprintf(stderr, "Source partition and reservoir partition differ in size properties.\n"); + free(pMapping); + return NULL; + } + + printf("Partition info: Total %d, Erase %d, write %d\n", total, erase, write); + + BmlOverMtdReadContext *readctx = bml_over_mtd_read_partition(pReservoirPart); + if (readctx == NULL) + { + fprintf(stderr, "Failed to open reservoir partition for reading.\n"); + free(pMapping); + return NULL; + } + + if (total < erase || total > INT_MAX) + { + fprintf(stderr, "Unsuitable reservoir partition properties.\n"); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + + int foundMappingTable = 0; + + int currOffset = total; //Offset *behind* the last byte + while (currOffset > 0) + { + currOffset -= erase; + loff_t pos = lseek64(readctx->fd, currOffset, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d in reservoir area, skipping.\n", currOffset/erase); + continue; + } + ssize_t readBytes = read(readctx->fd, readctx->buffer, erase); + if (readBytes != (ssize_t)erase) + { + fprintf(stderr, "Failed to read good block in reservoir area (%s).\n", + strerror(errno)); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + if (readBytes >= 0x2000) + { + char* buf = readctx->buffer; + if (buf[0]=='U' && buf[1]=='P' && buf[2]=='C' && buf[3]=='H') + { + printf ("Found mapping block mark at 0x%x (block %d).\n", currOffset, currOffset/erase); + + unsigned short* mappings = (unsigned short*) &buf[0x1000]; + if (mappings[0]==0 && mappings[1]==0xffff) + { + printf("Found start of mapping table.\n"); + foundMappingTable = 1; + //Skip first entry (dummy) + unsigned short* mappingEntry = mappings + 2; + while (mappingEntry - mappings < 100 + && mappingEntry[0] != 0xffff) + { + unsigned short rawSrcBlk = mappingEntry[0]; + unsigned short rawDstBlk = mappingEntry[1]; + + printf("Found raw block mapping %d -> %d\n", rawSrcBlk, + rawDstBlk); + + unsigned int srcAbsoluteStartAddress = srcPartStartBlock * erase; + unsigned int resAbsoluteStartAddress = reservoirPartStartBlock * erase; + + int reservoirLastBlock = reservoirPartStartBlock + numSrcBlocks - 1; + if (rawDstBlk < reservoirPartStartBlock + || rawDstBlk*erase >= resAbsoluteStartAddress+currOffset) + { + fprintf(stderr, "Mapped block not within reasonable reservoir area.\n"); + foundMappingTable = 0; + break; + } + + int srcLastBlock = srcPartStartBlock + numSrcBlocks - 1; + if (rawSrcBlk >= srcPartStartBlock && rawSrcBlk <= srcLastBlock) + { + + unsigned short relSrcBlk = rawSrcBlk - srcPartStartBlock; + unsigned short relDstBlk = rawDstBlk - reservoirPartStartBlock; + printf("Partition relative block mapping %d -> %d\n",relSrcBlk, relDstBlk); + + printf("Absolute mapped start addresses 0x%x -> 0x%x\n", + srcAbsoluteStartAddress+relSrcBlk*erase, + resAbsoluteStartAddress+relDstBlk*erase); + printf("Partition relative mapped start addresses 0x%x -> 0x%x\n", + relSrcBlk*erase, relDstBlk*erase); + + //Set mapping entry. For duplicate entries, later entries replace former ones. + //*Assumption*: Bad blocks in reservoir area will not be mapped themselves in + //the mapping table. User partition blocks will not be mapped to bad blocks + //(only) in the reservoir area. This has to be confirmed on a wider range of + //devices. + pMapping[relSrcBlk] = relDstBlk; + + } + mappingEntry+=2; + } + break; //We found the mapping table, no need to search further + } + + + } + } + + } + bml_over_mtd_read_close(readctx); + + if (foundMappingTable == 0) + { + fprintf(stderr, "Cannot find mapping table in reservoir partition.\n"); + free(pMapping); + return NULL; + } + + //Consistency and validity check + int mappingValid = 1; + readctx = bml_over_mtd_read_partition(pSrcPart); + if (readctx == NULL) + { + fprintf(stderr, "Cannot open source partition for reading.\n"); + free(pMapping); + return NULL; + } + int currBlock = 0; + for (;currBlock < numSrcBlocks; ++currBlock) + { + loff_t pos = lseek64(readctx->fd, currBlock*erase, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + if (pMapping[currBlock]!=0xffff) + { + fprintf(stderr, "Consistency error: Good block has mapping entry %d -> %d\n", currBlock, pMapping[currBlock]); + mappingValid = 0; + } + } else + { + //Bad block! + if (pMapping[currBlock]==0xffff) + { + fprintf(stderr, "Consistency error: Bad block has no mapping entry \n"); + mappingValid = 0; + } else + { + BmlOverMtdReadContext* reservoirReadCtx = bml_over_mtd_read_partition(pReservoirPart); + if (reservoirReadCtx == 0) + { + fprintf(stderr, "Reservoir partition cannot be opened for reading in consistency check.\n"); + mappingValid = 0; + } else + { + pos = lseek64(reservoirReadCtx->fd, pMapping[currBlock]*erase, SEEK_SET); + mgbb = ioctl(reservoirReadCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + printf("Bad block has properly mapped reservoir block %d -> %d\n",currBlock, pMapping[currBlock]); + } + else + { + fprintf(stderr, "Consistency error: Mapped block is bad, too. (%d -> %d)\n",currBlock, pMapping[currBlock]); + mappingValid = 0; + } + + } + bml_over_mtd_read_close(reservoirReadCtx); + } + + } + + } + bml_over_mtd_read_close(readctx); + + + if (!mappingValid) + { + free(pMapping); + return NULL; + } + + return pMapping; +} + +static void ReleaseBlockMapping(const unsigned short* blockMapping) +{ + free((void*)blockMapping); +} + +static int dump_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdReadContext* pSrcRead = bml_over_mtd_read_partition(pSrcPart); + if (pSrcRead == NULL) + { + close(fd); + fprintf(stderr, "dump_bml_partition: Error opening src part for reading.\n"); + return -1; + } + + BmlOverMtdReadContext* pResRead = bml_over_mtd_read_partition(pReservoirPart); + if (pResRead == NULL) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + fprintf(stderr, "dump_bml_partition: Error opening reservoir part for reading.\n"); + return -1; + } + + + int numBlocks = pSrcPart->size / pSrcPart->erase_size; + int currblock = 0; + for (;currblock < numBlocks; ++currblock) + { + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcRead->fd; + if (lseek64(pSrcRead->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResRead->fd; + if (lseek64(pResRead->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(srcFd, pSrcRead->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: reading partition failed\n"); + return -1; + } + blockBytesRead += len; + } + + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { + ssize_t len = write(fd, pSrcRead->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: writing partition dump file failed\n"); + return -1; + } + blockBytesWritten += len; + } + + } + + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + + if (close(fd)) { + unlink(filename); + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static ssize_t bml_over_mtd_write_block(int fd, ssize_t erase_size, char* data) +{ + off_t pos = lseek(fd, 0, SEEK_CUR); + if (pos == (off_t) -1) return -1; + + ssize_t size = erase_size; + loff_t bpos = pos; + int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); + if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { + fprintf(stderr, + "Mapping failure: Trying to write bad block at 0x%08lx (ret %d errno %d)\n", + pos, ret, errno); + return -1; + } + + struct erase_info_user erase_info; + erase_info.start = pos; + erase_info.length = size; + int retry; + for (retry = 0; retry < 2; ++retry) { + if (ioctl(fd, MEMERASE, &erase_info) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (lseek(fd, pos, SEEK_SET) != pos || + write(fd, data, size) != size) { + fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n", + pos, strerror(errno)); + } + + char verify[size]; + if (lseek(fd, pos, SEEK_SET) != pos || + read(fd, verify, size) != size) { + fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (memcmp(data, verify, size) != 0) { + fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + + if (retry > 0) { + fprintf(stderr, "mtd: wrote block after %d retries\n", retry); + } + fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos); + return size; // Success! + } + + + fprintf(stderr, "mtd: Block at %llx could not be properly written.\n", pos); + // Ran out of space on the device + errno = ENOSPC; + return -1; +} + +static int flash_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdWriteContext* pSrcWrite = bml_over_mtd_write_partition(pSrcPart); + if (pSrcWrite == NULL) + { + close(fd); + fprintf(stderr, "flash_bml_partition: Error opening src part for writing.\n"); + return -1; + } + +#ifdef DUMMY_WRITING + close(pSrcWrite->fd); + pSrcWrite->fd = open("/sdcard/srcPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + BmlOverMtdWriteContext* pResWrite = bml_over_mtd_write_partition(pReservoirPart); + if (pResWrite == NULL) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + fprintf(stderr, "flash_bml_partition: Error opening reservoir part for writing.\n"); + return -1; + } +#ifdef DUMMY_WRITING + close(pResWrite->fd); + pResWrite->fd = open("/sdcard/resPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + struct stat fileStat; + if (fstat(fd, &fileStat) != 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Failed to stat source file.\n"); + return -1; + + } + if (fileStat.st_size > pSrcPart->size) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Source file too large for target partition.\n"); + return -1; + } + + int numBlocks = (fileStat.st_size + pSrcPart->erase_size - 1) / pSrcPart->erase_size; + int currblock; + for (currblock = 0 ;currblock < numBlocks; ++currblock) + { + memset(pSrcWrite->buffer, 0xFF, pSrcPart->erase_size); + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(fd, pSrcWrite->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len < 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: read source file failed\n"); + return -1; + } + if (len == 0) + { + //End of file + break; + } + + blockBytesRead += len; + } + + + + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcWrite->fd; + if (lseek64(pSrcWrite->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResWrite->fd; + if (lseek64(pResWrite->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { +#ifdef DUMMY_WRITING + ssize_t len = write(srcFd, pSrcWrite->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); +#else + ssize_t len = bml_over_mtd_write_block(srcFd, pSrcPart->erase_size, pSrcWrite->buffer); +#endif + if (len <= 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: writing to partition failed\n"); + return -1; + } + blockBytesWritten += len; + } + + + } + + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + + if (close(fd)) { + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static int scan_partition(const MtdPartition* pPart) +{ + BmlOverMtdReadContext* readCtx = bml_over_mtd_read_partition(pPart); + if (readCtx == NULL) + { + fprintf(stderr, "Failed to open partition for reading.\n"); + return -1; + } + + int numBadBlocks = 0; + size_t numBlocks = pPart->size / pPart->erase_size; + size_t currBlock; + for (currBlock = 0; currBlock < numBlocks; ++currBlock) + { + + loff_t pos = currBlock * pPart->erase_size; + int mgbb = ioctl(readCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d at 0x%x.\n", currBlock, (unsigned int)pos); + numBadBlocks++; + } + } + + bml_over_mtd_read_close(readCtx); + if (numBadBlocks == 0) + { + printf("No bad blocks.\n"); + return 0; + } + return -1 ; +} + +int main(int argc, char **argv) +{ + if (argc != 7 && (argc != 3 || (argc == 3 && strcmp(argv[1],"scan"))!=0) + && (argc != 6 || (argc == 6 && strcmp(argv[1],"scan"))!=0)) + return die("Usage: %s dump|flash \n" + "E.g. %s dump boot 72 reservoir 2004 file.bin\n" + "Usage: %s scan [ ]\n" + ,argv[0], argv[0], argv[0]); + int num_partitions = mtd_scan_partitions(); + const MtdPartition *pSrcPart = mtd_find_partition_by_name(argv[2]); + if (pSrcPart == NULL) + return die("Cannot find partition %s", argv[2]); + + int scanResult = scan_partition(pSrcPart); + + if (argc == 3 && strcmp(argv[1],"scan")==0) + { + return (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + int retVal = 0; + const MtdPartition* pReservoirPart = mtd_find_partition_by_name(argv[4]); + if (pReservoirPart == NULL) + return die("Cannot find partition %s", argv[4]); + + int srcPartStartBlock = atoi(argv[3]); + int reservoirPartStartBlock = atoi(argv[5]); + const unsigned short* pMapping = CreateBlockMapping(pSrcPart, srcPartStartBlock, + pReservoirPart, reservoirPartStartBlock); + + if (pMapping == NULL && scanResult == 0) + { + printf("Generating empty block mapping table for error-free partition.\n"); + pMapping = CreateEmptyBlockMapping(pSrcPart); + } + + if (argc == 6 && strcmp(argv[1],"scan")==0) + { + retVal = (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + if (pMapping == NULL) + return die("Failed to create block mapping table"); + + if (strcmp(argv[1],"dump")==0) + { + retVal = dump_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully dumped partition to %s\n", argv[6]); + } + + if (strcmp(argv[1],"flash")==0) + { + retVal = flash_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully wrote %s to partition\n", argv[6]); + + } + + + ReleaseBlockMapping(pMapping); + return retVal; +} + From df3004bd16b986b1f8299a7f9dfdbe4bdb144ed7 Mon Sep 17 00:00:00 2001 From: mik_os Date: Tue, 26 Apr 2011 19:15:22 +0300 Subject: [PATCH 07/16] Try to mount vol->device2 partition to UMS. Stock sd-card that ships with LG-P500 formatted with no partitions. This issue can be handled with device2 in recovery.fstab. But UMS didn't. We will try to mount second device (if it was defined) if first failed. Change-Id: Ia8b58b9fdfa3e63f703a1dd5870cb76936cec88e --- extendedcommands.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extendedcommands.c b/extendedcommands.c index be93770..b691d10 100644 --- a/extendedcommands.c +++ b/extendedcommands.c @@ -353,7 +353,8 @@ void show_mount_usb_storage_menu() return -1; } - if (write(fd, vol->device, strlen(vol->device)) < 0) { + if ((write(fd, vol->device, strlen(vol->device)) < 0) && + (!vol->device2 || (write(fd, vol->device, strlen(vol->device2)) < 0))) { LOGE("Unable to write to ums lunfile (%s)", strerror(errno)); close(fd); return -1; From cea6eba92de57bb9026d4fa3ffe1d8e281c07a5f Mon Sep 17 00:00:00 2001 From: wjb Date: Sat, 30 Apr 2011 21:04:04 -0400 Subject: [PATCH 08/16] recovery: display mount point when confirming format Change-Id: I474a511774b9e7ecf92f9fcedb1952d4c0adfdbc --- extendedcommands.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extendedcommands.c b/extendedcommands.c index b691d10..d263405 100644 --- a/extendedcommands.c +++ b/extendedcommands.c @@ -549,6 +549,7 @@ void show_partition_menu() static char* confirm_format = "Confirm format?"; static char* confirm = "Yes - Format"; + char confirm_string[255]; for (;;) { @@ -602,7 +603,9 @@ void show_partition_menu() FormatMenuEntry* e = &format_menue[chosen_item]; Volume* v = e->v; - if (!confirm_selection(confirm_format, confirm)) + sprintf(confirm_string, "%s - %s", v->mount_point, confirm_format); + + if (!confirm_selection(confirm_string, confirm)) continue; ui_print("Formatting %s...\n", v->mount_point); if (0 != format_volume(v->mount_point)) From 54815c9dc91fcef776641757925d036626a9faef Mon Sep 17 00:00:00 2001 From: atinm Date: Tue, 3 May 2011 00:25:01 -0400 Subject: [PATCH 09/16] Use TARGET_RECOVERY_PRE_COMMAND before calling __reboot() recovery For the Power menu, frameworks/base/core/jni/android_os_Power.cpp#L180 already uses TARGET_RECOVERY_PRE_COMMAND if TARGET_RECOVERY_PRE_COMMAND is defined in the BoardConfig.mk for a device to make a system() call before calling __reboot() for recovery. This commit adds the same thing to the other places that we know we are getting into recovery using __reboot(). Change-Id: Ifd0394fed1e87b63d80d95e0e2a049004518e27e --- Android.mk | 2 +- extendedcommands.c | 3 +++ reboot.c | 10 +++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Android.mk b/Android.mk index 2d7db14..eccce6b 100644 --- a/Android.mk +++ b/Android.mk @@ -31,7 +31,7 @@ LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) -BOARD_RECOVERY_DEFINES := BOARD_HAS_NO_SELECT_BUTTON BOARD_HAS_SMALL_RECOVERY BOARD_LDPI_RECOVERY BOARD_UMS_LUNFILE +BOARD_RECOVERY_DEFINES := BOARD_HAS_NO_SELECT_BUTTON BOARD_HAS_SMALL_RECOVERY BOARD_LDPI_RECOVERY BOARD_UMS_LUNFILE TARGET_RECOVERY_PRE_COMMAND $(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ $(if $($(board_define)), \ diff --git a/extendedcommands.c b/extendedcommands.c index d263405..e5daa5a 100644 --- a/extendedcommands.c +++ b/extendedcommands.c @@ -875,6 +875,9 @@ void show_advanced_menu() switch (chosen_item) { case 0: +#ifdef TARGET_RECOVERY_PRE_COMMAND + __system( TARGET_RECOVERY_PRE_COMMAND ); +#endif __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, "recovery"); break; case 1: diff --git a/reboot.c b/reboot.c index 04aa9ae..6af84da 100644 --- a/reboot.c +++ b/reboot.c @@ -49,9 +49,13 @@ int reboot_main(int argc, char *argv[]) if(force || argc > optind) { if(poweroff) ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL); - else if(argc > optind) - ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, argv[optind]); - else + else if(argc > optind) { +#ifdef TARGET_RECOVERY_PRE_COMMAND + if (!strncmp(argv[optind],"recovery",8)) + system( TARGET_RECOVERY_PRE_COMMAND ); +#endif + ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, argv[optind]); + } else ret = reboot(RB_AUTOBOOT); } else { if(poweroff) { From 4d5109cfa8a7c6c6bbeca17d353b66b6763caff6 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 27 Apr 2011 13:08:24 -0700 Subject: [PATCH 10/16] update to 3.1.0.0 Change-Id: I86d5aa2651c297ddf03279269da0c23819a06be1 --- Android.mk | 2 +- res/images/icon_clockwork.png | Bin 23780 -> 19889 bytes 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 res/images/icon_clockwork.png diff --git a/Android.mk b/Android.mk index eccce6b..97e253b 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.0.2.8 +RECOVERY_VERSION := ClockworkMod Recovery v3.1.0.0 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) diff --git a/res/images/icon_clockwork.png b/res/images/icon_clockwork.png old mode 100755 new mode 100644 index b05951df6c752309d0d963055c3090c54ad61ce6..e696bc5db194660c358a8b9b136eba80b535a53d GIT binary patch literal 19889 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 E53+E^GXMYp literal 23780 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 z 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 HO+)?%yctv* From 9ad8ba21b31cebc66dccdd341798357f54c64b87 Mon Sep 17 00:00:00 2001 From: Brandon Bennett Date: Sat, 7 May 2011 23:42:58 -0600 Subject: [PATCH 11/16] Allow per device unsafe-to-format partition list The change switches is_safe_to_format() from a hard coded list to a list that can be overwritten by system property. Change-Id: Ie536044a912c3e88462831851d288a60fdc30e2b --- extendedcommands.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/extendedcommands.c b/extendedcommands.c index e5daa5a..2e3fa51 100644 --- a/extendedcommands.c +++ b/extendedcommands.c @@ -490,8 +490,19 @@ typedef struct { int is_safe_to_format(char* name) { - return !(strcmp(name, "/misc") == 0 || strcmp(name, "/radio") == 0 - || strcmp(name, "/bootloader") == 0 || strcmp(name, "/recovery") == 0); + char str[255]; + char* partition; + property_get("ro.recovery.format_ignore_partitions", str, "/misc,/radio,/bootloader,/recovery"); + + partition = strtok(str, ", "); + while (partition != NULL) { + if (strcmp(name, partition) == 0) { + return 0; + } + partition = strtok(NULL, ", "); + } + + return 1; } void show_partition_menu() From 49553c75612acf2e0c8731d9d9eff26dc0f18a19 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 13 May 2011 13:01:26 -0700 Subject: [PATCH 12/16] Do not close the zip archive prematurely. It may be accessed later for a firmware update. Change-Id: I31c298f75bbcdc7998221aa2b3aa334926343139 --- install.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install.c b/install.c index ad2c21d..8194c35 100644 --- a/install.c +++ b/install.c @@ -127,10 +127,10 @@ try_update_binary(const char *path, ZipArchive *zip) { } bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); close(fd); - mzCloseZipArchive(zip); if (!ok) { LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); + mzCloseZipArchive(zip); return 1; } @@ -239,13 +239,13 @@ try_update_binary(const char *path, ZipArchive *zip) { waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + mzCloseZipArchive(zip); return INSTALL_ERROR; } if (firmware_type != NULL) { + mzCloseZipArchive(zip); return handle_firmware_update(firmware_type, firmware_filename, zip); - } else { - return INSTALL_SUCCESS; } return INSTALL_SUCCESS; } From 20b516a408adebcad06e03f12516e70b8998c38f Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sun, 15 May 2011 18:48:17 -0700 Subject: [PATCH 13/16] set the env for UPDATE_PACKAGE (the source zip) for update-binary. This allows shell scripts to use the source zip. Change-Id: Ia8118b31408f687780dd45e14f540a7e76619cba --- install.c | 1 + 1 file changed, 1 insertion(+) diff --git a/install.c b/install.c index ad2c21d..1ed0465 100644 --- a/install.c +++ b/install.c @@ -181,6 +181,7 @@ try_update_binary(const char *path, ZipArchive *zip) { pid_t pid = fork(); if (pid == 0) { + setenv("UPDATE_PACKAGE", path, 1); close(pipefd[0]); execv(binary, args); fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); From 666053ed2612a407b288618e39581e4396588cff Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 16 May 2011 10:04:53 -0700 Subject: [PATCH 14/16] 3.1.0.1 Change-Id: Ia3115a520922254565532bbbb1e9aba6c680e80b --- Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android.mk b/Android.mk index 97e253b..a3012aa 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.1.0.0 +RECOVERY_VERSION := ClockworkMod Recovery v3.1.0.1 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) From 0b4eac9e0126ea3edd0d81bef972db4cb507d9ba Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 16 May 2011 13:32:01 -0700 Subject: [PATCH 15/16] also set UPDATE_PACKAGE in updater for old recoveries Change-Id: I90e3feb0c1655279d58751ce800f299d9b4f3d03 --- updater/updater.c | 1 + 1 file changed, 1 insertion(+) diff --git a/updater/updater.c b/updater/updater.c index aa626d2..6bc4f40 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -63,6 +63,7 @@ int main(int argc, char** argv) { // Extract the script from the package. char* package_data = argv[3]; + setenv("UPDATE_PACKAGE", package_data, 1); ZipArchive za; int err; err = mzOpenZipArchive(package_data, &za); From d79b7541f3ba5fc1c162da1aa7b28544ab85c170 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 25 May 2011 10:47:41 -0700 Subject: [PATCH 16/16] whoops Change-Id: Ided86fe1991e5d9d6a707465429ec35ccc09743a --- Android.mk | 2 +- install.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Android.mk b/Android.mk index a3012aa..13ce859 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.1.0.1 +RECOVERY_VERSION := ClockworkMod Recovery v3.1.0.2 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) diff --git a/install.c b/install.c index 7ad1b2d..0b35ee7 100644 --- a/install.c +++ b/install.c @@ -245,8 +245,9 @@ try_update_binary(const char *path, ZipArchive *zip) { } if (firmware_type != NULL) { + int ret = handle_firmware_update(firmware_type, firmware_filename, zip); mzCloseZipArchive(zip); - return handle_firmware_update(firmware_type, firmware_filename, zip); + return ret; } return INSTALL_SUCCESS; }