Compare commits
No commits in common. "ics" and "cupcake" have entirely different histories.
Android.mkCleanSpec.mk
amend
Android.mkamend.camend.hast.cast.hcommands.ccommands.hexecute.cexecute.hlexer.hlexer.lmain.cparser.hparser_y.ypermissions.cpermissions.hregister.cregister.hsymtab.csymtab.htest_commands.ctest_permissions.ctest_symtab.c
tests
001-nop
002-lex-empty
003-lex-command
004-lex-comment
005-lex-quoted-string
006-lex-words
007-lex-real-script
008-parse-real-script
XXX-long-token
XXX-stack-overflow
one-testrun-all-testsapplypatch
Android.mkapplypatch.capplypatch.happlypatch.shbsdiff.cbspatch.cfreecache.cimgdiff.cimgdiff.himgdiff_test.shimgpatch.cmain.c
testdata
utils.cbmlutils
bootloader.ccommands.ccommands.hcommon.hdefault_recovery_keys.cdefault_recovery_ui.cedify
166
Android.mk
166
Android.mk
@ -2,67 +2,26 @@ LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
commands_recovery_local_path := $(LOCAL_PATH)
|
||||
# LOCAL_CPP_EXTENSION := .c
|
||||
|
||||
ifneq ($(TARGET_SIMULATOR),true)
|
||||
ifeq ($(TARGET_ARCH),arm)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
recovery.c \
|
||||
bootloader.c \
|
||||
install.c \
|
||||
roots.c \
|
||||
ui.c \
|
||||
mounts.c \
|
||||
extendedcommands.c \
|
||||
nandroid.c \
|
||||
../../system/core/toolbox/reboot.c \
|
||||
firmware.c \
|
||||
edifyscripting.c \
|
||||
setprop.c \
|
||||
default_recovery_ui.c \
|
||||
verifier.c
|
||||
recovery.c \
|
||||
bootloader.c \
|
||||
commands.c \
|
||||
firmware.c \
|
||||
install.c \
|
||||
roots.c \
|
||||
ui.c \
|
||||
verifier.c
|
||||
|
||||
ADDITIONAL_RECOVERY_FILES := $(shell echo $$ADDITIONAL_RECOVERY_FILES)
|
||||
LOCAL_SRC_FILES += $(ADDITIONAL_RECOVERY_FILES)
|
||||
LOCAL_SRC_FILES += test_roots.c
|
||||
|
||||
LOCAL_MODULE := recovery
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
|
||||
ifdef I_AM_KOUSH
|
||||
RECOVERY_NAME := ClockworkMod Recovery
|
||||
LOCAL_CFLAGS += -DI_AM_KOUSH
|
||||
else
|
||||
RECOVERY_NAME := CWM-based Recovery
|
||||
endif
|
||||
|
||||
RECOVERY_VERSION := $(RECOVERY_NAME) v5.5.0.4
|
||||
|
||||
LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)"
|
||||
RECOVERY_API_VERSION := 2
|
||||
LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
|
||||
|
||||
ifneq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),)
|
||||
BOARD_RECOVERY_CHAR_WIDTH := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _ -f 2 | cut -d . -f 1 | cut -d x -f 1)
|
||||
BOARD_RECOVERY_CHAR_HEIGHT := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _ -f 2 | cut -d . -f 1 | cut -d x -f 2)
|
||||
else
|
||||
BOARD_RECOVERY_CHAR_WIDTH := 10
|
||||
BOARD_RECOVERY_CHAR_HEIGHT := 18
|
||||
endif
|
||||
LOCAL_CFLAGS += -DBOARD_RECOVERY_CHAR_WIDTH=$(BOARD_RECOVERY_CHAR_WIDTH) -DBOARD_RECOVERY_CHAR_HEIGHT=$(BOARD_RECOVERY_CHAR_HEIGHT)
|
||||
|
||||
BOARD_RECOVERY_DEFINES := BOARD_HAS_NO_SELECT_BUTTON BOARD_UMS_LUNFILE BOARD_RECOVERY_ALWAYS_WIPES BOARD_RECOVERY_HANDLES_MOUNT BOARD_TOUCH_RECOVERY
|
||||
|
||||
$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
|
||||
$(if $($(board_define)), \
|
||||
$(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
|
||||
) \
|
||||
)
|
||||
|
||||
LOCAL_STATIC_LIBRARIES :=
|
||||
|
||||
LOCAL_CFLAGS += -DUSE_EXT4
|
||||
LOCAL_C_INCLUDES += system/extras/ext4_utils
|
||||
LOCAL_STATIC_LIBRARIES += libext4_utils libz
|
||||
|
||||
# This binary is in the recovery ramdisk, which is otherwise a copy of root.
|
||||
# It gets copied there in config/Makefile. LOCAL_MODULE_TAGS suppresses
|
||||
# a (redundant) copy of the binary in /system/bin for user builds.
|
||||
@ -70,97 +29,32 @@ LOCAL_STATIC_LIBRARIES += libext4_utils libz
|
||||
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
|
||||
ifeq ($(BOARD_CUSTOM_RECOVERY_KEYMAPPING),)
|
||||
LOCAL_SRC_FILES += default_recovery_keys.c
|
||||
else
|
||||
LOCAL_SRC_FILES += $(BOARD_CUSTOM_RECOVERY_KEYMAPPING)
|
||||
endif
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libext4_utils libz
|
||||
LOCAL_STATIC_LIBRARIES += libminzip libunz libmincrypt
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libedify libbusybox libmkyaffs2image libunyaffs liberase_image libdump_image libflash_image
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libcrecovery 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 := libminzip libunz libamend libmtdutils libmincrypt
|
||||
LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libcutils
|
||||
LOCAL_STATIC_LIBRARIES += libstdc++ libc
|
||||
|
||||
LOCAL_C_INCLUDES += system/extras/ext4_utils
|
||||
# Specify a C-includable file containing the OTA public keys.
|
||||
# This is built in config/Makefile.
|
||||
# *** THIS IS A TOTAL HACK; EXECUTABLES MUST NOT CHANGE BETWEEN DIFFERENT
|
||||
# PRODUCTS/BUILD TYPES. ***
|
||||
# TODO: make recovery read the keys from an external file.
|
||||
RECOVERY_INSTALL_OTA_KEYS_INC := \
|
||||
$(call intermediates-dir-for,PACKAGING,ota_keys_inc)/keys.inc
|
||||
# Let install.c say #include "keys.inc"
|
||||
LOCAL_C_INCLUDES += $(dir $(RECOVERY_INSTALL_OTA_KEYS_INC))
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
RECOVERY_LINKS := edify busybox flash_image dump_image mkyaffs2image unyaffs erase_image nandroid reboot volume setprop
|
||||
# Depend on the generated keys.inc containing the OTA public keys.
|
||||
$(intermediates)/install.o: $(RECOVERY_INSTALL_OTA_KEYS_INC)
|
||||
|
||||
# nc is provided by external/netcat
|
||||
RECOVERY_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(RECOVERY_LINKS))
|
||||
$(RECOVERY_SYMLINKS): RECOVERY_BINARY := $(LOCAL_MODULE)
|
||||
$(RECOVERY_SYMLINKS): $(LOCAL_INSTALLED_MODULE)
|
||||
@echo "Symlink: $@ -> $(RECOVERY_BINARY)"
|
||||
@mkdir -p $(dir $@)
|
||||
@rm -rf $@
|
||||
$(hide) ln -sf $(RECOVERY_BINARY) $@
|
||||
|
||||
ALL_DEFAULT_INSTALLED_MODULES += $(RECOVERY_SYMLINKS)
|
||||
|
||||
# Now let's do recovery symlinks
|
||||
BUSYBOX_LINKS := $(shell cat external/busybox/busybox-minimal.links)
|
||||
exclude := tune2fs mke2fs
|
||||
RECOVERY_BUSYBOX_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(filter-out $(exclude),$(notdir $(BUSYBOX_LINKS))))
|
||||
$(RECOVERY_BUSYBOX_SYMLINKS): BUSYBOX_BINARY := busybox
|
||||
$(RECOVERY_BUSYBOX_SYMLINKS): $(LOCAL_INSTALLED_MODULE)
|
||||
@echo "Symlink: $@ -> $(BUSYBOX_BINARY)"
|
||||
@mkdir -p $(dir $@)
|
||||
@rm -rf $@
|
||||
$(hide) ln -sf $(BUSYBOX_BINARY) $@
|
||||
|
||||
ALL_DEFAULT_INSTALLED_MODULES += $(RECOVERY_BUSYBOX_SYMLINKS)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := nandroid-md5.sh
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
|
||||
LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
|
||||
LOCAL_SRC_FILES := nandroid-md5.sh
|
||||
include $(BUILD_PREBUILT)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := killrecovery.sh
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
|
||||
LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
|
||||
LOCAL_SRC_FILES := killrecovery.sh
|
||||
include $(BUILD_PREBUILT)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := verifier_test.c verifier.c
|
||||
|
||||
LOCAL_MODULE := verifier_test
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libmincrypt libcutils libstdc++ libc
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
include $(commands_recovery_local_path)/bmlutils/Android.mk
|
||||
include $(commands_recovery_local_path)/flashutils/Android.mk
|
||||
include $(commands_recovery_local_path)/libcrecovery/Android.mk
|
||||
include $(commands_recovery_local_path)/minui/Android.mk
|
||||
include $(commands_recovery_local_path)/minelf/Android.mk
|
||||
|
||||
endif # TARGET_ARCH == arm
|
||||
endif # !TARGET_SIMULATOR
|
||||
|
||||
include $(commands_recovery_local_path)/amend/Android.mk
|
||||
include $(commands_recovery_local_path)/minzip/Android.mk
|
||||
include $(commands_recovery_local_path)/mtdutils/Android.mk
|
||||
include $(commands_recovery_local_path)/mmcutils/Android.mk
|
||||
include $(commands_recovery_local_path)/tools/Android.mk
|
||||
include $(commands_recovery_local_path)/edify/Android.mk
|
||||
include $(commands_recovery_local_path)/updater/Android.mk
|
||||
include $(commands_recovery_local_path)/applypatch/Android.mk
|
||||
include $(commands_recovery_local_path)/utilities/Android.mk
|
||||
commands_recovery_local_path :=
|
||||
|
49
CleanSpec.mk
49
CleanSpec.mk
@ -1,49 +0,0 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# If you don't need to do a full clean build but would like to touch
|
||||
# a file or delete some intermediate files, add a clean step to the end
|
||||
# of the list. These steps will only be run once, if they haven't been
|
||||
# run before.
|
||||
#
|
||||
# E.g.:
|
||||
# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
|
||||
# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
|
||||
#
|
||||
# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
|
||||
# files that are missing or have been moved.
|
||||
#
|
||||
# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
|
||||
# Use $(OUT_DIR) to refer to the "out" directory.
|
||||
#
|
||||
# If you need to re-do something that's already mentioned, just copy
|
||||
# the command and add it to the bottom of the list. E.g., if a change
|
||||
# that you made last week required touching a file and a change you
|
||||
# made today requires touching the same file, just copy the old
|
||||
# touch step and add it to the end of the list.
|
||||
#
|
||||
# ************************************************
|
||||
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
|
||||
# ************************************************
|
||||
|
||||
# For example:
|
||||
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
|
||||
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
|
||||
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
|
||||
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
|
||||
|
||||
# ************************************************
|
||||
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
|
||||
# ************************************************
|
53
amend/Android.mk
Normal file
53
amend/Android.mk
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright 2007 The Android Open Source Project
|
||||
#
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
amend_src_files := \
|
||||
amend.c \
|
||||
lexer.l \
|
||||
parser_y.y \
|
||||
ast.c \
|
||||
symtab.c \
|
||||
commands.c \
|
||||
permissions.c \
|
||||
execute.c
|
||||
|
||||
amend_test_files := \
|
||||
test_symtab.c \
|
||||
test_commands.c \
|
||||
test_permissions.c
|
||||
|
||||
# "-x c" forces the lex/yacc files to be compiled as c;
|
||||
# the build system otherwise forces them to be c++.
|
||||
amend_cflags := -Wall -x c
|
||||
|
||||
#
|
||||
# Build the host-side command line tool
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(amend_src_files) \
|
||||
$(amend_test_files) \
|
||||
register.c \
|
||||
main.c
|
||||
|
||||
LOCAL_CFLAGS := $(amend_cflags) -g -O0
|
||||
LOCAL_MODULE := amend
|
||||
LOCAL_YACCFLAGS := -v
|
||||
|
||||
include $(BUILD_HOST_EXECUTABLE)
|
||||
|
||||
#
|
||||
# Build the device-side library
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(amend_src_files)
|
||||
LOCAL_SRC_FILES += $(amend_test_files)
|
||||
|
||||
LOCAL_CFLAGS := $(amend_cflags)
|
||||
LOCAL_MODULE := libamend
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
32
amend/amend.c
Normal file
32
amend/amend.c
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "amend.h"
|
||||
#include "lexer.h"
|
||||
|
||||
extern const AmCommandList *gCommands;
|
||||
|
||||
const AmCommandList *
|
||||
parseAmendScript(const char *buf, size_t bufLen)
|
||||
{
|
||||
setLexerInputBuffer(buf, bufLen);
|
||||
int ret = yyparse();
|
||||
if (ret != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return gCommands;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
* 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.
|
||||
@ -14,17 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H
|
||||
#define _BUILD_TOOLS_APPLYPATCH_UTILS_H
|
||||
#ifndef AMEND_H_
|
||||
#define AMEND_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include "ast.h"
|
||||
#include "execute.h"
|
||||
|
||||
// Read and write little-endian values of various sizes.
|
||||
const AmCommandList *parseAmendScript(const char *buf, size_t bufLen);
|
||||
|
||||
void Write4(int value, FILE* f);
|
||||
void Write8(long long value, FILE* f);
|
||||
int Read2(void* p);
|
||||
int Read4(void* p);
|
||||
long long Read8(void* p);
|
||||
|
||||
#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H
|
||||
#endif // AMEND_H_
|
198
amend/ast.c
Normal file
198
amend/ast.c
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "ast.h"
|
||||
|
||||
static const char gSpaces[] =
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" ";
|
||||
const int gSpacesMax = sizeof(gSpaces) - 1;
|
||||
|
||||
static const char *
|
||||
pad(int level)
|
||||
{
|
||||
level *= 4;
|
||||
if (level > gSpacesMax) {
|
||||
level = gSpacesMax;
|
||||
}
|
||||
return gSpaces + gSpacesMax - level;
|
||||
}
|
||||
|
||||
void dumpBooleanValue(int level, const AmBooleanValue *booleanValue);
|
||||
void dumpStringValue(int level, const AmStringValue *stringValue);
|
||||
|
||||
void
|
||||
dumpBooleanExpression(int level, const AmBooleanExpression *booleanExpression)
|
||||
{
|
||||
const char *op;
|
||||
bool unary = false;
|
||||
|
||||
switch (booleanExpression->op) {
|
||||
case AM_BOP_NOT:
|
||||
op = "NOT";
|
||||
unary = true;
|
||||
break;
|
||||
case AM_BOP_EQ:
|
||||
op = "EQ";
|
||||
break;
|
||||
case AM_BOP_NE:
|
||||
op = "NE";
|
||||
break;
|
||||
case AM_BOP_AND:
|
||||
op = "AND";
|
||||
break;
|
||||
case AM_BOP_OR:
|
||||
op = "OR";
|
||||
break;
|
||||
default:
|
||||
op = "??";
|
||||
break;
|
||||
}
|
||||
|
||||
printf("%sBOOLEAN %s {\n", pad(level), op);
|
||||
dumpBooleanValue(level + 1, booleanExpression->arg1);
|
||||
if (!unary) {
|
||||
dumpBooleanValue(level + 1, booleanExpression->arg2);
|
||||
}
|
||||
printf("%s}\n", pad(level));
|
||||
}
|
||||
|
||||
void
|
||||
dumpFunctionArguments(int level, const AmFunctionArguments *functionArguments)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < functionArguments->argc; i++) {
|
||||
dumpStringValue(level, &functionArguments->argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dumpFunctionCall(int level, const AmFunctionCall *functionCall)
|
||||
{
|
||||
printf("%sFUNCTION %s (\n", pad(level), functionCall->name);
|
||||
dumpFunctionArguments(level + 1, functionCall->args);
|
||||
printf("%s)\n", pad(level));
|
||||
}
|
||||
|
||||
void
|
||||
dumpStringValue(int level, const AmStringValue *stringValue)
|
||||
{
|
||||
switch (stringValue->type) {
|
||||
case AM_SVAL_LITERAL:
|
||||
printf("%s\"%s\"\n", pad(level), stringValue->u.literal);
|
||||
break;
|
||||
case AM_SVAL_FUNCTION:
|
||||
dumpFunctionCall(level, stringValue->u.function);
|
||||
break;
|
||||
default:
|
||||
printf("%s<UNKNOWN SVAL TYPE %d>\n", pad(level), stringValue->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dumpStringComparisonExpression(int level,
|
||||
const AmStringComparisonExpression *stringComparisonExpression)
|
||||
{
|
||||
const char *op;
|
||||
|
||||
switch (stringComparisonExpression->op) {
|
||||
case AM_SOP_LT:
|
||||
op = "LT";
|
||||
break;
|
||||
case AM_SOP_LE:
|
||||
op = "LE";
|
||||
break;
|
||||
case AM_SOP_GT:
|
||||
op = "GT";
|
||||
break;
|
||||
case AM_SOP_GE:
|
||||
op = "GE";
|
||||
break;
|
||||
case AM_SOP_EQ:
|
||||
op = "EQ";
|
||||
break;
|
||||
case AM_SOP_NE:
|
||||
op = "NE";
|
||||
break;
|
||||
default:
|
||||
op = "??";
|
||||
break;
|
||||
}
|
||||
printf("%sSTRING %s {\n", pad(level), op);
|
||||
dumpStringValue(level + 1, stringComparisonExpression->arg1);
|
||||
dumpStringValue(level + 1, stringComparisonExpression->arg2);
|
||||
printf("%s}\n", pad(level));
|
||||
}
|
||||
|
||||
void
|
||||
dumpBooleanValue(int level, const AmBooleanValue *booleanValue)
|
||||
{
|
||||
switch (booleanValue->type) {
|
||||
case AM_BVAL_EXPRESSION:
|
||||
dumpBooleanExpression(level, &booleanValue->u.expression);
|
||||
break;
|
||||
case AM_BVAL_STRING_COMPARISON:
|
||||
dumpStringComparisonExpression(level,
|
||||
&booleanValue->u.stringComparison);
|
||||
break;
|
||||
default:
|
||||
printf("%s<UNKNOWN BVAL TYPE %d>\n", pad(1), booleanValue->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dumpWordList(const AmWordList *wordList)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < wordList->argc; i++) {
|
||||
printf("%s\"%s\"\n", pad(1), wordList->argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dumpCommandArguments(const AmCommandArguments *commandArguments)
|
||||
{
|
||||
if (commandArguments->booleanArgs) {
|
||||
dumpBooleanValue(1, commandArguments->u.b);
|
||||
} else {
|
||||
dumpWordList(commandArguments->u.w);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dumpCommand(const AmCommand *command)
|
||||
{
|
||||
printf("command \"%s\" {\n", command->name);
|
||||
dumpCommandArguments(command->args);
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
void
|
||||
dumpCommandList(const AmCommandList *commandList)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < commandList->commandCount; i++) {
|
||||
dumpCommand(commandList->commands[i]);
|
||||
}
|
||||
}
|
165
amend/ast.h
Normal file
165
amend/ast.h
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_AST_H_
|
||||
#define AMEND_AST_H_
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
typedef struct AmStringValue AmStringValue;
|
||||
|
||||
typedef struct {
|
||||
int argc;
|
||||
AmStringValue *argv;
|
||||
} AmFunctionArguments;
|
||||
|
||||
/* An internal structure used only by the parser;
|
||||
* will not appear in the output AST.
|
||||
xxx try to move this into parser.h
|
||||
*/
|
||||
typedef struct AmFunctionArgumentBuilder AmFunctionArgumentBuilder;
|
||||
struct AmFunctionArgumentBuilder {
|
||||
AmFunctionArgumentBuilder *next;
|
||||
AmStringValue *arg;
|
||||
int argCount;
|
||||
};
|
||||
|
||||
typedef struct AmWordListBuilder AmWordListBuilder;
|
||||
struct AmWordListBuilder {
|
||||
AmWordListBuilder *next;
|
||||
const char *word;
|
||||
int wordCount;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
Function *fn;
|
||||
AmFunctionArguments *args;
|
||||
} AmFunctionCall;
|
||||
|
||||
|
||||
/* <string-value> ::=
|
||||
* <literal-string> |
|
||||
* <function-call>
|
||||
*/
|
||||
struct AmStringValue {
|
||||
unsigned int line;
|
||||
|
||||
enum {
|
||||
AM_SVAL_LITERAL,
|
||||
AM_SVAL_FUNCTION,
|
||||
} type;
|
||||
union {
|
||||
const char *literal;
|
||||
//xxx inline instead of using pointers
|
||||
AmFunctionCall *function;
|
||||
} u;
|
||||
};
|
||||
|
||||
|
||||
/* <string-comparison-expression> ::=
|
||||
* <string-value> <string-comparison-operator> <string-value>
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned int line;
|
||||
|
||||
enum {
|
||||
AM_SOP_LT,
|
||||
AM_SOP_LE,
|
||||
AM_SOP_GT,
|
||||
AM_SOP_GE,
|
||||
AM_SOP_EQ,
|
||||
AM_SOP_NE,
|
||||
} op;
|
||||
AmStringValue *arg1;
|
||||
AmStringValue *arg2;
|
||||
} AmStringComparisonExpression;
|
||||
|
||||
|
||||
/* <boolean-expression> ::=
|
||||
* ! <boolean-value> |
|
||||
* <boolean-value> <binary-boolean-operator> <boolean-value>
|
||||
*/
|
||||
typedef struct AmBooleanValue AmBooleanValue;
|
||||
typedef struct {
|
||||
unsigned int line;
|
||||
|
||||
enum {
|
||||
AM_BOP_NOT,
|
||||
|
||||
AM_BOP_EQ,
|
||||
AM_BOP_NE,
|
||||
|
||||
AM_BOP_AND,
|
||||
|
||||
AM_BOP_OR,
|
||||
} op;
|
||||
AmBooleanValue *arg1;
|
||||
AmBooleanValue *arg2;
|
||||
} AmBooleanExpression;
|
||||
|
||||
|
||||
/* <boolean-value> ::=
|
||||
* <boolean-expression> |
|
||||
* <string-comparison-expression>
|
||||
*/
|
||||
struct AmBooleanValue {
|
||||
unsigned int line;
|
||||
|
||||
enum {
|
||||
AM_BVAL_EXPRESSION,
|
||||
AM_BVAL_STRING_COMPARISON,
|
||||
} type;
|
||||
union {
|
||||
AmBooleanExpression expression;
|
||||
AmStringComparisonExpression stringComparison;
|
||||
} u;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned int line;
|
||||
|
||||
int argc;
|
||||
const char **argv;
|
||||
} AmWordList;
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool booleanArgs;
|
||||
union {
|
||||
AmWordList *w;
|
||||
AmBooleanValue *b;
|
||||
} u;
|
||||
} AmCommandArguments;
|
||||
|
||||
typedef struct {
|
||||
unsigned int line;
|
||||
|
||||
const char *name;
|
||||
Command *cmd;
|
||||
AmCommandArguments *args;
|
||||
} AmCommand;
|
||||
|
||||
typedef struct {
|
||||
AmCommand **commands;
|
||||
int commandCount;
|
||||
int arraySize;
|
||||
} AmCommandList;
|
||||
|
||||
void dumpCommandList(const AmCommandList *commandList);
|
||||
|
||||
#endif // AMEND_AST_H_
|
273
amend/commands.c
Normal file
273
amend/commands.c
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "symtab.h"
|
||||
#include "commands.h"
|
||||
|
||||
#if 1
|
||||
#define TRACE(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define TRACE(...) /**/
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CMD_TYPE_UNKNOWN = -1,
|
||||
CMD_TYPE_COMMAND = 0,
|
||||
CMD_TYPE_FUNCTION
|
||||
} CommandType;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
void *cookie;
|
||||
CommandType type;
|
||||
CommandArgumentType argType;
|
||||
CommandHook hook;
|
||||
} CommandEntry;
|
||||
|
||||
static struct {
|
||||
SymbolTable *symbolTable;
|
||||
bool commandStateInitialized;
|
||||
} gCommandState;
|
||||
|
||||
int
|
||||
commandInit()
|
||||
{
|
||||
if (gCommandState.commandStateInitialized) {
|
||||
return -1;
|
||||
}
|
||||
gCommandState.symbolTable = createSymbolTable();
|
||||
if (gCommandState.symbolTable == NULL) {
|
||||
return -1;
|
||||
}
|
||||
gCommandState.commandStateInitialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
commandCleanup()
|
||||
{
|
||||
if (gCommandState.commandStateInitialized) {
|
||||
gCommandState.commandStateInitialized = false;
|
||||
deleteSymbolTable(gCommandState.symbolTable);
|
||||
gCommandState.symbolTable = NULL;
|
||||
//xxx need to free the entries and names in the symbol table
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
registerCommandInternal(const char *name, CommandType type,
|
||||
CommandArgumentType argType, CommandHook hook, void *cookie)
|
||||
{
|
||||
CommandEntry *entry;
|
||||
|
||||
if (!gCommandState.commandStateInitialized) {
|
||||
return -1;
|
||||
}
|
||||
if (name == NULL || hook == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (type != CMD_TYPE_COMMAND && type != CMD_TYPE_FUNCTION) {
|
||||
return -1;
|
||||
}
|
||||
if (argType != CMD_ARGS_BOOLEAN && argType != CMD_ARGS_WORDS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry = (CommandEntry *)malloc(sizeof(CommandEntry));
|
||||
if (entry != NULL) {
|
||||
entry->name = strdup(name);
|
||||
if (entry->name != NULL) {
|
||||
int ret;
|
||||
|
||||
entry->cookie = cookie;
|
||||
entry->type = type;
|
||||
entry->argType = argType;
|
||||
entry->hook = hook;
|
||||
ret = addToSymbolTable(gCommandState.symbolTable,
|
||||
entry->name, entry->type, entry);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
free(entry);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
registerCommand(const char *name,
|
||||
CommandArgumentType argType, CommandHook hook, void *cookie)
|
||||
{
|
||||
return registerCommandInternal(name,
|
||||
CMD_TYPE_COMMAND, argType, hook, cookie);
|
||||
}
|
||||
|
||||
int
|
||||
registerFunction(const char *name, FunctionHook hook, void *cookie)
|
||||
{
|
||||
return registerCommandInternal(name,
|
||||
CMD_TYPE_FUNCTION, CMD_ARGS_WORDS, (CommandHook)hook, cookie);
|
||||
}
|
||||
|
||||
Command *
|
||||
findCommand(const char *name)
|
||||
{
|
||||
return (Command *)findInSymbolTable(gCommandState.symbolTable,
|
||||
name, CMD_TYPE_COMMAND);
|
||||
}
|
||||
|
||||
Function *
|
||||
findFunction(const char *name)
|
||||
{
|
||||
return (Function *)findInSymbolTable(gCommandState.symbolTable,
|
||||
name, CMD_TYPE_FUNCTION);
|
||||
}
|
||||
|
||||
CommandArgumentType
|
||||
getCommandArgumentType(Command *cmd)
|
||||
{
|
||||
CommandEntry *entry = (CommandEntry *)cmd;
|
||||
|
||||
if (entry != NULL) {
|
||||
return entry->argType;
|
||||
}
|
||||
return CMD_ARGS_UNKNOWN;
|
||||
}
|
||||
|
||||
static int
|
||||
callCommandInternal(CommandEntry *entry, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
|
||||
(argc == 0 || (argc > 0 && argv != NULL)))
|
||||
{
|
||||
if (permissions == NULL) {
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (argv[i] == NULL) {
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE("calling command %s\n", entry->name);
|
||||
return entry->hook(entry->name, entry->cookie, argc, argv, permissions);
|
||||
//xxx if permissions, make sure the entry has added at least one element.
|
||||
}
|
||||
bail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
callBooleanCommandInternal(CommandEntry *entry, bool arg,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
if (entry != NULL && entry->argType == CMD_ARGS_BOOLEAN) {
|
||||
TRACE("calling boolean command %s\n", entry->name);
|
||||
return entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL,
|
||||
permissions);
|
||||
//xxx if permissions, make sure the entry has added at least one element.
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
callCommand(Command *cmd, int argc, const char *argv[])
|
||||
{
|
||||
return callCommandInternal((CommandEntry *)cmd, argc, argv, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
callBooleanCommand(Command *cmd, bool arg)
|
||||
{
|
||||
return callBooleanCommandInternal((CommandEntry *)cmd, arg, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
getCommandPermissions(Command *cmd, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
if (permissions != NULL) {
|
||||
return callCommandInternal((CommandEntry *)cmd, argc, argv,
|
||||
permissions);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
getBooleanCommandPermissions(Command *cmd, bool arg,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
if (permissions != NULL) {
|
||||
return callBooleanCommandInternal((CommandEntry *)cmd, arg,
|
||||
permissions);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
callFunctionInternal(CommandEntry *entry, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen, PermissionRequestList *permissions)
|
||||
{
|
||||
if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
|
||||
(argc == 0 || (argc > 0 && argv != NULL)))
|
||||
{
|
||||
if ((permissions == NULL && result != NULL) ||
|
||||
(permissions != NULL && result == NULL))
|
||||
{
|
||||
if (permissions == NULL) {
|
||||
/* This is the actual invocation of the function,
|
||||
* which means that none of the arguments are allowed
|
||||
* to be NULL.
|
||||
*/
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (argv[i] == NULL) {
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE("calling function %s\n", entry->name);
|
||||
return ((FunctionHook)entry->hook)(entry->name, entry->cookie,
|
||||
argc, argv, result, resultLen, permissions);
|
||||
//xxx if permissions, make sure the entry has added at least one element.
|
||||
}
|
||||
}
|
||||
bail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
callFunction(Function *fn, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
return callFunctionInternal((CommandEntry *)fn, argc, argv,
|
||||
result, resultLen, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
getFunctionPermissions(Function *fn, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
if (permissions != NULL) {
|
||||
return callFunctionInternal((CommandEntry *)fn, argc, argv,
|
||||
NULL, NULL, permissions);
|
||||
}
|
||||
return -1;
|
||||
}
|
96
amend/commands.h
Normal file
96
amend/commands.h
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_COMMANDS_H_
|
||||
#define AMEND_COMMANDS_H_
|
||||
|
||||
#include "permissions.h"
|
||||
|
||||
/* Invoke or dry-run a command. If "permissions" is non-NULL,
|
||||
* the hook should fill it out with the list of files and operations that
|
||||
* it would need to complete its operation. If "permissions" is NULL,
|
||||
* the hook should do the actual work specified by its arguments.
|
||||
*
|
||||
* When a command is called with non-NULL "permissions", some arguments
|
||||
* may be NULL. A NULL argument indicates that the argument is actually
|
||||
* the output of another function, so is not known at permissions time.
|
||||
* The permissions of leaf-node functions (those that have only literal
|
||||
* strings as arguments) will get appended to the permissions of the
|
||||
* functions that call them. However, to be completely safe, functions
|
||||
* that receive a NULL argument should request the broadest-possible
|
||||
* permissions for the range of the input argument.
|
||||
*
|
||||
* When a boolean command is called, "argc" is the boolean value and
|
||||
* "argv" is NULL.
|
||||
*/
|
||||
typedef int (*CommandHook)(const char *name, void *cookie,
|
||||
int argc, const char *argv[],
|
||||
PermissionRequestList *permissions);
|
||||
|
||||
int commandInit(void);
|
||||
void commandCleanup(void);
|
||||
|
||||
/*
|
||||
* Command management
|
||||
*/
|
||||
|
||||
struct Command;
|
||||
typedef struct Command Command;
|
||||
|
||||
typedef enum {
|
||||
CMD_ARGS_UNKNOWN = -1,
|
||||
CMD_ARGS_BOOLEAN = 0,
|
||||
CMD_ARGS_WORDS
|
||||
} CommandArgumentType;
|
||||
|
||||
int registerCommand(const char *name,
|
||||
CommandArgumentType argType, CommandHook hook, void *cookie);
|
||||
|
||||
Command *findCommand(const char *name);
|
||||
|
||||
CommandArgumentType getCommandArgumentType(Command *cmd);
|
||||
|
||||
int callCommand(Command *cmd, int argc, const char *argv[]);
|
||||
int callBooleanCommand(Command *cmd, bool arg);
|
||||
|
||||
int getCommandPermissions(Command *cmd, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions);
|
||||
int getBooleanCommandPermissions(Command *cmd, bool arg,
|
||||
PermissionRequestList *permissions);
|
||||
|
||||
/*
|
||||
* Function management
|
||||
*/
|
||||
|
||||
typedef int (*FunctionHook)(const char *name, void *cookie,
|
||||
int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions);
|
||||
|
||||
struct Function;
|
||||
typedef struct Function Function;
|
||||
|
||||
int registerFunction(const char *name, FunctionHook hook, void *cookie);
|
||||
|
||||
Function *findFunction(const char *name);
|
||||
|
||||
int callFunction(Function *fn, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen);
|
||||
|
||||
int getFunctionPermissions(Function *fn, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions);
|
||||
|
||||
#endif // AMEND_COMMANDS_H_
|
315
amend/execute.c
Normal file
315
amend/execute.c
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include "ast.h"
|
||||
#include "execute.h"
|
||||
|
||||
typedef struct {
|
||||
int c;
|
||||
const char **v;
|
||||
} StringList;
|
||||
|
||||
static int execBooleanValue(ExecContext *ctx,
|
||||
const AmBooleanValue *booleanValue, bool *result);
|
||||
static int execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
|
||||
const char **result);
|
||||
|
||||
static int
|
||||
execBooleanExpression(ExecContext *ctx,
|
||||
const AmBooleanExpression *booleanExpression, bool *result)
|
||||
{
|
||||
int ret;
|
||||
bool arg1, arg2;
|
||||
bool unary;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(booleanExpression != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || booleanExpression == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
if (booleanExpression->op == AM_BOP_NOT) {
|
||||
unary = true;
|
||||
} else {
|
||||
unary = false;
|
||||
}
|
||||
|
||||
ret = execBooleanValue(ctx, booleanExpression->arg1, &arg1);
|
||||
if (ret != 0) return ret;
|
||||
|
||||
if (!unary) {
|
||||
ret = execBooleanValue(ctx, booleanExpression->arg2, &arg2);
|
||||
if (ret != 0) return ret;
|
||||
} else {
|
||||
arg2 = false;
|
||||
}
|
||||
|
||||
switch (booleanExpression->op) {
|
||||
case AM_BOP_NOT:
|
||||
*result = !arg1;
|
||||
break;
|
||||
case AM_BOP_EQ:
|
||||
*result = (arg1 == arg2);
|
||||
break;
|
||||
case AM_BOP_NE:
|
||||
*result = (arg1 != arg2);
|
||||
break;
|
||||
case AM_BOP_AND:
|
||||
*result = (arg1 && arg2);
|
||||
break;
|
||||
case AM_BOP_OR:
|
||||
*result = (arg1 || arg2);
|
||||
break;
|
||||
default:
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
execFunctionArguments(ExecContext *ctx,
|
||||
const AmFunctionArguments *functionArguments, StringList *result)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(functionArguments != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || functionArguments == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
result->c = functionArguments->argc;
|
||||
result->v = (const char **)malloc(result->c * sizeof(const char *));
|
||||
if (result->v == NULL) {
|
||||
result->c = 0;
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < functionArguments->argc; i++) {
|
||||
ret = execStringValue(ctx, &functionArguments->argv[i], &result->v[i]);
|
||||
if (ret != 0) {
|
||||
result->c = 0;
|
||||
free(result->v);
|
||||
//TODO: free the individual args, if we're responsible for them.
|
||||
result->v = NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
execFunctionCall(ExecContext *ctx, const AmFunctionCall *functionCall,
|
||||
const char **result)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(functionCall != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || functionCall == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
StringList args;
|
||||
ret = execFunctionArguments(ctx, functionCall->args, &args);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = callFunction(functionCall->fn, args.c, args.v, (char **)result, NULL);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
//TODO: clean up args
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
|
||||
const char **result)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(stringValue != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || stringValue == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
switch (stringValue->type) {
|
||||
case AM_SVAL_LITERAL:
|
||||
*result = strdup(stringValue->u.literal);
|
||||
break;
|
||||
case AM_SVAL_FUNCTION:
|
||||
ret = execFunctionCall(ctx, stringValue->u.function, result);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
execStringComparisonExpression(ExecContext *ctx,
|
||||
const AmStringComparisonExpression *stringComparisonExpression,
|
||||
bool *result)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(stringComparisonExpression != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || stringComparisonExpression == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
const char *arg1, *arg2;
|
||||
ret = execStringValue(ctx, stringComparisonExpression->arg1, &arg1);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = execStringValue(ctx, stringComparisonExpression->arg2, &arg2);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cmp = strcmp(arg1, arg2);
|
||||
|
||||
switch (stringComparisonExpression->op) {
|
||||
case AM_SOP_LT:
|
||||
*result = (cmp < 0);
|
||||
break;
|
||||
case AM_SOP_LE:
|
||||
*result = (cmp <= 0);
|
||||
break;
|
||||
case AM_SOP_GT:
|
||||
*result = (cmp > 0);
|
||||
break;
|
||||
case AM_SOP_GE:
|
||||
*result = (cmp >= 0);
|
||||
break;
|
||||
case AM_SOP_EQ:
|
||||
*result = (cmp == 0);
|
||||
break;
|
||||
case AM_SOP_NE:
|
||||
*result = (cmp != 0);
|
||||
break;
|
||||
default:
|
||||
return -__LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
execBooleanValue(ExecContext *ctx, const AmBooleanValue *booleanValue,
|
||||
bool *result)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(booleanValue != NULL);
|
||||
assert(result != NULL);
|
||||
if (ctx == NULL || booleanValue == NULL || result == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
switch (booleanValue->type) {
|
||||
case AM_BVAL_EXPRESSION:
|
||||
ret = execBooleanExpression(ctx, &booleanValue->u.expression, result);
|
||||
break;
|
||||
case AM_BVAL_STRING_COMPARISON:
|
||||
ret = execStringComparisonExpression(ctx,
|
||||
&booleanValue->u.stringComparison, result);
|
||||
break;
|
||||
default:
|
||||
ret = -__LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
execCommand(ExecContext *ctx, const AmCommand *command)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(command != NULL);
|
||||
if (ctx == NULL || command == NULL) {
|
||||
return -__LINE__;
|
||||
}
|
||||
|
||||
CommandArgumentType argType;
|
||||
argType = getCommandArgumentType(command->cmd);
|
||||
switch (argType) {
|
||||
case CMD_ARGS_BOOLEAN:
|
||||
{
|
||||
bool bVal;
|
||||
ret = execBooleanValue(ctx, command->args->u.b, &bVal);
|
||||
if (ret == 0) {
|
||||
ret = callBooleanCommand(command->cmd, bVal);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CMD_ARGS_WORDS:
|
||||
{
|
||||
AmWordList *words = command->args->u.w;
|
||||
ret = callCommand(command->cmd, words->argc, words->argv);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -__LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
execCommandList(ExecContext *ctx, const AmCommandList *commandList)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < commandList->commandCount; i++) {
|
||||
int ret = execCommand(ctx, commandList->commands[i]);
|
||||
if (ret != 0) {
|
||||
int line = commandList->commands[i]->line;
|
||||
return line > 0 ? line : ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
25
amend/execute.h
Normal file
25
amend/execute.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_EXECUTE_H_
|
||||
#define AMEND_EXECUTE_H_
|
||||
|
||||
typedef struct ExecContext ExecContext;
|
||||
|
||||
/* Returns 0 on success, otherwise the line number that failed. */
|
||||
int execCommandList(ExecContext *ctx, const AmCommandList *commandList);
|
||||
|
||||
#endif // AMEND_EXECUTE_H_
|
43
amend/lexer.h
Normal file
43
amend/lexer.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_LEXER_H_
|
||||
#define AMEND_LEXER_H_
|
||||
|
||||
#define AMEND_LEXER_BUFFER_INPUT 1
|
||||
|
||||
void yyerror(const char *msg);
|
||||
int yylex(void);
|
||||
|
||||
#if AMEND_LEXER_BUFFER_INPUT
|
||||
void setLexerInputBuffer(const char *buf, size_t buflen);
|
||||
#else
|
||||
#include <stdio.h>
|
||||
void yyset_in(FILE *in_str);
|
||||
#endif
|
||||
|
||||
const char *tokenToString(int token);
|
||||
|
||||
typedef enum {
|
||||
AM_UNKNOWN_ARGS,
|
||||
AM_WORD_ARGS,
|
||||
AM_BOOLEAN_ARGS,
|
||||
} AmArgumentType;
|
||||
|
||||
void setLexerArgumentType(AmArgumentType type);
|
||||
int getLexerLineNumber(void);
|
||||
|
||||
#endif // AMEND_LEXER_H_
|
299
amend/lexer.l
Normal file
299
amend/lexer.l
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
%{
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
|
||||
const char *tokenToString(int token)
|
||||
{
|
||||
static char scratch[128];
|
||||
|
||||
switch (token) {
|
||||
case TOK_AND:
|
||||
return "&&";
|
||||
case TOK_OR:
|
||||
return "||";
|
||||
case TOK_EQ:
|
||||
return "==";
|
||||
case TOK_NE:
|
||||
return "!=";
|
||||
case TOK_GE:
|
||||
return ">=";
|
||||
case TOK_LE:
|
||||
return "<=";
|
||||
case TOK_EOF:
|
||||
return "EOF";
|
||||
case TOK_EOL:
|
||||
return "EOL\n";
|
||||
case TOK_STRING:
|
||||
snprintf(scratch, sizeof(scratch),
|
||||
"STRING<%s>", yylval.literalString);
|
||||
return scratch;
|
||||
case TOK_IDENTIFIER:
|
||||
snprintf(scratch, sizeof(scratch), "IDENTIFIER<%s>",
|
||||
yylval.literalString);
|
||||
return scratch;
|
||||
case TOK_WORD:
|
||||
snprintf(scratch, sizeof(scratch), "WORD<%s>",
|
||||
yylval.literalString);
|
||||
return scratch;
|
||||
default:
|
||||
if (token > ' ' && token <= '~') {
|
||||
scratch[0] = (char)token;
|
||||
scratch[1] = '\0';
|
||||
} else {
|
||||
snprintf(scratch, sizeof(scratch), "??? <%d>", token);
|
||||
}
|
||||
return scratch;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *value;
|
||||
char *nextc;
|
||||
unsigned int alloc_size;
|
||||
} AmString;
|
||||
|
||||
static int addCharToString(AmString *str, char c)
|
||||
{
|
||||
if ((unsigned int)(str->nextc - str->value) >= str->alloc_size) {
|
||||
char *new_value;
|
||||
unsigned int new_size;
|
||||
|
||||
new_size = (str->alloc_size + 1) * 2;
|
||||
if (new_size < 64) {
|
||||
new_size = 64;
|
||||
}
|
||||
|
||||
new_value = (char *)realloc(str->value, new_size);
|
||||
if (new_value == NULL) {
|
||||
yyerror("out of memory");
|
||||
return -1;
|
||||
}
|
||||
str->nextc = str->nextc - str->value + new_value;
|
||||
str->value = new_value;
|
||||
str->alloc_size = new_size;
|
||||
}
|
||||
*str->nextc++ = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setString(AmString *str, const char *p)
|
||||
{
|
||||
str->nextc = str->value;
|
||||
while (*p != '\0') {
|
||||
//TODO: add the whole string at once
|
||||
addCharToString(str, *p++);
|
||||
}
|
||||
return addCharToString(str, '\0');
|
||||
}
|
||||
|
||||
static AmString gStr = { NULL, NULL, 0 };
|
||||
static int gLineNumber = 1;
|
||||
static AmArgumentType gArgumentType = AM_UNKNOWN_ARGS;
|
||||
static const char *gErrorMessage = NULL;
|
||||
|
||||
#if AMEND_LEXER_BUFFER_INPUT
|
||||
static const char *gInputBuffer;
|
||||
static const char *gInputBufferNext;
|
||||
static const char *gInputBufferEnd;
|
||||
|
||||
# define YY_INPUT(buf, result, max_size) \
|
||||
do { \
|
||||
int nbytes = gInputBufferEnd - gInputBufferNext; \
|
||||
if (nbytes > 0) { \
|
||||
if (nbytes > max_size) { \
|
||||
nbytes = max_size; \
|
||||
} \
|
||||
memcpy(buf, gInputBufferNext, nbytes); \
|
||||
gInputBufferNext += nbytes; \
|
||||
result = nbytes; \
|
||||
} else { \
|
||||
result = YY_NULL; \
|
||||
} \
|
||||
} while (false)
|
||||
#endif // AMEND_LEXER_BUFFER_INPUT
|
||||
|
||||
%}
|
||||
|
||||
%option noyywrap
|
||||
|
||||
%x QUOTED_STRING BOOLEAN WORDS
|
||||
|
||||
ident [a-zA-Z_][a-zA-Z_0-9]*
|
||||
word [^ \t\r\n"]+
|
||||
|
||||
%%
|
||||
/* This happens at the beginning of each call to yylex().
|
||||
*/
|
||||
if (gArgumentType == AM_WORD_ARGS) {
|
||||
BEGIN(WORDS);
|
||||
} else if (gArgumentType == AM_BOOLEAN_ARGS) {
|
||||
BEGIN(BOOLEAN);
|
||||
}
|
||||
|
||||
/*xxx require everything to be 7-bit-clean, printable characters */
|
||||
<INITIAL>{
|
||||
{ident}/[ \t\r\n] {
|
||||
/* The only token we recognize in the initial
|
||||
* state is an identifier followed by whitespace.
|
||||
*/
|
||||
setString(&gStr, yytext);
|
||||
yylval.literalString = gStr.value;
|
||||
return TOK_IDENTIFIER;
|
||||
}
|
||||
}
|
||||
|
||||
<BOOLEAN>{
|
||||
{ident} {
|
||||
/* Non-quoted identifier-style string */
|
||||
setString(&gStr, yytext);
|
||||
yylval.literalString = gStr.value;
|
||||
return TOK_IDENTIFIER;
|
||||
}
|
||||
"&&" return TOK_AND;
|
||||
"||" return TOK_OR;
|
||||
"==" return TOK_EQ;
|
||||
"!=" return TOK_NE;
|
||||
">=" return TOK_GE;
|
||||
"<=" return TOK_LE;
|
||||
[<>()!,] return yytext[0];
|
||||
}
|
||||
|
||||
/* Double-quoted string handling */
|
||||
|
||||
<WORDS,BOOLEAN>\" {
|
||||
/* Initial quote */
|
||||
gStr.nextc = gStr.value;
|
||||
BEGIN(QUOTED_STRING);
|
||||
}
|
||||
|
||||
<QUOTED_STRING>{
|
||||
\" {
|
||||
/* Closing quote */
|
||||
BEGIN(INITIAL);
|
||||
addCharToString(&gStr, '\0');
|
||||
yylval.literalString = gStr.value;
|
||||
if (gArgumentType == AM_WORD_ARGS) {
|
||||
return TOK_WORD;
|
||||
} else {
|
||||
return TOK_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
<<EOF>> |
|
||||
\n {
|
||||
/* Unterminated string */
|
||||
yyerror("unterminated string");
|
||||
return TOK_ERROR;
|
||||
}
|
||||
|
||||
\\\" {
|
||||
/* Escaped quote */
|
||||
addCharToString(&gStr, '"');
|
||||
}
|
||||
|
||||
\\\\ {
|
||||
/* Escaped backslash */
|
||||
addCharToString(&gStr, '\\');
|
||||
}
|
||||
|
||||
\\. {
|
||||
/* No other escapes allowed. */
|
||||
gErrorMessage = "illegal escape";
|
||||
return TOK_ERROR;
|
||||
}
|
||||
|
||||
[^\\\n\"]+ {
|
||||
/* String contents */
|
||||
char *p = yytext;
|
||||
while (*p != '\0') {
|
||||
/* TODO: add the whole string at once */
|
||||
addCharToString(&gStr, *p++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<WORDS>{
|
||||
/*xxx look out for backslashes; escape backslashes and quotes */
|
||||
/*xxx if a quote is right against a char, we should append */
|
||||
{word} {
|
||||
/* Whitespace-separated word */
|
||||
setString(&gStr, yytext);
|
||||
yylval.literalString = gStr.value;
|
||||
return TOK_WORD;
|
||||
}
|
||||
}
|
||||
|
||||
<INITIAL,WORDS,BOOLEAN>{
|
||||
\n {
|
||||
/* Count lines */
|
||||
gLineNumber++;
|
||||
gArgumentType = AM_UNKNOWN_ARGS;
|
||||
BEGIN(INITIAL);
|
||||
return TOK_EOL;
|
||||
}
|
||||
|
||||
/*xxx backslashes to extend lines? */
|
||||
/* Skip whitespace and comments.
|
||||
*/
|
||||
[ \t\r]+ ;
|
||||
#.* ;
|
||||
|
||||
. {
|
||||
/* Fail on anything we didn't expect. */
|
||||
gErrorMessage = "unexpected character";
|
||||
return TOK_ERROR;
|
||||
}
|
||||
}
|
||||
%%
|
||||
|
||||
void
|
||||
yyerror(const char *msg)
|
||||
{
|
||||
if (!strcmp(msg, "syntax error") && gErrorMessage != NULL) {
|
||||
msg = gErrorMessage;
|
||||
gErrorMessage = NULL;
|
||||
}
|
||||
fprintf(stderr, "line %d: %s at '%s'\n", gLineNumber, msg, yytext);
|
||||
}
|
||||
|
||||
#if AMEND_LEXER_BUFFER_INPUT
|
||||
void
|
||||
setLexerInputBuffer(const char *buf, size_t buflen)
|
||||
{
|
||||
gLineNumber = 1;
|
||||
gInputBuffer = buf;
|
||||
gInputBufferNext = gInputBuffer;
|
||||
gInputBufferEnd = gInputBuffer + buflen;
|
||||
}
|
||||
#endif // AMEND_LEXER_BUFFER_INPUT
|
||||
|
||||
void
|
||||
setLexerArgumentType(AmArgumentType type)
|
||||
{
|
||||
gArgumentType = type;
|
||||
}
|
||||
|
||||
int
|
||||
getLexerLineNumber(void)
|
||||
{
|
||||
return gLineNumber;
|
||||
}
|
195
amend/main.c
Normal file
195
amend/main.c
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
#include "register.h"
|
||||
#include "execute.h"
|
||||
|
||||
void
|
||||
lexTest()
|
||||
{
|
||||
int token;
|
||||
do {
|
||||
token = yylex();
|
||||
if (token == 0) {
|
||||
printf(" EOF");
|
||||
fflush(stdout);
|
||||
break;
|
||||
} else {
|
||||
printf(" %s", tokenToString(token));
|
||||
fflush(stdout);
|
||||
if (token == TOK_IDENTIFIER) {
|
||||
if (strcmp(yylval.literalString, "assert") == 0) {
|
||||
setLexerArgumentType(AM_BOOLEAN_ARGS);
|
||||
} else {
|
||||
setLexerArgumentType(AM_WORD_ARGS);
|
||||
}
|
||||
do {
|
||||
token = yylex();
|
||||
printf(" %s", tokenToString(token));
|
||||
fflush(stdout);
|
||||
} while (token != TOK_EOL && token != TOK_EOF && token != 0);
|
||||
} else if (token != TOK_EOL) {
|
||||
fprintf(stderr, "syntax error: expected identifier\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (token != 0);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void
|
||||
usage()
|
||||
{
|
||||
printf("usage: amend [--debug-lex|--debug-ast] [<filename>]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
extern const AmCommandList *gCommands;
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
FILE *inputFile = NULL;
|
||||
bool debugLex = false;
|
||||
bool debugAst = false;
|
||||
const char *fileName = NULL;
|
||||
int err;
|
||||
|
||||
#if 1
|
||||
extern int test_symtab(void);
|
||||
int ret = test_symtab();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_symtab() failed: %d\n", ret);
|
||||
exit(ret);
|
||||
}
|
||||
extern int test_cmd_fn(void);
|
||||
ret = test_cmd_fn();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_cmd_fn() failed: %d\n", ret);
|
||||
exit(ret);
|
||||
}
|
||||
extern int test_permissions(void);
|
||||
ret = test_permissions();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_permissions() failed: %d\n", ret);
|
||||
exit(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
argc--;
|
||||
argv++;
|
||||
while (argc > 0) {
|
||||
if (strcmp("--debug-lex", argv[0]) == 0) {
|
||||
debugLex = true;
|
||||
} else if (strcmp("--debug-ast", argv[0]) == 0) {
|
||||
debugAst = true;
|
||||
} else if (argv[0][0] == '-') {
|
||||
fprintf(stderr, "amend: Unknown option \"%s\"\n", argv[0]);
|
||||
usage();
|
||||
} else {
|
||||
fileName = argv[0];
|
||||
}
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
|
||||
if (fileName != NULL) {
|
||||
inputFile = fopen(fileName, "r");
|
||||
if (inputFile == NULL) {
|
||||
fprintf(stderr, "amend: Can't open input file '%s'\n", fileName);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
commandInit();
|
||||
//xxx clean up
|
||||
|
||||
err = registerUpdateCommands();
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "amend: Error registering commands: %d\n", err);
|
||||
exit(-err);
|
||||
}
|
||||
err = registerUpdateFunctions();
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "amend: Error registering functions: %d\n", err);
|
||||
exit(-err);
|
||||
}
|
||||
|
||||
#if AMEND_LEXER_BUFFER_INPUT
|
||||
if (inputFile == NULL) {
|
||||
fprintf(stderr, "amend: No input file\n");
|
||||
usage();
|
||||
}
|
||||
char *fileData;
|
||||
int fileDataLen;
|
||||
fseek(inputFile, 0, SEEK_END);
|
||||
fileDataLen = ftell(inputFile);
|
||||
rewind(inputFile);
|
||||
if (fileDataLen < 0) {
|
||||
fprintf(stderr, "amend: Can't get file length\n");
|
||||
exit(2);
|
||||
} else if (fileDataLen == 0) {
|
||||
printf("amend: Empty input file\n");
|
||||
exit(0);
|
||||
}
|
||||
fileData = (char *)malloc(fileDataLen + 1);
|
||||
if (fileData == NULL) {
|
||||
fprintf(stderr, "amend: Can't allocate %d bytes\n", fileDataLen + 1);
|
||||
exit(2);
|
||||
}
|
||||
size_t nread = fread(fileData, 1, fileDataLen, inputFile);
|
||||
if (nread != (size_t)fileDataLen) {
|
||||
fprintf(stderr, "amend: Didn't read %d bytes, only %zd\n", fileDataLen,
|
||||
nread);
|
||||
exit(2);
|
||||
}
|
||||
fileData[fileDataLen] = '\0';
|
||||
setLexerInputBuffer(fileData, fileDataLen);
|
||||
#else
|
||||
if (inputFile == NULL) {
|
||||
inputFile = stdin;
|
||||
}
|
||||
yyset_in(inputFile);
|
||||
#endif
|
||||
|
||||
if (debugLex) {
|
||||
lexTest();
|
||||
} else {
|
||||
int ret = yyparse();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "amend: Parse failed (%d)\n", ret);
|
||||
exit(2);
|
||||
} else {
|
||||
if (debugAst) {
|
||||
dumpCommandList(gCommands);
|
||||
}
|
||||
printf("amend: Parse successful.\n");
|
||||
ret = execCommandList((ExecContext *)1, gCommands);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "amend: Execution failed (%d)\n", ret);
|
||||
exit(3);
|
||||
}
|
||||
printf("amend: Execution successful.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
* 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.
|
||||
@ -14,9 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _UPDATER_INSTALL_H_
|
||||
#define _UPDATER_INSTALL_H_
|
||||
#ifndef AMEND_PARSER_H_
|
||||
#define AMEND_PARSER_H_
|
||||
|
||||
void RegisterInstallFunctions();
|
||||
#include "parser_y.h"
|
||||
|
||||
#endif
|
||||
int yyparse(void);
|
||||
|
||||
#endif // AMEND_PARSER_H_
|
430
amend/parser_y.y
Normal file
430
amend/parser_y.y
Normal file
@ -0,0 +1,430 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
%{
|
||||
#undef NDEBUG
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
#include "commands.h"
|
||||
|
||||
void yyerror(const char *msg);
|
||||
int yylex(void);
|
||||
|
||||
#define STRING_COMPARISON(out, a1, sop, a2) \
|
||||
do { \
|
||||
out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
|
||||
if (out == NULL) { \
|
||||
YYABORT; \
|
||||
} \
|
||||
out->type = AM_BVAL_STRING_COMPARISON; \
|
||||
out->u.stringComparison.op = sop; \
|
||||
out->u.stringComparison.arg1 = a1; \
|
||||
out->u.stringComparison.arg2 = a2; \
|
||||
} while (false)
|
||||
|
||||
#define BOOLEAN_EXPRESSION(out, a1, bop, a2) \
|
||||
do { \
|
||||
out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
|
||||
if (out == NULL) { \
|
||||
YYABORT; \
|
||||
} \
|
||||
out->type = AM_BVAL_EXPRESSION; \
|
||||
out->u.expression.op = bop; \
|
||||
out->u.expression.arg1 = a1; \
|
||||
out->u.expression.arg2 = a2; \
|
||||
} while (false)
|
||||
|
||||
AmCommandList *gCommands = NULL;
|
||||
%}
|
||||
|
||||
%start lines
|
||||
|
||||
%union {
|
||||
char *literalString;
|
||||
AmFunctionArgumentBuilder *functionArgumentBuilder;
|
||||
AmFunctionArguments *functionArguments;
|
||||
AmFunctionCall *functionCall;
|
||||
AmStringValue *stringValue;
|
||||
AmBooleanValue *booleanValue;
|
||||
AmWordListBuilder *wordListBuilder;
|
||||
AmCommandArguments *commandArguments;
|
||||
AmCommand *command;
|
||||
AmCommandList *commandList;
|
||||
}
|
||||
|
||||
%token TOK_AND TOK_OR TOK_EQ TOK_NE TOK_GE TOK_LE TOK_EOF TOK_EOL TOK_ERROR
|
||||
%token <literalString> TOK_STRING TOK_IDENTIFIER TOK_WORD
|
||||
|
||||
%type <commandList> lines
|
||||
%type <command> command line
|
||||
%type <functionArgumentBuilder> function_arguments
|
||||
%type <functionArguments> function_arguments_or_empty
|
||||
%type <functionCall> function_call
|
||||
%type <literalString> function_name
|
||||
%type <stringValue> string_value
|
||||
%type <booleanValue> boolean_expression
|
||||
%type <wordListBuilder> word_list
|
||||
%type <commandArguments> arguments
|
||||
|
||||
/* Operator precedence, weakest to strongest.
|
||||
* Same as C/Java precedence.
|
||||
*/
|
||||
|
||||
%left TOK_OR
|
||||
%left TOK_AND
|
||||
%left TOK_EQ TOK_NE
|
||||
%left '<' '>' TOK_LE TOK_GE
|
||||
%right '!'
|
||||
|
||||
%%
|
||||
|
||||
lines : /* empty */
|
||||
{
|
||||
$$ = (AmCommandList *)malloc(sizeof(AmCommandList));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
gCommands = $$;
|
||||
$$->arraySize = 64;
|
||||
$$->commandCount = 0;
|
||||
$$->commands = (AmCommand **)malloc(
|
||||
sizeof(AmCommand *) * $$->arraySize);
|
||||
if ($$->commands == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
}
|
||||
| lines line
|
||||
{
|
||||
if ($2 != NULL) {
|
||||
if ($1->commandCount >= $1->arraySize) {
|
||||
AmCommand **newArray;
|
||||
newArray = (AmCommand **)realloc($$->commands,
|
||||
sizeof(AmCommand *) * $$->arraySize * 2);
|
||||
if (newArray == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->commands = newArray;
|
||||
$$->arraySize *= 2;
|
||||
}
|
||||
$1->commands[$1->commandCount++] = $2;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
line : line_ending
|
||||
{
|
||||
$$ = NULL; /* ignore blank lines */
|
||||
}
|
||||
| command arguments line_ending
|
||||
{
|
||||
$$ = $1;
|
||||
$$->args = $2;
|
||||
setLexerArgumentType(AM_UNKNOWN_ARGS);
|
||||
}
|
||||
;
|
||||
|
||||
command : TOK_IDENTIFIER
|
||||
{
|
||||
Command *cmd = findCommand($1);
|
||||
if (cmd == NULL) {
|
||||
fprintf(stderr, "Unknown command \"%s\"\n", $1);
|
||||
YYABORT;
|
||||
}
|
||||
$$ = (AmCommand *)malloc(sizeof(AmCommand));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->line = getLexerLineNumber();
|
||||
$$->name = strdup($1);
|
||||
if ($$->name == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->args = NULL;
|
||||
CommandArgumentType argType = getCommandArgumentType(cmd);
|
||||
if (argType == CMD_ARGS_BOOLEAN) {
|
||||
setLexerArgumentType(AM_BOOLEAN_ARGS);
|
||||
} else {
|
||||
setLexerArgumentType(AM_WORD_ARGS);
|
||||
}
|
||||
$$->cmd = cmd;
|
||||
}
|
||||
;
|
||||
|
||||
line_ending :
|
||||
TOK_EOL
|
||||
| TOK_EOF
|
||||
;
|
||||
|
||||
arguments : boolean_expression
|
||||
{
|
||||
$$ = (AmCommandArguments *)malloc(
|
||||
sizeof(AmCommandArguments));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->booleanArgs = true;
|
||||
$$->u.b = $1;
|
||||
}
|
||||
| word_list
|
||||
{
|
||||
/* Convert the builder list into an array.
|
||||
* Do it in reverse order; the words were pushed
|
||||
* onto the list in LIFO order.
|
||||
*/
|
||||
AmWordList *w = (AmWordList *)malloc(sizeof(AmWordList));
|
||||
if (w == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
if ($1 != NULL) {
|
||||
AmWordListBuilder *words = $1;
|
||||
|
||||
w->argc = words->wordCount;
|
||||
w->argv = (const char **)malloc(w->argc *
|
||||
sizeof(char *));
|
||||
if (w->argv == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
int i;
|
||||
for (i = w->argc; words != NULL && i > 0; --i) {
|
||||
AmWordListBuilder *f = words;
|
||||
w->argv[i-1] = words->word;
|
||||
words = words->next;
|
||||
free(f);
|
||||
}
|
||||
assert(i == 0);
|
||||
assert(words == NULL);
|
||||
} else {
|
||||
w->argc = 0;
|
||||
w->argv = NULL;
|
||||
}
|
||||
$$ = (AmCommandArguments *)malloc(
|
||||
sizeof(AmCommandArguments));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->booleanArgs = false;
|
||||
$$->u.w = w;
|
||||
}
|
||||
;
|
||||
|
||||
word_list : /* empty */
|
||||
{ $$ = NULL; }
|
||||
| word_list TOK_WORD
|
||||
{
|
||||
if ($1 == NULL) {
|
||||
$$ = (AmWordListBuilder *)malloc(
|
||||
sizeof(AmWordListBuilder));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->next = NULL;
|
||||
$$->wordCount = 1;
|
||||
} else {
|
||||
$$ = (AmWordListBuilder *)malloc(
|
||||
sizeof(AmWordListBuilder));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->next = $1;
|
||||
$$->wordCount = $$->next->wordCount + 1;
|
||||
}
|
||||
$$->word = strdup($2);
|
||||
if ($$->word == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
boolean_expression :
|
||||
'!' boolean_expression
|
||||
{
|
||||
$$ = (AmBooleanValue *)malloc(sizeof(AmBooleanValue));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->type = AM_BVAL_EXPRESSION;
|
||||
$$->u.expression.op = AM_BOP_NOT;
|
||||
$$->u.expression.arg1 = $2;
|
||||
$$->u.expression.arg2 = NULL;
|
||||
}
|
||||
/* TODO: if both expressions are literals, evaluate now */
|
||||
| boolean_expression TOK_AND boolean_expression
|
||||
{ BOOLEAN_EXPRESSION($$, $1, AM_BOP_AND, $3); }
|
||||
| boolean_expression TOK_OR boolean_expression
|
||||
{ BOOLEAN_EXPRESSION($$, $1, AM_BOP_OR, $3); }
|
||||
| boolean_expression TOK_EQ boolean_expression
|
||||
{ BOOLEAN_EXPRESSION($$, $1, AM_BOP_EQ, $3); }
|
||||
| boolean_expression TOK_NE boolean_expression
|
||||
{ BOOLEAN_EXPRESSION($$, $1, AM_BOP_NE, $3); }
|
||||
| '(' boolean_expression ')'
|
||||
{ $$ = $2; }
|
||||
/* TODO: if both strings are literals, evaluate now */
|
||||
| string_value '<' string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_LT, $3); }
|
||||
| string_value '>' string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_GT, $3); }
|
||||
| string_value TOK_EQ string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_EQ, $3); }
|
||||
| string_value TOK_NE string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_NE, $3); }
|
||||
| string_value TOK_LE string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_LE, $3); }
|
||||
| string_value TOK_GE string_value
|
||||
{ STRING_COMPARISON($$, $1, AM_SOP_GE, $3); }
|
||||
;
|
||||
|
||||
string_value :
|
||||
TOK_IDENTIFIER
|
||||
{
|
||||
$$ = (AmStringValue *)malloc(sizeof(AmStringValue));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->type = AM_SVAL_LITERAL;
|
||||
$$->u.literal = strdup($1);
|
||||
if ($$->u.literal == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
}
|
||||
| TOK_STRING
|
||||
{
|
||||
$$ = (AmStringValue *)malloc(sizeof(AmStringValue));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->type = AM_SVAL_LITERAL;
|
||||
$$->u.literal = strdup($1);
|
||||
if ($$->u.literal == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
}
|
||||
| function_call
|
||||
{
|
||||
$$ = (AmStringValue *)malloc(sizeof(AmStringValue));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->type = AM_SVAL_FUNCTION;
|
||||
$$->u.function = $1;
|
||||
}
|
||||
;
|
||||
|
||||
/* We can't just say
|
||||
* TOK_IDENTIFIER '(' function_arguments_or_empty ')'
|
||||
* because parsing function_arguments_or_empty will clobber
|
||||
* the underlying string that yylval.literalString points to.
|
||||
*/
|
||||
function_call :
|
||||
function_name '(' function_arguments_or_empty ')'
|
||||
{
|
||||
Function *fn = findFunction($1);
|
||||
if (fn == NULL) {
|
||||
fprintf(stderr, "Unknown function \"%s\"\n", $1);
|
||||
YYABORT;
|
||||
}
|
||||
$$ = (AmFunctionCall *)malloc(sizeof(AmFunctionCall));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->name = $1;
|
||||
if ($$->name == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->fn = fn;
|
||||
$$->args = $3;
|
||||
}
|
||||
;
|
||||
|
||||
function_name :
|
||||
TOK_IDENTIFIER
|
||||
{
|
||||
$$ = strdup($1);
|
||||
}
|
||||
;
|
||||
|
||||
function_arguments_or_empty :
|
||||
/* empty */
|
||||
{
|
||||
$$ = (AmFunctionArguments *)malloc(
|
||||
sizeof(AmFunctionArguments));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->argc = 0;
|
||||
$$->argv = NULL;
|
||||
}
|
||||
| function_arguments
|
||||
{
|
||||
AmFunctionArgumentBuilder *args = $1;
|
||||
assert(args != NULL);
|
||||
|
||||
/* Convert the builder list into an array.
|
||||
* Do it in reverse order; the args were pushed
|
||||
* onto the list in LIFO order.
|
||||
*/
|
||||
$$ = (AmFunctionArguments *)malloc(
|
||||
sizeof(AmFunctionArguments));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->argc = args->argCount;
|
||||
$$->argv = (AmStringValue *)malloc(
|
||||
$$->argc * sizeof(AmStringValue));
|
||||
if ($$->argv == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
int i;
|
||||
for (i = $$->argc; args != NULL && i > 0; --i) {
|
||||
AmFunctionArgumentBuilder *f = args;
|
||||
$$->argv[i-1] = *args->arg;
|
||||
args = args->next;
|
||||
free(f->arg);
|
||||
free(f);
|
||||
}
|
||||
assert(i == 0);
|
||||
assert(args == NULL);
|
||||
}
|
||||
;
|
||||
|
||||
function_arguments :
|
||||
string_value
|
||||
{
|
||||
$$ = (AmFunctionArgumentBuilder *)malloc(
|
||||
sizeof(AmFunctionArgumentBuilder));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->next = NULL;
|
||||
$$->argCount = 1;
|
||||
$$->arg = $1;
|
||||
}
|
||||
| function_arguments ',' string_value
|
||||
{
|
||||
$$ = (AmFunctionArgumentBuilder *)malloc(
|
||||
sizeof(AmFunctionArgumentBuilder));
|
||||
if ($$ == NULL) {
|
||||
YYABORT;
|
||||
}
|
||||
$$->next = $1;
|
||||
$$->argCount = $$->next->argCount + 1;
|
||||
$$->arg = $3;
|
||||
}
|
||||
;
|
||||
/* xxx this whole tool needs to be hardened */
|
270
amend/permissions.c
Normal file
270
amend/permissions.c
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "permissions.h"
|
||||
|
||||
int
|
||||
initPermissionRequestList(PermissionRequestList *list)
|
||||
{
|
||||
if (list != NULL) {
|
||||
list->requests = NULL;
|
||||
list->numRequests = 0;
|
||||
list->requestsAllocated = 0;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
addPermissionRequestToList(PermissionRequestList *list,
|
||||
const char *path, bool recursive, unsigned int permissions)
|
||||
{
|
||||
if (list == NULL || list->numRequests < 0 ||
|
||||
list->requestsAllocated < list->numRequests || path == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (list->numRequests == list->requestsAllocated) {
|
||||
int newSize;
|
||||
PermissionRequest *newRequests;
|
||||
|
||||
newSize = list->requestsAllocated * 2;
|
||||
if (newSize < 16) {
|
||||
newSize = 16;
|
||||
}
|
||||
newRequests = (PermissionRequest *)realloc(list->requests,
|
||||
newSize * sizeof(PermissionRequest));
|
||||
if (newRequests == NULL) {
|
||||
return -2;
|
||||
}
|
||||
list->requests = newRequests;
|
||||
list->requestsAllocated = newSize;
|
||||
}
|
||||
|
||||
PermissionRequest *req;
|
||||
req = &list->requests[list->numRequests++];
|
||||
req->path = strdup(path);
|
||||
if (req->path == NULL) {
|
||||
list->numRequests--;
|
||||
return -3;
|
||||
}
|
||||
req->recursive = recursive;
|
||||
req->requested = permissions;
|
||||
req->allowed = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
freePermissionRequestListElements(PermissionRequestList *list)
|
||||
{
|
||||
if (list != NULL && list->numRequests >= 0 &&
|
||||
list->requestsAllocated >= list->numRequests)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < list->numRequests; i++) {
|
||||
free((void *)list->requests[i].path);
|
||||
}
|
||||
free(list->requests);
|
||||
initPermissionRequestList(list);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Global permission table
|
||||
*/
|
||||
|
||||
static struct {
|
||||
Permission *permissions;
|
||||
int numPermissionEntries;
|
||||
int allocatedPermissionEntries;
|
||||
bool permissionStateInitialized;
|
||||
} gPermissionState = {
|
||||
#if 1
|
||||
NULL, 0, 0, false
|
||||
#else
|
||||
.permissions = NULL,
|
||||
.numPermissionEntries = 0,
|
||||
.allocatedPermissionEntries = 0,
|
||||
.permissionStateInitialized = false
|
||||
#endif
|
||||
};
|
||||
|
||||
int
|
||||
permissionInit()
|
||||
{
|
||||
if (gPermissionState.permissionStateInitialized) {
|
||||
return -1;
|
||||
}
|
||||
gPermissionState.permissions = NULL;
|
||||
gPermissionState.numPermissionEntries = 0;
|
||||
gPermissionState.allocatedPermissionEntries = 0;
|
||||
gPermissionState.permissionStateInitialized = true;
|
||||
//xxx maybe add an "namespace root gets no permissions" fallback by default
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
permissionCleanup()
|
||||
{
|
||||
if (gPermissionState.permissionStateInitialized) {
|
||||
gPermissionState.permissionStateInitialized = false;
|
||||
if (gPermissionState.permissions != NULL) {
|
||||
int i;
|
||||
for (i = 0; i < gPermissionState.numPermissionEntries; i++) {
|
||||
free((void *)gPermissionState.permissions[i].path);
|
||||
}
|
||||
free(gPermissionState.permissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
getPermissionCount()
|
||||
{
|
||||
if (gPermissionState.permissionStateInitialized) {
|
||||
return gPermissionState.numPermissionEntries;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const Permission *
|
||||
getPermissionAt(int index)
|
||||
{
|
||||
if (!gPermissionState.permissionStateInitialized) {
|
||||
return NULL;
|
||||
}
|
||||
if (index < 0 || index >= gPermissionState.numPermissionEntries) {
|
||||
return NULL;
|
||||
}
|
||||
return &gPermissionState.permissions[index];
|
||||
}
|
||||
|
||||
int
|
||||
getAllowedPermissions(const char *path, bool recursive,
|
||||
unsigned int *outAllowed)
|
||||
{
|
||||
if (!gPermissionState.permissionStateInitialized) {
|
||||
return -2;
|
||||
}
|
||||
if (outAllowed == NULL) {
|
||||
return -1;
|
||||
}
|
||||
*outAllowed = 0;
|
||||
if (path == NULL) {
|
||||
return -1;
|
||||
}
|
||||
//TODO: implement this for real.
|
||||
recursive = false;
|
||||
*outAllowed = PERMSET_ALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
countPermissionConflicts(PermissionRequestList *requests, bool updateAllowed)
|
||||
{
|
||||
if (!gPermissionState.permissionStateInitialized) {
|
||||
return -2;
|
||||
}
|
||||
if (requests == NULL || requests->requests == NULL ||
|
||||
requests->numRequests < 0 ||
|
||||
requests->requestsAllocated < requests->numRequests)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int conflicts = 0;
|
||||
int i;
|
||||
for (i = 0; i < requests->numRequests; i++) {
|
||||
PermissionRequest *req;
|
||||
unsigned int allowed;
|
||||
int ret;
|
||||
|
||||
req = &requests->requests[i];
|
||||
ret = getAllowedPermissions(req->path, req->recursive, &allowed);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((req->requested & ~allowed) != 0) {
|
||||
conflicts++;
|
||||
}
|
||||
if (updateAllowed) {
|
||||
req->allowed = allowed;
|
||||
}
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
int
|
||||
registerPermissionSet(int count, Permission *set)
|
||||
{
|
||||
if (!gPermissionState.permissionStateInitialized) {
|
||||
return -2;
|
||||
}
|
||||
if (count < 0 || (count > 0 && set == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (gPermissionState.numPermissionEntries + count >=
|
||||
gPermissionState.allocatedPermissionEntries)
|
||||
{
|
||||
Permission *newList;
|
||||
int newSize;
|
||||
|
||||
newSize = (gPermissionState.allocatedPermissionEntries + count) * 2;
|
||||
if (newSize < 16) {
|
||||
newSize = 16;
|
||||
}
|
||||
newList = (Permission *)realloc(gPermissionState.permissions,
|
||||
newSize * sizeof(Permission));
|
||||
if (newList == NULL) {
|
||||
return -3;
|
||||
}
|
||||
gPermissionState.permissions = newList;
|
||||
gPermissionState.allocatedPermissionEntries = newSize;
|
||||
}
|
||||
|
||||
Permission *p = &gPermissionState.permissions[
|
||||
gPermissionState.numPermissionEntries];
|
||||
int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
*p = set[i];
|
||||
//TODO: cache the strlen of the path
|
||||
//TODO: normalize; strip off trailing /
|
||||
p->path = strdup(p->path);
|
||||
if (p->path == NULL) {
|
||||
/* If we can't add all of the entries, we don't
|
||||
* add any of them.
|
||||
*/
|
||||
Permission *pp = &gPermissionState.permissions[
|
||||
gPermissionState.numPermissionEntries];
|
||||
while (pp != p) {
|
||||
free((void *)pp->path);
|
||||
pp++;
|
||||
}
|
||||
return -4;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
gPermissionState.numPermissionEntries += count;
|
||||
|
||||
return 0;
|
||||
}
|
111
amend/permissions.h
Normal file
111
amend/permissions.h
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_PERMISSIONS_H_
|
||||
#define AMEND_PERMISSIONS_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PERM_NONE (0)
|
||||
#define PERM_STAT (1<<0)
|
||||
#define PERM_READ (1<<1)
|
||||
#define PERM_WRITE (1<<2) // including create, delete, mkdir, rmdir
|
||||
#define PERM_CHMOD (1<<3)
|
||||
#define PERM_CHOWN (1<<4)
|
||||
#define PERM_CHGRP (1<<5)
|
||||
#define PERM_SETUID (1<<6)
|
||||
#define PERM_SETGID (1<<7)
|
||||
|
||||
#define PERMSET_READ (PERM_STAT | PERM_READ)
|
||||
#define PERMSET_WRITE (PERMSET_READ | PERM_WRITE)
|
||||
|
||||
#define PERMSET_ALL \
|
||||
(PERM_STAT | PERM_READ | PERM_WRITE | PERM_CHMOD | \
|
||||
PERM_CHOWN | PERM_CHGRP | PERM_SETUID | PERM_SETGID)
|
||||
|
||||
typedef struct {
|
||||
unsigned int requested;
|
||||
unsigned int allowed;
|
||||
const char *path;
|
||||
bool recursive;
|
||||
} PermissionRequest;
|
||||
|
||||
typedef struct {
|
||||
PermissionRequest *requests;
|
||||
int numRequests;
|
||||
int requestsAllocated;
|
||||
} PermissionRequestList;
|
||||
|
||||
/* Properly clear out a PermissionRequestList.
|
||||
*
|
||||
* @return 0 if list is non-NULL, negative otherwise.
|
||||
*/
|
||||
int initPermissionRequestList(PermissionRequestList *list);
|
||||
|
||||
/* Add a permission request to the list, allocating more space
|
||||
* if necessary.
|
||||
*
|
||||
* @return 0 on success or a negative value on failure.
|
||||
*/
|
||||
int addPermissionRequestToList(PermissionRequestList *list,
|
||||
const char *path, bool recursive, unsigned int permissions);
|
||||
|
||||
/* Free anything allocated by addPermissionRequestToList(). The caller
|
||||
* is responsible for freeing the actual PermissionRequestList.
|
||||
*/
|
||||
void freePermissionRequestListElements(PermissionRequestList *list);
|
||||
|
||||
|
||||
/*
|
||||
* Global permission table
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
unsigned int allowed;
|
||||
} Permission;
|
||||
|
||||
int permissionInit(void);
|
||||
void permissionCleanup(void);
|
||||
|
||||
/* Returns the allowed permissions for the path in "outAllowed".
|
||||
* Returns 0 if successful, negative if a parameter or global state
|
||||
* is bad.
|
||||
*/
|
||||
int getAllowedPermissions(const char *path, bool recursive,
|
||||
unsigned int *outAllowed);
|
||||
|
||||
/* More-recently-registered permissions override older permissions.
|
||||
*/
|
||||
int registerPermissionSet(int count, Permission *set);
|
||||
|
||||
/* Check to make sure that each request is allowed.
|
||||
*
|
||||
* @param requests The list of permission requests
|
||||
* @param updateAllowed If true, update the "allowed" field in each
|
||||
* element of the list
|
||||
* @return the number of requests that were denied, or negative if
|
||||
* an error occurred.
|
||||
*/
|
||||
int countPermissionConflicts(PermissionRequestList *requests,
|
||||
bool updateAllowed);
|
||||
|
||||
/* Inspection/testing/debugging functions
|
||||
*/
|
||||
int getPermissionCount(void);
|
||||
const Permission *getPermissionAt(int index);
|
||||
|
||||
#endif // AMEND_PERMISSIONS_H_
|
394
amend/register.c
Normal file
394
amend/register.c
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include "commands.h"
|
||||
|
||||
#include "register.h"
|
||||
|
||||
#define UNUSED(p) ((void)(p))
|
||||
|
||||
#define CHECK_BOOL() \
|
||||
do { \
|
||||
assert(argv == NULL); \
|
||||
if (argv != NULL) return -1; \
|
||||
assert(argc == true || argc == false); \
|
||||
if (argc != true && argc != false) return -1; \
|
||||
} while (false)
|
||||
|
||||
#define CHECK_WORDS() \
|
||||
do { \
|
||||
assert(argc >= 0); \
|
||||
if (argc < 0) return -1; \
|
||||
assert(argc == 0 || argv != NULL); \
|
||||
if (argc != 0 && argv == NULL) return -1; \
|
||||
if (permissions != NULL) { \
|
||||
int CW_I_; \
|
||||
for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
|
||||
assert(argv[CW_I_] != NULL); \
|
||||
if (argv[CW_I_] == NULL) return -1; \
|
||||
} \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define CHECK_FN() \
|
||||
do { \
|
||||
CHECK_WORDS(); \
|
||||
if (permissions != NULL) { \
|
||||
assert(result == NULL); \
|
||||
if (result != NULL) return -1; \
|
||||
} else { \
|
||||
assert(result != NULL); \
|
||||
if (result == NULL) return -1; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define NO_PERMS(perms) \
|
||||
do { \
|
||||
PermissionRequestList *NP_PRL_ = (perms); \
|
||||
if (NP_PRL_ != NULL) { \
|
||||
int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
|
||||
"", false, PERM_NONE); \
|
||||
if (NP_RET_ < 0) { \
|
||||
/* Returns from the calling function. \
|
||||
*/ \
|
||||
return NP_RET_; \
|
||||
} \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
/*
|
||||
* Command definitions
|
||||
*/
|
||||
|
||||
/* assert <boolexpr>
|
||||
*/
|
||||
static int
|
||||
cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_BOOL();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
/* If our argument is false, return non-zero (failure)
|
||||
* If our argument is true, return zero (success)
|
||||
*/
|
||||
if (argc) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* format <root>
|
||||
*/
|
||||
static int
|
||||
cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* copy_dir <srcdir> <dstdir>
|
||||
*/
|
||||
static int
|
||||
cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* mark <resource> dirty|clean
|
||||
*/
|
||||
static int
|
||||
cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx when marking, save the top-level hash at the mark point
|
||||
// so we can retry on failure. Otherwise the hashes won't match,
|
||||
// or someone could intentionally dirty the FS to force a downgrade
|
||||
//xxx
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* done
|
||||
*/
|
||||
static int
|
||||
cmd_done(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
registerUpdateCommands()
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerCommand("copy_dir", CMD_ARGS_WORDS, cmd_copy_dir, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
//xxx some way to fix permissions
|
||||
//xxx could have "installperms" commands that build the fs_config list
|
||||
//xxx along with a "commitperms", and any copy_dir etc. needs to see
|
||||
// a commitperms before it will work
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function definitions
|
||||
*/
|
||||
|
||||
/* update_forced()
|
||||
*
|
||||
* Returns "true" if some system setting has determined that
|
||||
* the update should happen no matter what.
|
||||
*/
|
||||
static int
|
||||
fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 0) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
name, argc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
//xxx check some global or property
|
||||
bool force = true;
|
||||
if (force) {
|
||||
*result = strdup("true");
|
||||
} else {
|
||||
*result = strdup("");
|
||||
}
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get_mark(<resource>)
|
||||
*
|
||||
* Returns the current mark associated with the provided resource.
|
||||
*/
|
||||
static int
|
||||
fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
name, argc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
//xxx look up the value
|
||||
*result = strdup("");
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hash_dir(<path-to-directory>)
|
||||
*/
|
||||
static int
|
||||
fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
|
||||
const char *dir;
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
name, argc);
|
||||
return 1;
|
||||
} else {
|
||||
dir = argv[0];
|
||||
}
|
||||
|
||||
if (permissions != NULL) {
|
||||
if (dir == NULL) {
|
||||
/* The argument is the result of another function.
|
||||
* Assume the worst case, where the function returns
|
||||
* the root.
|
||||
*/
|
||||
dir = "/";
|
||||
}
|
||||
ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
|
||||
} else {
|
||||
//xxx build and return the string
|
||||
*result = strdup("hashvalue");
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* matches(<str>, <str1> [, <strN>...])
|
||||
* If <str> matches (strcmp) any of <str1>...<strN>, returns <str>,
|
||||
* otherwise returns "".
|
||||
*
|
||||
* E.g., assert matches(hash_dir("/path"), "hash1", "hash2")
|
||||
*/
|
||||
static int
|
||||
fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
|
||||
name, argc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[0], argv[i]) == 0) {
|
||||
*result = strdup(argv[0]);
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
*result = strdup("");
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* concat(<str>, <str1> [, <strN>...])
|
||||
* Returns the concatenation of all strings.
|
||||
*/
|
||||
static int
|
||||
fn_concat(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
size_t totalLen = 0;
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
totalLen += strlen(argv[i]);
|
||||
}
|
||||
|
||||
char *s = (char *)malloc(totalLen + 1);
|
||||
if (s == NULL) {
|
||||
return -1;
|
||||
}
|
||||
s[totalLen] = '\0';
|
||||
for (i = 0; i < argc; i++) {
|
||||
//TODO: keep track of the end to avoid walking the string each time
|
||||
strcat(s, argv[i]);
|
||||
}
|
||||
*result = s;
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
registerUpdateFunctions()
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = registerFunction("update_forced", fn_update_forced, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerFunction("get_mark", fn_get_mark, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerFunction("hash_dir", fn_hash_dir, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerFunction("matches", fn_matches, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
ret = registerFunction("concat", fn_concat, NULL);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
return 0;
|
||||
}
|
23
amend/register.h
Normal file
23
amend/register.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AMEND_REGISTER_H_
|
||||
#define AMEND_REGISTER_H_
|
||||
|
||||
int registerUpdateCommands(void);
|
||||
int registerUpdateFunctions(void);
|
||||
|
||||
#endif // AMEND_REGISTER_H_
|
132
amend/symtab.c
Normal file
132
amend/symtab.c
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "symtab.h"
|
||||
|
||||
#define DEFAULT_TABLE_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
char *symbol;
|
||||
const void *cookie;
|
||||
unsigned int flags;
|
||||
} SymbolTableEntry;
|
||||
|
||||
struct SymbolTable {
|
||||
SymbolTableEntry *table;
|
||||
int numEntries;
|
||||
int maxSize;
|
||||
};
|
||||
|
||||
SymbolTable *
|
||||
createSymbolTable()
|
||||
{
|
||||
SymbolTable *tab;
|
||||
|
||||
tab = (SymbolTable *)malloc(sizeof(SymbolTable));
|
||||
if (tab != NULL) {
|
||||
tab->numEntries = 0;
|
||||
tab->maxSize = DEFAULT_TABLE_SIZE;
|
||||
tab->table = (SymbolTableEntry *)malloc(
|
||||
tab->maxSize * sizeof(SymbolTableEntry));
|
||||
if (tab->table == NULL) {
|
||||
free(tab);
|
||||
tab = NULL;
|
||||
}
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
void
|
||||
deleteSymbolTable(SymbolTable *tab)
|
||||
{
|
||||
if (tab != NULL) {
|
||||
while (tab->numEntries > 0) {
|
||||
free(tab->table[--tab->numEntries].symbol);
|
||||
}
|
||||
free(tab->table);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
findInSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (tab == NULL || symbol == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO: Sort the table and binary search
|
||||
for (i = 0; i < tab->numEntries; i++) {
|
||||
if (strcmp(tab->table[i].symbol, symbol) == 0 &&
|
||||
tab->table[i].flags == flags)
|
||||
{
|
||||
return (void *)tab->table[i].cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
|
||||
const void *cookie)
|
||||
{
|
||||
if (tab == NULL || symbol == NULL || cookie == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Make sure that this symbol isn't already in the table.
|
||||
*/
|
||||
if (findInSymbolTable(tab, symbol, flags) != NULL) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Make sure there's enough space for the new entry.
|
||||
*/
|
||||
if (tab->numEntries == tab->maxSize) {
|
||||
SymbolTableEntry *newTable;
|
||||
int newSize;
|
||||
|
||||
newSize = tab->numEntries * 2;
|
||||
if (newSize < DEFAULT_TABLE_SIZE) {
|
||||
newSize = DEFAULT_TABLE_SIZE;
|
||||
}
|
||||
newTable = (SymbolTableEntry *)realloc(tab->table,
|
||||
newSize * sizeof(SymbolTableEntry));
|
||||
if (newTable == NULL) {
|
||||
return -1;
|
||||
}
|
||||
tab->maxSize = newSize;
|
||||
tab->table = newTable;
|
||||
}
|
||||
|
||||
/* Insert the new entry.
|
||||
*/
|
||||
symbol = strdup(symbol);
|
||||
if (symbol == NULL) {
|
||||
return -1;
|
||||
}
|
||||
// TODO: Sort the table
|
||||
tab->table[tab->numEntries].symbol = (char *)symbol;
|
||||
tab->table[tab->numEntries].cookie = cookie;
|
||||
tab->table[tab->numEntries].flags = flags;
|
||||
tab->numEntries++;
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
* 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.
|
||||
@ -14,25 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _YYDEFS_H_
|
||||
#define _YYDEFS_H_
|
||||
#ifndef AMEND_SYMTAB_H_
|
||||
#define AMEND_SYMTAB_H_
|
||||
|
||||
#define YYLTYPE YYLTYPE
|
||||
typedef struct {
|
||||
int start, end;
|
||||
} YYLTYPE;
|
||||
typedef struct SymbolTable SymbolTable;
|
||||
|
||||
#define YYLLOC_DEFAULT(Current, Rhs, N) \
|
||||
do { \
|
||||
if (N) { \
|
||||
(Current).start = YYRHSLOC(Rhs, 1).start; \
|
||||
(Current).end = YYRHSLOC(Rhs, N).end; \
|
||||
} else { \
|
||||
(Current).start = YYRHSLOC(Rhs, 0).start; \
|
||||
(Current).end = YYRHSLOC(Rhs, 0).end; \
|
||||
} \
|
||||
} while (0)
|
||||
SymbolTable *createSymbolTable(void);
|
||||
|
||||
int yylex();
|
||||
void deleteSymbolTable(SymbolTable *tab);
|
||||
|
||||
#endif
|
||||
/* symbol and cookie must be non-NULL.
|
||||
*/
|
||||
int addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
|
||||
const void *cookie);
|
||||
|
||||
void *findInSymbolTable(SymbolTable *tab, const char *symbol,
|
||||
unsigned int flags);
|
||||
|
||||
#endif // AMEND_SYMTAB_H_
|
538
amend/test_commands.c
Normal file
538
amend/test_commands.c
Normal file
@ -0,0 +1,538 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include "commands.h"
|
||||
|
||||
static struct {
|
||||
bool called;
|
||||
const char *name;
|
||||
void *cookie;
|
||||
int argc;
|
||||
const char **argv;
|
||||
PermissionRequestList *permissions;
|
||||
int returnValue;
|
||||
char *functionResult;
|
||||
} gTestCommandState;
|
||||
|
||||
static int
|
||||
testCommand(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
{
|
||||
gTestCommandState.called = true;
|
||||
gTestCommandState.name = name;
|
||||
gTestCommandState.cookie = cookie;
|
||||
gTestCommandState.argc = argc;
|
||||
gTestCommandState.argv = argv;
|
||||
gTestCommandState.permissions = permissions;
|
||||
return gTestCommandState.returnValue;
|
||||
}
|
||||
|
||||
static int
|
||||
testFunction(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen, PermissionRequestList *permissions)
|
||||
{
|
||||
gTestCommandState.called = true;
|
||||
gTestCommandState.name = name;
|
||||
gTestCommandState.cookie = cookie;
|
||||
gTestCommandState.argc = argc;
|
||||
gTestCommandState.argv = argv;
|
||||
gTestCommandState.permissions = permissions;
|
||||
if (result != NULL) {
|
||||
*result = gTestCommandState.functionResult;
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
}
|
||||
return gTestCommandState.returnValue;
|
||||
}
|
||||
|
||||
static int
|
||||
test_commands()
|
||||
{
|
||||
Command *cmd;
|
||||
int ret;
|
||||
CommandArgumentType argType;
|
||||
|
||||
ret = commandInit();
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure we can't initialize twice.
|
||||
*/
|
||||
ret = commandInit();
|
||||
assert(ret < 0);
|
||||
|
||||
/* Try calling with some bad values.
|
||||
*/
|
||||
ret = registerCommand(NULL, CMD_ARGS_UNKNOWN, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = registerCommand("hello", CMD_ARGS_UNKNOWN, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = registerCommand("hello", CMD_ARGS_WORDS, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
cmd = findCommand(NULL);
|
||||
assert(cmd == NULL);
|
||||
|
||||
argType = getCommandArgumentType(NULL);
|
||||
assert((int)argType < 0);
|
||||
|
||||
ret = callCommand(NULL, -1, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = callBooleanCommand(NULL, false);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Register some commands.
|
||||
*/
|
||||
ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
|
||||
&gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerCommand("two", CMD_ARGS_WORDS, testCommand,
|
||||
&gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerCommand("bool", CMD_ARGS_BOOLEAN, testCommand,
|
||||
&gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure that all of those commands exist and that their
|
||||
* argument types are correct.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
argType = getCommandArgumentType(cmd);
|
||||
assert(argType == CMD_ARGS_WORDS);
|
||||
|
||||
cmd = findCommand("two");
|
||||
assert(cmd != NULL);
|
||||
argType = getCommandArgumentType(cmd);
|
||||
assert(argType == CMD_ARGS_WORDS);
|
||||
|
||||
cmd = findCommand("bool");
|
||||
assert(cmd != NULL);
|
||||
argType = getCommandArgumentType(cmd);
|
||||
assert(argType == CMD_ARGS_BOOLEAN);
|
||||
|
||||
/* Make sure that no similar commands exist.
|
||||
*/
|
||||
cmd = findCommand("on");
|
||||
assert(cmd == NULL);
|
||||
|
||||
cmd = findCommand("onee");
|
||||
assert(cmd == NULL);
|
||||
|
||||
/* Make sure that a double insertion fails.
|
||||
*/
|
||||
ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
|
||||
&gTestCommandState);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Make sure that bad args fail.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
|
||||
ret = callCommand(cmd, -1, NULL); // argc must be non-negative
|
||||
assert(ret < 0);
|
||||
|
||||
ret = callCommand(cmd, 1, NULL); // argv can't be NULL if argc > 0
|
||||
assert(ret < 0);
|
||||
|
||||
/* Make sure that you can't make a boolean call on a regular command.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
|
||||
ret = callBooleanCommand(cmd, false);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Make sure that you can't make a regular call on a boolean command.
|
||||
*/
|
||||
cmd = findCommand("bool");
|
||||
assert(cmd != NULL);
|
||||
|
||||
ret = callCommand(cmd, 0, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Set up some arguments.
|
||||
*/
|
||||
int argc = 4;
|
||||
const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
|
||||
|
||||
/* Make a call and make sure that it occurred.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 25;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
ret = callCommand(cmd, argc, argv);
|
||||
//xxx also try calling with a null argv element (should fail)
|
||||
assert(ret == 25);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
|
||||
/* Make a boolean call and make sure that it occurred.
|
||||
*/
|
||||
cmd = findCommand("bool");
|
||||
assert(cmd != NULL);
|
||||
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 12;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
ret = callBooleanCommand(cmd, false);
|
||||
assert(ret == 12);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "bool") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == 0);
|
||||
assert(gTestCommandState.argv == NULL);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 13;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
ret = callBooleanCommand(cmd, true);
|
||||
assert(ret == 13);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "bool") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == 1);
|
||||
assert(gTestCommandState.argv == NULL);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
|
||||
/* Try looking up permissions.
|
||||
*/
|
||||
PermissionRequestList permissions;
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 27;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
argv[1] = NULL; // null out an arg, which should be ok
|
||||
ret = getCommandPermissions(cmd, argc, argv, &permissions);
|
||||
assert(ret == 27);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == &permissions);
|
||||
|
||||
/* Boolean command permissions
|
||||
*/
|
||||
cmd = findCommand("bool");
|
||||
assert(cmd != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 55;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
// argv[1] is still NULL
|
||||
ret = getBooleanCommandPermissions(cmd, true, &permissions);
|
||||
assert(ret == 55);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "bool") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == 1);
|
||||
assert(gTestCommandState.argv == NULL);
|
||||
assert(gTestCommandState.permissions == &permissions);
|
||||
|
||||
|
||||
/* Smoke test commandCleanup().
|
||||
*/
|
||||
commandCleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_functions()
|
||||
{
|
||||
Function *fn;
|
||||
int ret;
|
||||
|
||||
ret = commandInit();
|
||||
assert(ret == 0);
|
||||
|
||||
/* Try calling with some bad values.
|
||||
*/
|
||||
ret = registerFunction(NULL, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = registerFunction("hello", NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
fn = findFunction(NULL);
|
||||
assert(fn == NULL);
|
||||
|
||||
ret = callFunction(NULL, -1, NULL, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Register some functions.
|
||||
*/
|
||||
ret = registerFunction("one", testFunction, &gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerFunction("two", testFunction, &gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerFunction("three", testFunction, &gTestCommandState);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure that all of those functions exist.
|
||||
* argument types are correct.
|
||||
*/
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
|
||||
fn = findFunction("two");
|
||||
assert(fn != NULL);
|
||||
|
||||
fn = findFunction("three");
|
||||
assert(fn != NULL);
|
||||
|
||||
/* Make sure that no similar functions exist.
|
||||
*/
|
||||
fn = findFunction("on");
|
||||
assert(fn == NULL);
|
||||
|
||||
fn = findFunction("onee");
|
||||
assert(fn == NULL);
|
||||
|
||||
/* Make sure that a double insertion fails.
|
||||
*/
|
||||
ret = registerFunction("one", testFunction, &gTestCommandState);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Make sure that bad args fail.
|
||||
*/
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
|
||||
// argc must be non-negative
|
||||
ret = callFunction(fn, -1, NULL, (char **)1, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
// argv can't be NULL if argc > 0
|
||||
ret = callFunction(fn, 1, NULL, (char **)1, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
// result can't be NULL
|
||||
ret = callFunction(fn, 0, NULL, NULL, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Set up some arguments.
|
||||
*/
|
||||
int argc = 4;
|
||||
const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
|
||||
|
||||
/* Make a call and make sure that it occurred.
|
||||
*/
|
||||
char *functionResult;
|
||||
size_t functionResultLen;
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 25;
|
||||
gTestCommandState.functionResult = "1234";
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
functionResult = NULL;
|
||||
functionResultLen = 55;
|
||||
ret = callFunction(fn, argc, argv,
|
||||
&functionResult, &functionResultLen);
|
||||
//xxx also try calling with a null resultLen arg (should succeed)
|
||||
//xxx also try calling with a null argv element (should fail)
|
||||
assert(ret == 25);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
assert(strcmp(functionResult, "1234") == 0);
|
||||
assert(functionResultLen == strlen(functionResult));
|
||||
|
||||
/* Try looking up permissions.
|
||||
*/
|
||||
PermissionRequestList permissions;
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 27;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
argv[1] = NULL; // null out an arg, which should be ok
|
||||
ret = getFunctionPermissions(fn, argc, argv, &permissions);
|
||||
assert(ret == 27);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert(gTestCommandState.cookie == &gTestCommandState);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == &permissions);
|
||||
|
||||
/* Smoke test commandCleanup().
|
||||
*/
|
||||
commandCleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_interaction()
|
||||
{
|
||||
Command *cmd;
|
||||
Function *fn;
|
||||
int ret;
|
||||
|
||||
ret = commandInit();
|
||||
assert(ret == 0);
|
||||
|
||||
/* Register some commands.
|
||||
*/
|
||||
ret = registerCommand("one", CMD_ARGS_WORDS, testCommand, (void *)0xc1);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerCommand("two", CMD_ARGS_WORDS, testCommand, (void *)0xc2);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Register some functions, one of which shares a name with a command.
|
||||
*/
|
||||
ret = registerFunction("one", testFunction, (void *)0xf1);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = registerFunction("three", testFunction, (void *)0xf3);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Look up each of the commands, and make sure no command exists
|
||||
* with the name used only by our function.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
|
||||
cmd = findCommand("two");
|
||||
assert(cmd != NULL);
|
||||
|
||||
cmd = findCommand("three");
|
||||
assert(cmd == NULL);
|
||||
|
||||
/* Look up each of the functions, and make sure no function exists
|
||||
* with the name used only by our command.
|
||||
*/
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
|
||||
fn = findFunction("two");
|
||||
assert(fn == NULL);
|
||||
|
||||
fn = findFunction("three");
|
||||
assert(fn != NULL);
|
||||
|
||||
/* Set up some arguments.
|
||||
*/
|
||||
int argc = 4;
|
||||
const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
|
||||
|
||||
/* Call the overlapping command and make sure that the cookie is correct.
|
||||
*/
|
||||
cmd = findCommand("one");
|
||||
assert(cmd != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 123;
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
ret = callCommand(cmd, argc, argv);
|
||||
assert(ret == 123);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert((int)gTestCommandState.cookie == 0xc1);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
|
||||
/* Call the overlapping function and make sure that the cookie is correct.
|
||||
*/
|
||||
char *functionResult;
|
||||
size_t functionResultLen;
|
||||
fn = findFunction("one");
|
||||
assert(fn != NULL);
|
||||
memset(&gTestCommandState, 0, sizeof(gTestCommandState));
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 125;
|
||||
gTestCommandState.functionResult = "5678";
|
||||
gTestCommandState.permissions = (PermissionRequestList *)2;
|
||||
functionResult = NULL;
|
||||
functionResultLen = 66;
|
||||
ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
|
||||
assert(ret == 125);
|
||||
assert(gTestCommandState.called);
|
||||
assert(strcmp(gTestCommandState.name, "one") == 0);
|
||||
assert((int)gTestCommandState.cookie == 0xf1);
|
||||
assert(gTestCommandState.argc == argc);
|
||||
assert(gTestCommandState.argv == argv);
|
||||
assert(gTestCommandState.permissions == NULL);
|
||||
assert(strcmp(functionResult, "5678") == 0);
|
||||
assert(functionResultLen == strlen(functionResult));
|
||||
|
||||
/* Clean up.
|
||||
*/
|
||||
commandCleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
test_cmd_fn()
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = test_commands();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_commands() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = test_functions();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_functions() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = test_interaction();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_interaction() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
347
amend/test_permissions.c
Normal file
347
amend/test_permissions.c
Normal file
@ -0,0 +1,347 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include "permissions.h"
|
||||
|
||||
static int
|
||||
test_permission_list()
|
||||
{
|
||||
PermissionRequestList list;
|
||||
int ret;
|
||||
int numRequests;
|
||||
|
||||
/* Bad parameter
|
||||
*/
|
||||
ret = initPermissionRequestList(NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Good parameter
|
||||
*/
|
||||
ret = initPermissionRequestList(&list);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Bad parameters
|
||||
*/
|
||||
ret = addPermissionRequestToList(NULL, NULL, false, 0);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = addPermissionRequestToList(&list, NULL, false, 0);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Good parameters
|
||||
*/
|
||||
numRequests = 0;
|
||||
|
||||
ret = addPermissionRequestToList(&list, "one", false, 1);
|
||||
assert(ret == 0);
|
||||
numRequests++;
|
||||
|
||||
ret = addPermissionRequestToList(&list, "two", false, 2);
|
||||
assert(ret == 0);
|
||||
numRequests++;
|
||||
|
||||
ret = addPermissionRequestToList(&list, "three", false, 3);
|
||||
assert(ret == 0);
|
||||
numRequests++;
|
||||
|
||||
ret = addPermissionRequestToList(&list, "recursive", true, 55);
|
||||
assert(ret == 0);
|
||||
numRequests++;
|
||||
|
||||
/* Validate the list
|
||||
*/
|
||||
assert(list.requests != NULL);
|
||||
assert(list.numRequests == numRequests);
|
||||
assert(list.numRequests <= list.requestsAllocated);
|
||||
bool sawOne = false;
|
||||
bool sawTwo = false;
|
||||
bool sawThree = false;
|
||||
bool sawRecursive = false;
|
||||
int i;
|
||||
for (i = 0; i < list.numRequests; i++) {
|
||||
PermissionRequest *req = &list.requests[i];
|
||||
assert(req->allowed == 0);
|
||||
|
||||
/* Order isn't guaranteed, so we have to switch every time.
|
||||
*/
|
||||
if (strcmp(req->path, "one") == 0) {
|
||||
assert(!sawOne);
|
||||
assert(req->requested == 1);
|
||||
assert(!req->recursive);
|
||||
sawOne = true;
|
||||
} else if (strcmp(req->path, "two") == 0) {
|
||||
assert(!sawTwo);
|
||||
assert(req->requested == 2);
|
||||
assert(!req->recursive);
|
||||
sawTwo = true;
|
||||
} else if (strcmp(req->path, "three") == 0) {
|
||||
assert(!sawThree);
|
||||
assert(req->requested == 3);
|
||||
assert(!req->recursive);
|
||||
sawThree = true;
|
||||
} else if (strcmp(req->path, "recursive") == 0) {
|
||||
assert(!sawRecursive);
|
||||
assert(req->requested == 55);
|
||||
assert(req->recursive);
|
||||
sawRecursive = true;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
assert(sawOne);
|
||||
assert(sawTwo);
|
||||
assert(sawThree);
|
||||
assert(sawRecursive);
|
||||
|
||||
/* Smoke test the teardown
|
||||
*/
|
||||
freePermissionRequestListElements(&list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_permission_table()
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Test the global permissions table.
|
||||
* Try calling functions without initializing first.
|
||||
*/
|
||||
ret = registerPermissionSet(0, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = countPermissionConflicts((PermissionRequestList *)16, false);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = getPermissionCount();
|
||||
assert(ret < 0);
|
||||
|
||||
const Permission *p;
|
||||
p = getPermissionAt(0);
|
||||
assert(p == NULL);
|
||||
|
||||
/* Initialize.
|
||||
*/
|
||||
ret = permissionInit();
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure we can't initialize twice.
|
||||
*/
|
||||
ret = permissionInit();
|
||||
assert(ret < 0);
|
||||
|
||||
/* Test the inspection functions.
|
||||
*/
|
||||
ret = getPermissionCount();
|
||||
assert(ret == 0);
|
||||
|
||||
p = getPermissionAt(-1);
|
||||
assert(p == NULL);
|
||||
|
||||
p = getPermissionAt(0);
|
||||
assert(p == NULL);
|
||||
|
||||
p = getPermissionAt(1);
|
||||
assert(p == NULL);
|
||||
|
||||
/* Test registerPermissionSet().
|
||||
* Try some bad parameter values.
|
||||
*/
|
||||
ret = registerPermissionSet(-1, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = registerPermissionSet(1, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Register some permissions.
|
||||
*/
|
||||
Permission p1;
|
||||
p1.path = "one";
|
||||
p1.allowed = 1;
|
||||
ret = registerPermissionSet(1, &p1);
|
||||
assert(ret == 0);
|
||||
ret = getPermissionCount();
|
||||
assert(ret == 1);
|
||||
|
||||
Permission p2[2];
|
||||
p2[0].path = "two";
|
||||
p2[0].allowed = 2;
|
||||
p2[1].path = "three";
|
||||
p2[1].allowed = 3;
|
||||
ret = registerPermissionSet(2, p2);
|
||||
assert(ret == 0);
|
||||
ret = getPermissionCount();
|
||||
assert(ret == 3);
|
||||
|
||||
ret = registerPermissionSet(0, NULL);
|
||||
assert(ret == 0);
|
||||
ret = getPermissionCount();
|
||||
assert(ret == 3);
|
||||
|
||||
p1.path = "four";
|
||||
p1.allowed = 4;
|
||||
ret = registerPermissionSet(1, &p1);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure the table looks correct.
|
||||
* Order is important; more-recent additions
|
||||
* should appear at higher indices.
|
||||
*/
|
||||
ret = getPermissionCount();
|
||||
assert(ret == 4);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ret; i++) {
|
||||
const Permission *p;
|
||||
p = getPermissionAt(i);
|
||||
assert(p != NULL);
|
||||
assert(p->allowed == (unsigned int)(i + 1));
|
||||
switch (i) {
|
||||
case 0:
|
||||
assert(strcmp(p->path, "one") == 0);
|
||||
break;
|
||||
case 1:
|
||||
assert(strcmp(p->path, "two") == 0);
|
||||
break;
|
||||
case 2:
|
||||
assert(strcmp(p->path, "three") == 0);
|
||||
break;
|
||||
case 3:
|
||||
assert(strcmp(p->path, "four") == 0);
|
||||
break;
|
||||
default:
|
||||
assert(!"internal error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
p = getPermissionAt(ret);
|
||||
assert(p == NULL);
|
||||
|
||||
/* Smoke test the teardown
|
||||
*/
|
||||
permissionCleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_allowed_permissions()
|
||||
{
|
||||
int ret;
|
||||
int numPerms;
|
||||
|
||||
/* Make sure these fail before initialization.
|
||||
*/
|
||||
ret = countPermissionConflicts((PermissionRequestList *)1, false);
|
||||
assert(ret < 0);
|
||||
|
||||
ret = getAllowedPermissions((const char *)1, false, (unsigned int *)1);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Initialize.
|
||||
*/
|
||||
ret = permissionInit();
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure countPermissionConflicts() fails with bad parameters.
|
||||
*/
|
||||
ret = countPermissionConflicts(NULL, false);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Register a set of permissions.
|
||||
*/
|
||||
Permission perms[] = {
|
||||
{ "/", PERM_NONE },
|
||||
{ "/stat", PERM_STAT },
|
||||
{ "/read", PERMSET_READ },
|
||||
{ "/write", PERMSET_WRITE },
|
||||
{ "/.stat", PERM_STAT },
|
||||
{ "/.stat/.read", PERMSET_READ },
|
||||
{ "/.stat/.read/.write", PERMSET_WRITE },
|
||||
{ "/.stat/.write", PERMSET_WRITE },
|
||||
};
|
||||
numPerms = sizeof(perms) / sizeof(perms[0]);
|
||||
ret = registerPermissionSet(numPerms, perms);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Build a permission request list.
|
||||
*/
|
||||
PermissionRequestList list;
|
||||
ret = initPermissionRequestList(&list);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = addPermissionRequestToList(&list, "/stat", false, PERM_STAT);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = addPermissionRequestToList(&list, "/read", false, PERM_READ);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = addPermissionRequestToList(&list, "/write", false, PERM_WRITE);
|
||||
assert(ret == 0);
|
||||
|
||||
//TODO: cover more cases once the permission stuff has been implemented
|
||||
|
||||
/* All of the requests in the list should be allowed.
|
||||
*/
|
||||
ret = countPermissionConflicts(&list, false);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Add a request that will be denied.
|
||||
*/
|
||||
ret = addPermissionRequestToList(&list, "/stat", false, 1<<31 | PERM_STAT);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = countPermissionConflicts(&list, false);
|
||||
assert(ret == 1);
|
||||
|
||||
//TODO: more tests
|
||||
|
||||
permissionCleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
test_permissions()
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = test_permission_list();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_permission_list() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = test_permission_table();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_permission_table() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = test_allowed_permissions();
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "test_permission_table() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
146
amend/test_symtab.c
Normal file
146
amend/test_symtab.c
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include "symtab.h"
|
||||
|
||||
int
|
||||
test_symtab()
|
||||
{
|
||||
SymbolTable *tab;
|
||||
void *cookie;
|
||||
int ret;
|
||||
|
||||
/* Test creation */
|
||||
tab = createSymbolTable();
|
||||
assert(tab != NULL);
|
||||
|
||||
/* Smoke-test deletion */
|
||||
deleteSymbolTable(tab);
|
||||
|
||||
|
||||
tab = createSymbolTable();
|
||||
assert(tab != NULL);
|
||||
|
||||
|
||||
/* table parameter must be non-NULL. */
|
||||
ret = addToSymbolTable(NULL, NULL, 0, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* symbol parameter must be non-NULL. */
|
||||
ret = addToSymbolTable(tab, NULL, 0, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
/* cookie parameter must be non-NULL. */
|
||||
ret = addToSymbolTable(tab, "null", 0, NULL);
|
||||
assert(ret < 0);
|
||||
|
||||
|
||||
/* table parameter must be non-NULL. */
|
||||
cookie = findInSymbolTable(NULL, NULL, 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
/* symbol parameter must be non-NULL. */
|
||||
cookie = findInSymbolTable(tab, NULL, 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
|
||||
/* Try some actual inserts.
|
||||
*/
|
||||
ret = addToSymbolTable(tab, "one", 0, (void *)1);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = addToSymbolTable(tab, "two", 0, (void *)2);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = addToSymbolTable(tab, "three", 0, (void *)3);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Try some lookups.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "one", 0);
|
||||
assert((int)cookie == 1);
|
||||
|
||||
cookie = findInSymbolTable(tab, "two", 0);
|
||||
assert((int)cookie == 2);
|
||||
|
||||
cookie = findInSymbolTable(tab, "three", 0);
|
||||
assert((int)cookie == 3);
|
||||
|
||||
/* Try to insert something that's already there.
|
||||
*/
|
||||
ret = addToSymbolTable(tab, "one", 0, (void *)1111);
|
||||
assert(ret < 0);
|
||||
|
||||
/* Make sure that the failed duplicate insert didn't
|
||||
* clobber the original cookie value.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "one", 0);
|
||||
assert((int)cookie == 1);
|
||||
|
||||
/* Try looking up something that isn't there.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "FOUR", 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
/* Try looking up something that's similar to an existing entry.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "on", 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
cookie = findInSymbolTable(tab, "onee", 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
/* Test flags.
|
||||
* Try inserting something with a different flag.
|
||||
*/
|
||||
ret = addToSymbolTable(tab, "ten", 333, (void *)10);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure it's there.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "ten", 333);
|
||||
assert((int)cookie == 10);
|
||||
|
||||
/* Make sure it's not there when looked up with a different flag.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "ten", 0);
|
||||
assert(cookie == NULL);
|
||||
|
||||
/* Try inserting something that has the same name as something
|
||||
* with a different flag.
|
||||
*/
|
||||
ret = addToSymbolTable(tab, "one", 333, (void *)11);
|
||||
assert(ret == 0);
|
||||
|
||||
/* Make sure the new entry exists.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "one", 333);
|
||||
assert((int)cookie == 11);
|
||||
|
||||
/* Make sure the old entry still has the right value.
|
||||
*/
|
||||
cookie = findInSymbolTable(tab, "one", 0);
|
||||
assert((int)cookie == 1);
|
||||
|
||||
/* Try deleting again, now that there's stuff in the table.
|
||||
*/
|
||||
deleteSymbolTable(tab);
|
||||
|
||||
return 0;
|
||||
}
|
1
amend/tests/001-nop/expected.txt
Normal file
1
amend/tests/001-nop/expected.txt
Normal file
@ -0,0 +1 @@
|
||||
I am a jelly donut.
|
2
amend/tests/001-nop/info.txt
Normal file
2
amend/tests/001-nop/info.txt
Normal file
@ -0,0 +1,2 @@
|
||||
This is a sample no-op test, which does at least serve to verify that the
|
||||
test harness is working.
|
@ -1,10 +1,12 @@
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#!/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
|
||||
# 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,
|
||||
@ -12,16 +14,4 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
Retouch.c
|
||||
|
||||
LOCAL_C_INCLUDES += bootable/recovery
|
||||
|
||||
LOCAL_MODULE := libminelf
|
||||
|
||||
LOCAL_CFLAGS += -Wall
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
echo 'I am a jelly donut.'
|
0
amend/tests/002-lex-empty/SKIP
Normal file
0
amend/tests/002-lex-empty/SKIP
Normal file
1
amend/tests/002-lex-empty/expected.txt
Normal file
1
amend/tests/002-lex-empty/expected.txt
Normal file
@ -0,0 +1 @@
|
||||
EOF
|
1
amend/tests/002-lex-empty/info.txt
Normal file
1
amend/tests/002-lex-empty/info.txt
Normal file
@ -0,0 +1 @@
|
||||
Test to make sure that an empty file is accepted properly.
|
0
amend/tests/002-lex-empty/input
Normal file
0
amend/tests/002-lex-empty/input
Normal file
17
amend/tests/002-lex-empty/run
Normal file
17
amend/tests/002-lex-empty/run
Normal file
@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
13
amend/tests/003-lex-command/expected.txt
Normal file
13
amend/tests/003-lex-command/expected.txt
Normal file
@ -0,0 +1,13 @@
|
||||
IDENTIFIER<this_identifier_is_not_assert> EOL
|
||||
IDENTIFIER<NEITHER_IS_THIS_123> EOL
|
||||
IDENTIFIER<but_the_next_one_is> EOL
|
||||
IDENTIFIER<assert> EOL
|
||||
IDENTIFIER<next_one_is_not_an_identifier> EOL
|
||||
line 6: unexpected character at '1'
|
||||
EOF
|
||||
line 1: unexpected character at '"'
|
||||
EOF
|
||||
line 1: unexpected character at '='
|
||||
EOF
|
||||
line 1: unexpected character at '9'
|
||||
EOF
|
1
amend/tests/003-lex-command/info.txt
Normal file
1
amend/tests/003-lex-command/info.txt
Normal file
@ -0,0 +1 @@
|
||||
Test to make sure that simple command names are tokenized properly.
|
6
amend/tests/003-lex-command/input
Normal file
6
amend/tests/003-lex-command/input
Normal file
@ -0,0 +1,6 @@
|
||||
this_identifier_is_not_assert
|
||||
NEITHER_IS_THIS_123
|
||||
but_the_next_one_is
|
||||
assert
|
||||
next_one_is_not_an_identifier
|
||||
12not_an_identifier
|
1
amend/tests/003-lex-command/input2
Normal file
1
amend/tests/003-lex-command/input2
Normal file
@ -0,0 +1 @@
|
||||
"quoted"
|
1
amend/tests/003-lex-command/input3
Normal file
1
amend/tests/003-lex-command/input3
Normal file
@ -0,0 +1 @@
|
||||
==
|
1
amend/tests/003-lex-command/input4
Normal file
1
amend/tests/003-lex-command/input4
Normal file
@ -0,0 +1 @@
|
||||
99
|
20
amend/tests/003-lex-command/run
Normal file
20
amend/tests/003-lex-command/run
Normal file
@ -0,0 +1,20 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
||||
amend --debug-lex input2
|
||||
amend --debug-lex input3
|
||||
amend --debug-lex input4
|
5
amend/tests/004-lex-comment/expected.txt
Normal file
5
amend/tests/004-lex-comment/expected.txt
Normal file
@ -0,0 +1,5 @@
|
||||
IDENTIFIER<comment_on_this_line> EOL
|
||||
IDENTIFIER<none_on_this_one> EOL
|
||||
EOL
|
||||
EOL
|
||||
EOF
|
1
amend/tests/004-lex-comment/info.txt
Normal file
1
amend/tests/004-lex-comment/info.txt
Normal file
@ -0,0 +1 @@
|
||||
Test to make sure that comments are stripped out.
|
4
amend/tests/004-lex-comment/input
Normal file
4
amend/tests/004-lex-comment/input
Normal file
@ -0,0 +1,4 @@
|
||||
comment_on_this_line # this is a "comment" (with / a bunch) # \\ of stuff \
|
||||
none_on_this_one
|
||||
# beginning of line
|
||||
# preceded by whitespace
|
17
amend/tests/004-lex-comment/run
Normal file
17
amend/tests/004-lex-comment/run
Normal file
@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
13
amend/tests/005-lex-quoted-string/expected.txt
Normal file
13
amend/tests/005-lex-quoted-string/expected.txt
Normal file
@ -0,0 +1,13 @@
|
||||
IDENTIFIER<test> WORD<string> EOL
|
||||
IDENTIFIER<test> WORD<string with spaces> EOL
|
||||
IDENTIFIER<test> WORD<string with "escaped" quotes> EOL
|
||||
IDENTIFIER<test> WORD<string with \escaped\ backslashes> EOL
|
||||
IDENTIFIER<test> WORD<string with # a comment character> EOL
|
||||
EOF
|
||||
EOL
|
||||
IDENTIFIER<test1>line 2: unterminated string at '
|
||||
'
|
||||
??? <0>
|
||||
EOL
|
||||
IDENTIFIER<test1>line 2: illegal escape at '\n'
|
||||
??? <0>
|
1
amend/tests/005-lex-quoted-string/info.txt
Normal file
1
amend/tests/005-lex-quoted-string/info.txt
Normal file
@ -0,0 +1 @@
|
||||
Test to make sure that quoted strings are tokenized properly.
|
5
amend/tests/005-lex-quoted-string/input
Normal file
5
amend/tests/005-lex-quoted-string/input
Normal file
@ -0,0 +1,5 @@
|
||||
test "string"
|
||||
test "string with spaces"
|
||||
test "string with \"escaped\" quotes"
|
||||
test "string with \\escaped\\ backslashes"
|
||||
test "string with # a comment character"
|
2
amend/tests/005-lex-quoted-string/input2
Normal file
2
amend/tests/005-lex-quoted-string/input2
Normal file
@ -0,0 +1,2 @@
|
||||
# This should fail
|
||||
test1 "unterminated string
|
2
amend/tests/005-lex-quoted-string/input3
Normal file
2
amend/tests/005-lex-quoted-string/input3
Normal file
@ -0,0 +1,2 @@
|
||||
# This should fail
|
||||
test1 "string with illegal escape \n in the middle"
|
19
amend/tests/005-lex-quoted-string/run
Normal file
19
amend/tests/005-lex-quoted-string/run
Normal file
@ -0,0 +1,19 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
||||
amend --debug-lex input2
|
||||
amend --debug-lex input3
|
0
amend/tests/006-lex-words/SKIP
Normal file
0
amend/tests/006-lex-words/SKIP
Normal file
6
amend/tests/006-lex-words/expected.txt
Normal file
6
amend/tests/006-lex-words/expected.txt
Normal file
@ -0,0 +1,6 @@
|
||||
IDENTIFIER<test> WORD<this> WORD<has> WORD<a> WORD<bunch> WORD<of> WORD<BARE> WORD<ALPHA> WORD<WORDS> EOL
|
||||
IDENTIFIER<test> WORD<12> WORD<this> WORD<has(some> WORD<)> WORD<ALPHANUMER1C> WORD<and> WORD<\\> WORD<whatever> WORD<characters> EOL
|
||||
IDENTIFIER<test> WORD<this> WORD<has> WORD<mixed> WORD<bare> WORD<and quoted> WORD<words> EOL
|
||||
IDENTIFIER<test> WORD<what> WORD<about> WORD<quotesin the middle?> EOL
|
||||
IDENTIFIER<test> WORD<"""shouldn't> WORD<be> WORD<a> WORD<quoted> WORD<string> EOL
|
||||
EOF
|
1
amend/tests/006-lex-words/info.txt
Normal file
1
amend/tests/006-lex-words/info.txt
Normal file
@ -0,0 +1 @@
|
||||
Test to make sure that argument words are tokenized properly.
|
5
amend/tests/006-lex-words/input
Normal file
5
amend/tests/006-lex-words/input
Normal file
@ -0,0 +1,5 @@
|
||||
test this has a bunch of BARE ALPHA WORDS
|
||||
test 12 this has(some ) ALPHANUMER1C and \\ whatever characters
|
||||
test this has mixed bare "and quoted" words
|
||||
test what about quotes"in the middle?"
|
||||
test \"\"\"shouldn't be a quoted string
|
2
amend/tests/006-lex-words/input2
Normal file
2
amend/tests/006-lex-words/input2
Normal file
@ -0,0 +1,2 @@
|
||||
# This should fail
|
||||
test1 "unterminated string
|
2
amend/tests/006-lex-words/input3
Normal file
2
amend/tests/006-lex-words/input3
Normal file
@ -0,0 +1,2 @@
|
||||
# This should fail
|
||||
test1 "string with illegal escape \n in the middle"
|
17
amend/tests/006-lex-words/run
Normal file
17
amend/tests/006-lex-words/run
Normal file
@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
11
amend/tests/007-lex-real-script/expected.txt
Normal file
11
amend/tests/007-lex-real-script/expected.txt
Normal file
@ -0,0 +1,11 @@
|
||||
IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<112345oldhashvalue1234123> EOL
|
||||
IDENTIFIER<mark> WORD<SYS:> WORD<dirty> EOL
|
||||
IDENTIFIER<copy_dir> WORD<PKG:android-files> WORD<SYS:> EOL
|
||||
IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
|
||||
IDENTIFIER<mark> WORD<SYS:> WORD<clean> EOL
|
||||
IDENTIFIER<done> EOL
|
||||
IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> , STRING<blah> ) == STRING<112345oldhashvalue1234123> EOL
|
||||
IDENTIFIER<assert> STRING<true> == STRING<false> EOL
|
||||
IDENTIFIER<assert> IDENTIFIER<one> ( STRING<abc> , IDENTIFIER<two> ( STRING<def> ) ) == STRING<five> EOL
|
||||
IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> || IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
|
||||
EOF
|
1
amend/tests/007-lex-real-script/info.txt
Normal file
1
amend/tests/007-lex-real-script/info.txt
Normal file
@ -0,0 +1 @@
|
||||
An input script similar to one that will actually be used in practice.
|
10
amend/tests/007-lex-real-script/input
Normal file
10
amend/tests/007-lex-real-script/input
Normal file
@ -0,0 +1,10 @@
|
||||
assert hash_dir("SYS:") == "112345oldhashvalue1234123"
|
||||
mark SYS: dirty
|
||||
copy_dir "PKG:android-files" SYS:
|
||||
assert hash_dir("SYS:") == "667890newhashvalue6678909"
|
||||
mark SYS: clean
|
||||
done
|
||||
assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
|
||||
assert "true" == "false"
|
||||
assert one("abc", two("def")) == "five"
|
||||
assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "667890newhashvalue6678909"
|
17
amend/tests/007-lex-real-script/run
Normal file
17
amend/tests/007-lex-real-script/run
Normal file
@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-lex input
|
74
amend/tests/008-parse-real-script/expected.txt
Normal file
74
amend/tests/008-parse-real-script/expected.txt
Normal file
@ -0,0 +1,74 @@
|
||||
command "assert" {
|
||||
STRING EQ {
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
)
|
||||
"112345oldhashvalue1234123"
|
||||
}
|
||||
}
|
||||
command "mark" {
|
||||
"SYS:"
|
||||
"dirty"
|
||||
}
|
||||
command "copy_dir" {
|
||||
"PKG:android-files"
|
||||
"SYS:"
|
||||
}
|
||||
command "assert" {
|
||||
STRING EQ {
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
)
|
||||
"667890newhashvalue6678909"
|
||||
}
|
||||
}
|
||||
command "mark" {
|
||||
"SYS:"
|
||||
"clean"
|
||||
}
|
||||
command "done" {
|
||||
}
|
||||
command "assert" {
|
||||
STRING EQ {
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
"blah"
|
||||
)
|
||||
"112345oldhashvalue1234123"
|
||||
}
|
||||
}
|
||||
command "assert" {
|
||||
STRING EQ {
|
||||
"true"
|
||||
"false"
|
||||
}
|
||||
}
|
||||
command "assert" {
|
||||
STRING NE {
|
||||
FUNCTION matches (
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
)
|
||||
"667890newhashvalue6678909"
|
||||
"999999newhashvalue6678909"
|
||||
)
|
||||
""
|
||||
}
|
||||
}
|
||||
command "assert" {
|
||||
BOOLEAN OR {
|
||||
STRING EQ {
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
)
|
||||
"667890newhashvalue6678909"
|
||||
}
|
||||
STRING EQ {
|
||||
FUNCTION hash_dir (
|
||||
"SYS:"
|
||||
)
|
||||
"999999newhashvalue6678909"
|
||||
}
|
||||
}
|
||||
}
|
||||
amend: Parse successful.
|
1
amend/tests/008-parse-real-script/info.txt
Normal file
1
amend/tests/008-parse-real-script/info.txt
Normal file
@ -0,0 +1 @@
|
||||
An input script similar to one that will actually be used in practice.
|
10
amend/tests/008-parse-real-script/input
Normal file
10
amend/tests/008-parse-real-script/input
Normal file
@ -0,0 +1,10 @@
|
||||
assert hash_dir("SYS:") == "112345oldhashvalue1234123"
|
||||
mark SYS: dirty
|
||||
copy_dir "PKG:android-files" SYS:
|
||||
assert hash_dir("SYS:") == "667890newhashvalue6678909"
|
||||
mark SYS: clean
|
||||
done
|
||||
assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
|
||||
assert "true" == "false"
|
||||
assert matches(hash_dir("SYS:"), "667890newhashvalue6678909", "999999newhashvalue6678909") != ""
|
||||
assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "999999newhashvalue6678909"
|
17
amend/tests/008-parse-real-script/run
Normal file
17
amend/tests/008-parse-real-script/run
Normal file
@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
amend --debug-ast input
|
0
amend/tests/XXX-long-token/SKIP
Normal file
0
amend/tests/XXX-long-token/SKIP
Normal file
0
amend/tests/XXX-stack-overflow/SKIP
Normal file
0
amend/tests/XXX-stack-overflow/SKIP
Normal file
150
amend/tests/one-test
Executable file
150
amend/tests/one-test
Executable file
@ -0,0 +1,150 @@
|
||||
#!/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.
|
||||
|
||||
# Set up prog to be the path of this script, including following symlinks,
|
||||
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||
prog="$0"
|
||||
while [ -h "${prog}" ]; do
|
||||
newProg=`/bin/ls -ld "${prog}"`
|
||||
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||
prog="${newProg}"
|
||||
else
|
||||
progdir=`dirname "${prog}"`
|
||||
prog="${progdir}/${newProg}"
|
||||
fi
|
||||
done
|
||||
oldwd=`pwd`
|
||||
progdir=`dirname "${prog}"`
|
||||
cd "${progdir}"
|
||||
progdir=`pwd`
|
||||
prog="${progdir}"/`basename "${prog}"`
|
||||
|
||||
info="info.txt"
|
||||
run="run"
|
||||
expected="expected.txt"
|
||||
output="out.txt"
|
||||
skip="SKIP"
|
||||
|
||||
dev_mode="no"
|
||||
if [ "x$1" = "x--dev" ]; then
|
||||
dev_mode="yes"
|
||||
shift
|
||||
fi
|
||||
|
||||
update_mode="no"
|
||||
if [ "x$1" = "x--update" ]; then
|
||||
update_mode="yes"
|
||||
shift
|
||||
fi
|
||||
|
||||
usage="no"
|
||||
if [ "x$1" = "x--help" ]; then
|
||||
usage="yes"
|
||||
else
|
||||
if [ "x$1" = "x" ]; then
|
||||
testdir=`basename "$oldwd"`
|
||||
else
|
||||
testdir="$1"
|
||||
fi
|
||||
|
||||
if [ '!' -d "$testdir" ]; then
|
||||
td2=`echo ${testdir}-*`
|
||||
if [ '!' -d "$td2" ]; then
|
||||
echo "${testdir}: no such test directory" 1>&2
|
||||
usage="yes"
|
||||
fi
|
||||
testdir="$td2"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$usage" = "yes" ]; then
|
||||
prog=`basename $prog`
|
||||
(
|
||||
echo "usage:"
|
||||
echo " $prog --help Print this message."
|
||||
echo " $prog testname Run test normally."
|
||||
echo " $prog --dev testname Development mode (dump to stdout)."
|
||||
echo " $prog --update testname Update mode (replace expected.txt)."
|
||||
echo " Omitting the test name uses the current directory as the test."
|
||||
) 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
td_info="$testdir"/"$info"
|
||||
td_run="$testdir"/"$run"
|
||||
td_expected="$testdir"/"$expected"
|
||||
td_skip="$testdir"/"$skip"
|
||||
|
||||
if [ -r "$td_skip" ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
tmpdir=/tmp/test-$$
|
||||
|
||||
if [ '!' '(' -r "$td_info" -a -r "$td_run" -a -r "$td_expected" ')' ]; then
|
||||
echo "${testdir}: missing files" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# copy the test to a temp dir and run it
|
||||
|
||||
echo "${testdir}: running..." 1>&2
|
||||
|
||||
rm -rf "$tmpdir"
|
||||
cp -Rp "$testdir" "$tmpdir"
|
||||
cd "$tmpdir"
|
||||
chmod 755 "$run"
|
||||
|
||||
#PATH="${progdir}/../build/bin:${PATH}"
|
||||
|
||||
good="no"
|
||||
if [ "$dev_mode" = "yes" ]; then
|
||||
"./$run" 2>&1
|
||||
echo "exit status: $?" 1>&2
|
||||
good="yes"
|
||||
elif [ "$update_mode" = "yes" ]; then
|
||||
"./$run" >"${progdir}/$td_expected" 2>&1
|
||||
good="yes"
|
||||
else
|
||||
"./$run" >"$output" 2>&1
|
||||
cmp -s "$expected" "$output"
|
||||
if [ "$?" = "0" ]; then
|
||||
# output == expected
|
||||
good="yes"
|
||||
echo "$testdir"': succeeded!' 1>&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$good" = "yes" ]; then
|
||||
cd "$oldwd"
|
||||
rm -rf "$tmpdir"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
(
|
||||
echo "${testdir}: FAILED!"
|
||||
echo ' '
|
||||
echo '#################### info'
|
||||
cat "$info" | sed 's/^/# /g'
|
||||
echo '#################### diffs'
|
||||
diff -u "$expected" "$output"
|
||||
echo '####################'
|
||||
echo ' '
|
||||
echo "files left in $tmpdir"
|
||||
) 1>&2
|
||||
|
||||
exit 1
|
69
amend/tests/run-all-tests
Executable file
69
amend/tests/run-all-tests
Executable file
@ -0,0 +1,69 @@
|
||||
#!/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.
|
||||
|
||||
# Set up prog to be the path of this script, including following symlinks,
|
||||
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||
prog="$0"
|
||||
while [ -h "${prog}" ]; do
|
||||
newProg=`/bin/ls -ld "${prog}"`
|
||||
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||
prog="${newProg}"
|
||||
else
|
||||
progdir=`dirname "${prog}"`
|
||||
prog="${progdir}/${newProg}"
|
||||
fi
|
||||
done
|
||||
oldwd=`pwd`
|
||||
progdir=`dirname "${prog}"`
|
||||
cd "${progdir}"
|
||||
progdir=`pwd`
|
||||
prog="${progdir}"/`basename "${prog}"`
|
||||
|
||||
passed=0
|
||||
skipped=0
|
||||
skipNames=""
|
||||
failed=0
|
||||
failNames=""
|
||||
|
||||
for i in *; do
|
||||
if [ -d "$i" -a -r "$i" ]; then
|
||||
./one-test "$i"
|
||||
status=$?
|
||||
if [ "$status" = "0" ]; then
|
||||
((passed += 1))
|
||||
elif [ "$status" = "2" ]; then
|
||||
((skipped += 1))
|
||||
skipNames="$skipNames $i"
|
||||
else
|
||||
((failed += 1))
|
||||
failNames="$failNames $i"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "passed: $passed test(s)"
|
||||
echo "skipped: $skipped test(s)"
|
||||
|
||||
for i in $skipNames; do
|
||||
echo "skipped: $i"
|
||||
done
|
||||
|
||||
echo "failed: $failed test(s)"
|
||||
|
||||
for i in $failNames; do
|
||||
echo "failed: $i"
|
||||
done
|
@ -1,57 +0,0 @@
|
||||
# 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_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c
|
||||
LOCAL_MODULE := libapplypatch
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery
|
||||
LOCAL_STATIC_LIBRARIES += libmtdutils libmincrypt libbz libz
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := main.c
|
||||
LOCAL_MODULE := applypatch
|
||||
LOCAL_C_INCLUDES += bootable/recovery
|
||||
LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz libminelf
|
||||
LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := main.c
|
||||
LOCAL_MODULE := applypatch_static
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
LOCAL_C_INCLUDES += bootable/recovery
|
||||
LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz libminelf
|
||||
LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c
|
||||
LOCAL_MODULE := imgdiff
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
LOCAL_C_INCLUDES += external/zlib external/bzip2
|
||||
LOCAL_STATIC_LIBRARIES += libz libbz
|
||||
|
||||
include $(BUILD_HOST_EXECUTABLE)
|
@ -1,911 +0,0 @@
|
||||
/*
|
||||
* 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 <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mincrypt/sha.h"
|
||||
#include "applypatch.h"
|
||||
#include "mtdutils/mtdutils.h"
|
||||
#include "edify/expr.h"
|
||||
|
||||
int SaveFileContents(const char* filename, FileContents file);
|
||||
static int LoadPartitionContents(const char* filename, FileContents* file);
|
||||
int ParseSha1(const char* str, uint8_t* digest);
|
||||
static ssize_t FileSink(unsigned char* data, ssize_t len, void* token);
|
||||
|
||||
static int mtd_partitions_scanned = 0;
|
||||
|
||||
// Read a file into memory; optionally (retouch_flag == RETOUCH_DO_MASK) mask
|
||||
// the retouched entries back to their original value (such that SHA-1 checks
|
||||
// don't fail due to randomization); store the file contents and associated
|
||||
// metadata in *file.
|
||||
//
|
||||
// Return 0 on success.
|
||||
int LoadFileContents(const char* filename, FileContents* file,
|
||||
int retouch_flag) {
|
||||
file->data = NULL;
|
||||
|
||||
// A special 'filename' beginning with "MTD:" or "EMMC:" means to
|
||||
// load the contents of a partition.
|
||||
if (strncmp(filename, "MTD:", 4) == 0 ||
|
||||
strncmp(filename, "EMMC:", 5) == 0) {
|
||||
return LoadPartitionContents(filename, file);
|
||||
}
|
||||
|
||||
if (stat(filename, &file->st) != 0) {
|
||||
printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
file->size = file->st.st_size;
|
||||
file->data = malloc(file->size);
|
||||
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
printf("failed to open \"%s\": %s\n", filename, strerror(errno));
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t bytes_read = fread(file->data, 1, file->size, f);
|
||||
if (bytes_read != file->size) {
|
||||
printf("short read of \"%s\" (%ld bytes of %ld)\n",
|
||||
filename, (long)bytes_read, (long)file->size);
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
// apply_patch[_check] functions are blind to randomization. Randomization
|
||||
// is taken care of in [Undo]RetouchBinariesFn. If there is a mismatch
|
||||
// within a file, this means the file is assumed "corrupt" for simplicity.
|
||||
if (retouch_flag) {
|
||||
int32_t desired_offset = 0;
|
||||
if (retouch_mask_data(file->data, file->size,
|
||||
&desired_offset, NULL) != RETOUCH_DATA_MATCHED) {
|
||||
printf("error trying to mask retouch entries\n");
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SHA(file->data, file->size, file->sha1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t* size_array;
|
||||
// comparison function for qsort()ing an int array of indexes into
|
||||
// size_array[].
|
||||
static int compare_size_indices(const void* a, const void* b) {
|
||||
int aa = *(int*)a;
|
||||
int bb = *(int*)b;
|
||||
if (size_array[aa] < size_array[bb]) {
|
||||
return -1;
|
||||
} else if (size_array[aa] > size_array[bb]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeFileContents(FileContents* file) {
|
||||
if (file) free(file->data);
|
||||
free(file);
|
||||
}
|
||||
|
||||
// Load the contents of an MTD or EMMC partition into the provided
|
||||
// FileContents. filename should be a string of the form
|
||||
// "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..." (or
|
||||
// "EMMC:<partition_device>:..."). The smallest size_n bytes for
|
||||
// which that prefix of the partition contents has the corresponding
|
||||
// sha1 hash will be loaded. It is acceptable for a size value to be
|
||||
// repeated with different sha1s. Will return 0 on success.
|
||||
//
|
||||
// This complexity is needed because if an OTA installation is
|
||||
// interrupted, the partition might contain either the source or the
|
||||
// target data, which might be of different lengths. We need to know
|
||||
// the length in order to read from a partition (there is no
|
||||
// "end-of-file" marker), so the caller must specify the possible
|
||||
// lengths and the hash of the data, and we'll do the load expecting
|
||||
// to find one of those hashes.
|
||||
enum PartitionType { MTD, EMMC };
|
||||
|
||||
static int LoadPartitionContents(const char* filename, FileContents* file) {
|
||||
char* copy = strdup(filename);
|
||||
const char* magic = strtok(copy, ":");
|
||||
|
||||
enum PartitionType type;
|
||||
|
||||
if (strcmp(magic, "MTD") == 0) {
|
||||
type = MTD;
|
||||
} else if (strcmp(magic, "EMMC") == 0) {
|
||||
type = EMMC;
|
||||
} else {
|
||||
printf("LoadPartitionContents called with bad filename (%s)\n",
|
||||
filename);
|
||||
return -1;
|
||||
}
|
||||
const char* partition = strtok(NULL, ":");
|
||||
|
||||
int i;
|
||||
int colons = 0;
|
||||
for (i = 0; filename[i] != '\0'; ++i) {
|
||||
if (filename[i] == ':') {
|
||||
++colons;
|
||||
}
|
||||
}
|
||||
if (colons < 3 || colons%2 == 0) {
|
||||
printf("LoadPartitionContents called with bad filename (%s)\n",
|
||||
filename);
|
||||
}
|
||||
|
||||
int pairs = (colons-1)/2; // # of (size,sha1) pairs in filename
|
||||
int* index = malloc(pairs * sizeof(int));
|
||||
size_t* size = malloc(pairs * sizeof(size_t));
|
||||
char** sha1sum = malloc(pairs * sizeof(char*));
|
||||
|
||||
for (i = 0; i < pairs; ++i) {
|
||||
const char* size_str = strtok(NULL, ":");
|
||||
size[i] = strtol(size_str, NULL, 10);
|
||||
if (size[i] == 0) {
|
||||
printf("LoadPartitionContents called with bad size (%s)\n", filename);
|
||||
return -1;
|
||||
}
|
||||
sha1sum[i] = strtok(NULL, ":");
|
||||
index[i] = i;
|
||||
}
|
||||
|
||||
// sort the index[] array so it indexes the pairs in order of
|
||||
// increasing size.
|
||||
size_array = size;
|
||||
qsort(index, pairs, sizeof(int), compare_size_indices);
|
||||
|
||||
MtdReadContext* ctx = NULL;
|
||||
FILE* dev = NULL;
|
||||
|
||||
switch (type) {
|
||||
case MTD:
|
||||
if (!mtd_partitions_scanned) {
|
||||
mtd_scan_partitions();
|
||||
mtd_partitions_scanned = 1;
|
||||
}
|
||||
|
||||
const MtdPartition* mtd = mtd_find_partition_by_name(partition);
|
||||
if (mtd == NULL) {
|
||||
printf("mtd partition \"%s\" not found (loading %s)\n",
|
||||
partition, filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx = mtd_read_partition(mtd);
|
||||
if (ctx == NULL) {
|
||||
printf("failed to initialize read of mtd partition \"%s\"\n",
|
||||
partition);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case EMMC:
|
||||
dev = fopen(partition, "rb");
|
||||
if (dev == NULL) {
|
||||
printf("failed to open emmc partition \"%s\": %s\n",
|
||||
partition, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SHA_CTX sha_ctx;
|
||||
SHA_init(&sha_ctx);
|
||||
uint8_t parsed_sha[SHA_DIGEST_SIZE];
|
||||
|
||||
// allocate enough memory to hold the largest size.
|
||||
file->data = malloc(size[index[pairs-1]]);
|
||||
char* p = (char*)file->data;
|
||||
file->size = 0; // # bytes read so far
|
||||
|
||||
for (i = 0; i < pairs; ++i) {
|
||||
// Read enough additional bytes to get us up to the next size
|
||||
// (again, we're trying the possibilities in order of increasing
|
||||
// size).
|
||||
size_t next = size[index[i]] - file->size;
|
||||
size_t read = 0;
|
||||
if (next > 0) {
|
||||
switch (type) {
|
||||
case MTD:
|
||||
read = mtd_read_data(ctx, p, next);
|
||||
break;
|
||||
|
||||
case EMMC:
|
||||
read = fread(p, 1, next, dev);
|
||||
break;
|
||||
}
|
||||
if (next != read) {
|
||||
printf("short read (%d bytes of %d) for partition \"%s\"\n",
|
||||
read, next, partition);
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
SHA_update(&sha_ctx, p, read);
|
||||
file->size += read;
|
||||
}
|
||||
|
||||
// Duplicate the SHA context and finalize the duplicate so we can
|
||||
// check it against this pair's expected hash.
|
||||
SHA_CTX temp_ctx;
|
||||
memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
|
||||
const uint8_t* sha_so_far = SHA_final(&temp_ctx);
|
||||
|
||||
if (ParseSha1(sha1sum[index[i]], parsed_sha) != 0) {
|
||||
printf("failed to parse sha1 %s in %s\n",
|
||||
sha1sum[index[i]], filename);
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) {
|
||||
// we have a match. stop reading the partition; we'll return
|
||||
// the data we've read so far.
|
||||
printf("partition read matched size %d sha %s\n",
|
||||
size[index[i]], sha1sum[index[i]]);
|
||||
break;
|
||||
}
|
||||
|
||||
p += read;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MTD:
|
||||
mtd_read_close(ctx);
|
||||
break;
|
||||
|
||||
case EMMC:
|
||||
fclose(dev);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (i == pairs) {
|
||||
// Ran off the end of the list of (size,sha1) pairs without
|
||||
// finding a match.
|
||||
printf("contents of partition \"%s\" didn't match %s\n",
|
||||
partition, filename);
|
||||
free(file->data);
|
||||
file->data = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint8_t* sha_final = SHA_final(&sha_ctx);
|
||||
for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
|
||||
file->sha1[i] = sha_final[i];
|
||||
}
|
||||
|
||||
// Fake some stat() info.
|
||||
file->st.st_mode = 0644;
|
||||
file->st.st_uid = 0;
|
||||
file->st.st_gid = 0;
|
||||
|
||||
free(copy);
|
||||
free(index);
|
||||
free(size);
|
||||
free(sha1sum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Save the contents of the given FileContents object under the given
|
||||
// filename. Return 0 on success.
|
||||
int SaveFileContents(const char* filename, FileContents file) {
|
||||
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (fd < 0) {
|
||||
printf("failed to open \"%s\" for write: %s\n",
|
||||
filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t bytes_written = FileSink(file.data, file.size, &fd);
|
||||
if (bytes_written != file.size) {
|
||||
printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n",
|
||||
filename, (long)bytes_written, (long)file.size,
|
||||
strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
fsync(fd);
|
||||
close(fd);
|
||||
|
||||
if (chmod(filename, file.st.st_mode) != 0) {
|
||||
printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (chown(filename, file.st.st_uid, file.st.st_gid) != 0) {
|
||||
printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write a memory buffer to 'target' partition, a string of the form
|
||||
// "MTD:<partition>[:...]" or "EMMC:<partition_device>:". Return 0 on
|
||||
// success.
|
||||
int WriteToPartition(unsigned char* data, size_t len,
|
||||
const char* target) {
|
||||
char* copy = strdup(target);
|
||||
const char* magic = strtok(copy, ":");
|
||||
|
||||
enum PartitionType type;
|
||||
if (strcmp(magic, "MTD") == 0) {
|
||||
type = MTD;
|
||||
} else if (strcmp(magic, "EMMC") == 0) {
|
||||
type = EMMC;
|
||||
} else {
|
||||
printf("WriteToPartition called with bad target (%s)\n", target);
|
||||
return -1;
|
||||
}
|
||||
const char* partition = strtok(NULL, ":");
|
||||
|
||||
if (partition == NULL) {
|
||||
printf("bad partition target name \"%s\"\n", target);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MTD:
|
||||
if (!mtd_partitions_scanned) {
|
||||
mtd_scan_partitions();
|
||||
mtd_partitions_scanned = 1;
|
||||
}
|
||||
|
||||
const MtdPartition* mtd = mtd_find_partition_by_name(partition);
|
||||
if (mtd == NULL) {
|
||||
printf("mtd partition \"%s\" not found for writing\n",
|
||||
partition);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MtdWriteContext* ctx = mtd_write_partition(mtd);
|
||||
if (ctx == NULL) {
|
||||
printf("failed to init mtd partition \"%s\" for writing\n",
|
||||
partition);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t written = mtd_write_data(ctx, (char*)data, len);
|
||||
if (written != len) {
|
||||
printf("only wrote %d of %d bytes to MTD %s\n",
|
||||
written, len, partition);
|
||||
mtd_write_close(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mtd_erase_blocks(ctx, -1) < 0) {
|
||||
printf("error finishing mtd write of %s\n", partition);
|
||||
mtd_write_close(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mtd_write_close(ctx)) {
|
||||
printf("error closing mtd write of %s\n", partition);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case EMMC:
|
||||
;
|
||||
FILE* f = fopen(partition, "wb");
|
||||
if (fwrite(data, 1, len, f) != len) {
|
||||
printf("short write writing to %s (%s)\n",
|
||||
partition, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (fclose(f) != 0) {
|
||||
printf("error closing %s (%s)\n", partition, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
free(copy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Take a string 'str' of 40 hex digits and parse it into the 20
|
||||
// byte array 'digest'. 'str' may contain only the digest or be of
|
||||
// the form "<digest>:<anything>". Return 0 on success, -1 on any
|
||||
// error.
|
||||
int ParseSha1(const char* str, uint8_t* digest) {
|
||||
int i;
|
||||
const char* ps = str;
|
||||
uint8_t* pd = digest;
|
||||
for (i = 0; i < SHA_DIGEST_SIZE * 2; ++i, ++ps) {
|
||||
int digit;
|
||||
if (*ps >= '0' && *ps <= '9') {
|
||||
digit = *ps - '0';
|
||||
} else if (*ps >= 'a' && *ps <= 'f') {
|
||||
digit = *ps - 'a' + 10;
|
||||
} else if (*ps >= 'A' && *ps <= 'F') {
|
||||
digit = *ps - 'A' + 10;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (i % 2 == 0) {
|
||||
*pd = digit << 4;
|
||||
} else {
|
||||
*pd |= digit;
|
||||
++pd;
|
||||
}
|
||||
}
|
||||
if (*ps != '\0') return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search an array of sha1 strings for one matching the given sha1.
|
||||
// Return the index of the match on success, or -1 if no match is
|
||||
// found.
|
||||
int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str,
|
||||
int num_patches) {
|
||||
int i;
|
||||
uint8_t patch_sha1[SHA_DIGEST_SIZE];
|
||||
for (i = 0; i < num_patches; ++i) {
|
||||
if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 &&
|
||||
memcmp(patch_sha1, sha1, SHA_DIGEST_SIZE) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns 0 if the contents of the file (argv[2]) or the cached file
|
||||
// match any of the sha1's on the command line (argv[3:]). Returns
|
||||
// nonzero otherwise.
|
||||
int applypatch_check(const char* filename,
|
||||
int num_patches, char** const patch_sha1_str) {
|
||||
FileContents file;
|
||||
file.data = NULL;
|
||||
|
||||
// It's okay to specify no sha1s; the check will pass if the
|
||||
// LoadFileContents is successful. (Useful for reading
|
||||
// partitions, where the filename encodes the sha1s; no need to
|
||||
// check them twice.)
|
||||
if (LoadFileContents(filename, &file, RETOUCH_DO_MASK) != 0 ||
|
||||
(num_patches > 0 &&
|
||||
FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) {
|
||||
printf("file \"%s\" doesn't have any of expected "
|
||||
"sha1 sums; checking cache\n", filename);
|
||||
|
||||
free(file.data);
|
||||
|
||||
// If the source file is missing or corrupted, it might be because
|
||||
// we were killed in the middle of patching it. A copy of it
|
||||
// should have been made in CACHE_TEMP_SOURCE. If that file
|
||||
// exists and matches the sha1 we're looking for, the check still
|
||||
// passes.
|
||||
|
||||
if (LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK) != 0) {
|
||||
printf("failed to load cache file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) {
|
||||
printf("cache bits don't match any sha1 for \"%s\"\n", filename);
|
||||
free(file.data);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
free(file.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ShowLicenses() {
|
||||
ShowBSDiffLicense();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t FileSink(unsigned char* data, ssize_t len, void* token) {
|
||||
int fd = *(int *)token;
|
||||
ssize_t done = 0;
|
||||
ssize_t wrote;
|
||||
while (done < (ssize_t) len) {
|
||||
wrote = write(fd, data+done, len-done);
|
||||
if (wrote <= 0) {
|
||||
printf("error writing %d bytes: %s\n", (int)(len-done), strerror(errno));
|
||||
return done;
|
||||
}
|
||||
done += wrote;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned char* buffer;
|
||||
ssize_t size;
|
||||
ssize_t pos;
|
||||
} MemorySinkInfo;
|
||||
|
||||
ssize_t MemorySink(unsigned char* data, ssize_t len, void* token) {
|
||||
MemorySinkInfo* msi = (MemorySinkInfo*)token;
|
||||
if (msi->size - msi->pos < len) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(msi->buffer + msi->pos, data, len);
|
||||
msi->pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
// Return the amount of free space (in bytes) on the filesystem
|
||||
// containing filename. filename must exist. Return -1 on error.
|
||||
size_t FreeSpaceForFile(const char* filename) {
|
||||
struct statfs sf;
|
||||
if (statfs(filename, &sf) != 0) {
|
||||
printf("failed to statfs %s: %s\n", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return sf.f_bsize * sf.f_bfree;
|
||||
}
|
||||
|
||||
int CacheSizeCheck(size_t bytes) {
|
||||
if (MakeFreeSpaceOnCache(bytes) < 0) {
|
||||
printf("unable to make %ld bytes available on /cache\n", (long)bytes);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This function applies binary patches to files in a way that is safe
|
||||
// (the original file is not touched until we have the desired
|
||||
// replacement for it) and idempotent (it's okay to run this program
|
||||
// multiple times).
|
||||
//
|
||||
// - if the sha1 hash of <target_filename> is <target_sha1_string>,
|
||||
// does nothing and exits successfully.
|
||||
//
|
||||
// - otherwise, if the sha1 hash of <source_filename> is one of the
|
||||
// entries in <patch_sha1_str>, the corresponding patch from
|
||||
// <patch_data> (which must be a VAL_BLOB) is applied to produce a
|
||||
// new file (the type of patch is automatically detected from the
|
||||
// blob daat). If that new file has sha1 hash <target_sha1_str>,
|
||||
// moves it to replace <target_filename>, and exits successfully.
|
||||
// Note that if <source_filename> and <target_filename> are not the
|
||||
// same, <source_filename> is NOT deleted on success.
|
||||
// <target_filename> may be the string "-" to mean "the same as
|
||||
// source_filename".
|
||||
//
|
||||
// - otherwise, or if any error is encountered, exits with non-zero
|
||||
// status.
|
||||
//
|
||||
// <source_filename> may refer to a partition to read the source data.
|
||||
// See the comments for the LoadPartition Contents() function above
|
||||
// for the format of such a filename.
|
||||
|
||||
int applypatch(const char* source_filename,
|
||||
const char* target_filename,
|
||||
const char* target_sha1_str,
|
||||
size_t target_size,
|
||||
int num_patches,
|
||||
char** const patch_sha1_str,
|
||||
Value** patch_data) {
|
||||
printf("\napplying patch to %s\n", source_filename);
|
||||
|
||||
if (target_filename[0] == '-' &&
|
||||
target_filename[1] == '\0') {
|
||||
target_filename = source_filename;
|
||||
}
|
||||
|
||||
uint8_t target_sha1[SHA_DIGEST_SIZE];
|
||||
if (ParseSha1(target_sha1_str, target_sha1) != 0) {
|
||||
printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
FileContents copy_file;
|
||||
FileContents source_file;
|
||||
const Value* source_patch_value = NULL;
|
||||
const Value* copy_patch_value = NULL;
|
||||
int made_copy = 0;
|
||||
|
||||
// We try to load the target file into the source_file object.
|
||||
if (LoadFileContents(target_filename, &source_file,
|
||||
RETOUCH_DO_MASK) == 0) {
|
||||
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) {
|
||||
// The early-exit case: the patch was already applied, this file
|
||||
// has the desired hash, nothing for us to do.
|
||||
printf("\"%s\" is already target; no patch needed\n",
|
||||
target_filename);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (source_file.data == NULL ||
|
||||
(target_filename != source_filename &&
|
||||
strcmp(target_filename, source_filename) != 0)) {
|
||||
// Need to load the source file: either we failed to load the
|
||||
// target file, or we did but it's different from the source file.
|
||||
free(source_file.data);
|
||||
LoadFileContents(source_filename, &source_file,
|
||||
RETOUCH_DO_MASK);
|
||||
}
|
||||
|
||||
if (source_file.data != NULL) {
|
||||
int to_use = FindMatchingPatch(source_file.sha1,
|
||||
patch_sha1_str, num_patches);
|
||||
if (to_use >= 0) {
|
||||
source_patch_value = patch_data[to_use];
|
||||
}
|
||||
}
|
||||
|
||||
if (source_patch_value == NULL) {
|
||||
free(source_file.data);
|
||||
printf("source file is bad; trying copy\n");
|
||||
|
||||
if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file,
|
||||
RETOUCH_DO_MASK) < 0) {
|
||||
// fail.
|
||||
printf("failed to read copy file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int to_use = FindMatchingPatch(copy_file.sha1,
|
||||
patch_sha1_str, num_patches);
|
||||
if (to_use >= 0) {
|
||||
copy_patch_value = patch_data[to_use];
|
||||
}
|
||||
|
||||
if (copy_patch_value == NULL) {
|
||||
// fail.
|
||||
printf("copy file doesn't match source SHA-1s either\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int retry = 1;
|
||||
SHA_CTX ctx;
|
||||
int output;
|
||||
MemorySinkInfo msi;
|
||||
FileContents* source_to_use;
|
||||
char* outname;
|
||||
|
||||
// assume that target_filename (eg "/system/app/Foo.apk") is located
|
||||
// on the same filesystem as its top-level directory ("/system").
|
||||
// We need something that exists for calling statfs().
|
||||
char target_fs[strlen(target_filename)+1];
|
||||
char* slash = strchr(target_filename+1, '/');
|
||||
if (slash != NULL) {
|
||||
int count = slash - target_filename;
|
||||
strncpy(target_fs, target_filename, count);
|
||||
target_fs[count] = '\0';
|
||||
} else {
|
||||
strcpy(target_fs, target_filename);
|
||||
}
|
||||
|
||||
do {
|
||||
// Is there enough room in the target filesystem to hold the patched
|
||||
// file?
|
||||
|
||||
if (strncmp(target_filename, "MTD:", 4) == 0 ||
|
||||
strncmp(target_filename, "EMMC:", 5) == 0) {
|
||||
// If the target is a partition, we're actually going to
|
||||
// write the output to /tmp and then copy it to the
|
||||
// partition. statfs() always returns 0 blocks free for
|
||||
// /tmp, so instead we'll just assume that /tmp has enough
|
||||
// space to hold the file.
|
||||
|
||||
// We still write the original source to cache, in case
|
||||
// the partition write is interrupted.
|
||||
if (MakeFreeSpaceOnCache(source_file.size) < 0) {
|
||||
printf("not enough free space on /cache\n");
|
||||
return 1;
|
||||
}
|
||||
if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
|
||||
printf("failed to back up source file\n");
|
||||
return 1;
|
||||
}
|
||||
made_copy = 1;
|
||||
retry = 0;
|
||||
} else {
|
||||
int enough_space = 0;
|
||||
if (retry > 0) {
|
||||
size_t free_space = FreeSpaceForFile(target_fs);
|
||||
enough_space =
|
||||
(free_space > (256 << 10)) && // 256k (two-block) minimum
|
||||
(free_space > (target_size * 3 / 2)); // 50% margin of error
|
||||
printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n",
|
||||
(long)target_size, (long)free_space, retry, enough_space);
|
||||
}
|
||||
|
||||
if (!enough_space) {
|
||||
retry = 0;
|
||||
}
|
||||
|
||||
if (!enough_space && source_patch_value != NULL) {
|
||||
// Using the original source, but not enough free space. First
|
||||
// copy the source file to cache, then delete it from the original
|
||||
// location.
|
||||
|
||||
if (strncmp(source_filename, "MTD:", 4) == 0 ||
|
||||
strncmp(source_filename, "EMMC:", 5) == 0) {
|
||||
// It's impossible to free space on the target filesystem by
|
||||
// deleting the source if the source is a partition. If
|
||||
// we're ever in a state where we need to do this, fail.
|
||||
printf("not enough free space for target but source "
|
||||
"is partition\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (MakeFreeSpaceOnCache(source_file.size) < 0) {
|
||||
printf("not enough free space on /cache\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
|
||||
printf("failed to back up source file\n");
|
||||
return 1;
|
||||
}
|
||||
made_copy = 1;
|
||||
unlink(source_filename);
|
||||
|
||||
size_t free_space = FreeSpaceForFile(target_fs);
|
||||
printf("(now %ld bytes free for target)\n", (long)free_space);
|
||||
}
|
||||
}
|
||||
|
||||
const Value* patch;
|
||||
if (source_patch_value != NULL) {
|
||||
source_to_use = &source_file;
|
||||
patch = source_patch_value;
|
||||
} else {
|
||||
source_to_use = ©_file;
|
||||
patch = copy_patch_value;
|
||||
}
|
||||
|
||||
if (patch->type != VAL_BLOB) {
|
||||
printf("patch is not a blob\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SinkFn sink = NULL;
|
||||
void* token = NULL;
|
||||
output = -1;
|
||||
outname = NULL;
|
||||
if (strncmp(target_filename, "MTD:", 4) == 0 ||
|
||||
strncmp(target_filename, "EMMC:", 5) == 0) {
|
||||
// We store the decoded output in memory.
|
||||
msi.buffer = malloc(target_size);
|
||||
if (msi.buffer == NULL) {
|
||||
printf("failed to alloc %ld bytes for output\n",
|
||||
(long)target_size);
|
||||
return 1;
|
||||
}
|
||||
msi.pos = 0;
|
||||
msi.size = target_size;
|
||||
sink = MemorySink;
|
||||
token = &msi;
|
||||
} else {
|
||||
// We write the decoded output to "<tgt-file>.patch".
|
||||
outname = (char*)malloc(strlen(target_filename) + 10);
|
||||
strcpy(outname, target_filename);
|
||||
strcat(outname, ".patch");
|
||||
|
||||
output = open(outname, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (output < 0) {
|
||||
printf("failed to open output file %s: %s\n",
|
||||
outname, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
sink = FileSink;
|
||||
token = &output;
|
||||
}
|
||||
|
||||
char* header = patch->data;
|
||||
ssize_t header_bytes_read = patch->size;
|
||||
|
||||
SHA_init(&ctx);
|
||||
|
||||
int result;
|
||||
|
||||
if (header_bytes_read >= 8 &&
|
||||
memcmp(header, "BSDIFF40", 8) == 0) {
|
||||
result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size,
|
||||
patch, 0, sink, token, &ctx);
|
||||
} else if (header_bytes_read >= 8 &&
|
||||
memcmp(header, "IMGDIFF2", 8) == 0) {
|
||||
result = ApplyImagePatch(source_to_use->data, source_to_use->size,
|
||||
patch, sink, token, &ctx);
|
||||
} else {
|
||||
printf("Unknown patch file format\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (output >= 0) {
|
||||
fsync(output);
|
||||
close(output);
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
if (retry == 0) {
|
||||
printf("applying patch failed\n");
|
||||
return result != 0;
|
||||
} else {
|
||||
printf("applying patch failed; retrying\n");
|
||||
}
|
||||
if (outname != NULL) {
|
||||
unlink(outname);
|
||||
}
|
||||
} else {
|
||||
// succeeded; no need to retry
|
||||
break;
|
||||
}
|
||||
} while (retry-- > 0);
|
||||
|
||||
const uint8_t* current_target_sha1 = SHA_final(&ctx);
|
||||
if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) {
|
||||
printf("patch did not produce expected sha1\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (output < 0) {
|
||||
// Copy the temp file to the partition.
|
||||
if (WriteToPartition(msi.buffer, msi.pos, target_filename) != 0) {
|
||||
printf("write of patched data to %s failed\n", target_filename);
|
||||
return 1;
|
||||
}
|
||||
free(msi.buffer);
|
||||
} else {
|
||||
// Give the .patch file the same owner, group, and mode of the
|
||||
// original source file.
|
||||
if (chmod(outname, source_to_use->st.st_mode) != 0) {
|
||||
printf("chmod of \"%s\" failed: %s\n", outname, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (chown(outname, source_to_use->st.st_uid,
|
||||
source_to_use->st.st_gid) != 0) {
|
||||
printf("chown of \"%s\" failed: %s\n", outname, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Finally, rename the .patch file to replace the target file.
|
||||
if (rename(outname, target_filename) != 0) {
|
||||
printf("rename of .patch to \"%s\" failed: %s\n",
|
||||
target_filename, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If this run of applypatch created the copy, and we're here, we
|
||||
// can delete it.
|
||||
if (made_copy) unlink(CACHE_TEMP_SOURCE);
|
||||
|
||||
// Success!
|
||||
return 0;
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _APPLYPATCH_H
|
||||
#define _APPLYPATCH_H
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include "mincrypt/sha.h"
|
||||
#include "minelf/Retouch.h"
|
||||
#include "edify/expr.h"
|
||||
|
||||
typedef struct _Patch {
|
||||
uint8_t sha1[SHA_DIGEST_SIZE];
|
||||
const char* patch_filename;
|
||||
} Patch;
|
||||
|
||||
typedef struct _FileContents {
|
||||
uint8_t sha1[SHA_DIGEST_SIZE];
|
||||
unsigned char* data;
|
||||
ssize_t size;
|
||||
struct stat st;
|
||||
} FileContents;
|
||||
|
||||
// When there isn't enough room on the target filesystem to hold the
|
||||
// patched version of the file, we copy the original here and delete
|
||||
// it to free up space. If the expected source file doesn't exist, or
|
||||
// is corrupted, we look to see if this file contains the bits we want
|
||||
// and use it as the source instead.
|
||||
#define CACHE_TEMP_SOURCE "/cache/saved.file"
|
||||
|
||||
typedef ssize_t (*SinkFn)(unsigned char*, ssize_t, void*);
|
||||
|
||||
// applypatch.c
|
||||
int ShowLicenses();
|
||||
size_t FreeSpaceForFile(const char* filename);
|
||||
int CacheSizeCheck(size_t bytes);
|
||||
int ParseSha1(const char* str, uint8_t* digest);
|
||||
|
||||
int applypatch(const char* source_filename,
|
||||
const char* target_filename,
|
||||
const char* target_sha1_str,
|
||||
size_t target_size,
|
||||
int num_patches,
|
||||
char** const patch_sha1_str,
|
||||
Value** patch_data);
|
||||
int applypatch_check(const char* filename,
|
||||
int num_patches,
|
||||
char** const patch_sha1_str);
|
||||
|
||||
int LoadFileContents(const char* filename, FileContents* file,
|
||||
int retouch_flag);
|
||||
int SaveFileContents(const char* filename, FileContents file);
|
||||
void FreeFileContents(FileContents* file);
|
||||
int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str,
|
||||
int num_patches);
|
||||
|
||||
// bsdiff.c
|
||||
void ShowBSDiffLicense();
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx);
|
||||
int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
unsigned char** new_data, ssize_t* new_size);
|
||||
|
||||
// imgpatch.c
|
||||
int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx);
|
||||
|
||||
// freecache.c
|
||||
int MakeFreeSpaceOnCache(size_t bytes_needed);
|
||||
|
||||
#endif
|
@ -1,350 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A test suite for applypatch. Run in a client where you have done
|
||||
# envsetup, choosecombo, etc.
|
||||
#
|
||||
# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your
|
||||
# system partition.
|
||||
#
|
||||
#
|
||||
# TODO: find some way to get this run regularly along with the rest of
|
||||
# the tests.
|
||||
|
||||
EMULATOR_PORT=5580
|
||||
DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata
|
||||
|
||||
# This must be the filename that applypatch uses for its copies.
|
||||
CACHE_TEMP_SOURCE=/cache/saved.file
|
||||
|
||||
# Put all binaries and files here. We use /cache because it's a
|
||||
# temporary filesystem in the emulator; it's created fresh each time
|
||||
# the emulator starts.
|
||||
WORK_DIR=/system
|
||||
|
||||
# partition that WORK_DIR is located on, without the leading slash
|
||||
WORK_FS=system
|
||||
|
||||
# set to 0 to use a device instead
|
||||
USE_EMULATOR=1
|
||||
|
||||
# ------------------------
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
|
||||
if [ "$USE_EMULATOR" == 1 ]; then
|
||||
emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
|
||||
pid_emulator=$!
|
||||
ADB="adb -s emulator-$EMULATOR_PORT "
|
||||
else
|
||||
ADB="adb -d "
|
||||
fi
|
||||
|
||||
echo "waiting to connect to device"
|
||||
$ADB wait-for-device
|
||||
echo "device is available"
|
||||
$ADB remount
|
||||
# free up enough space on the system partition for the test to run.
|
||||
$ADB shell rm -r /system/media
|
||||
|
||||
# run a command on the device; exit with the exit status of the device
|
||||
# command.
|
||||
run_command() {
|
||||
$ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
|
||||
}
|
||||
|
||||
testname() {
|
||||
echo
|
||||
echo "$1"...
|
||||
testname="$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo
|
||||
echo FAIL: $testname
|
||||
echo
|
||||
[ "$open_pid" == "" ] || kill $open_pid
|
||||
[ "$pid_emulator" == "" ] || kill $pid_emulator
|
||||
exit 1
|
||||
}
|
||||
|
||||
sha1() {
|
||||
sha1sum $1 | awk '{print $1}'
|
||||
}
|
||||
|
||||
free_space() {
|
||||
run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# not necessary if we're about to kill the emulator, but nice for
|
||||
# running on real devices or already-running emulators.
|
||||
testname "removing test files"
|
||||
run_command rm $WORK_DIR/bloat.dat
|
||||
run_command rm $WORK_DIR/old.file
|
||||
run_command rm $WORK_DIR/foo
|
||||
run_command rm $WORK_DIR/patch.bsdiff
|
||||
run_command rm $WORK_DIR/applypatch
|
||||
run_command rm $CACHE_TEMP_SOURCE
|
||||
run_command rm /cache/bloat*.dat
|
||||
|
||||
[ "$pid_emulator" == "" ] || kill $pid_emulator
|
||||
|
||||
if [ $# == 0 ]; then
|
||||
rm -rf $tmpdir
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup leave_tmp
|
||||
|
||||
$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
|
||||
|
||||
BAD1_SHA1=$(printf "%040x" $RANDOM)
|
||||
BAD2_SHA1=$(printf "%040x" $RANDOM)
|
||||
OLD_SHA1=$(sha1 $DATA_DIR/old.file)
|
||||
NEW_SHA1=$(sha1 $DATA_DIR/new.file)
|
||||
NEW_SIZE=$(stat -c %s $DATA_DIR/new.file)
|
||||
|
||||
# --------------- basic execution ----------------------
|
||||
|
||||
testname "usage message"
|
||||
run_command $WORK_DIR/applypatch && fail
|
||||
|
||||
testname "display license"
|
||||
run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail
|
||||
|
||||
|
||||
# --------------- check mode ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $WORK_DIR
|
||||
|
||||
testname "check mode single"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
|
||||
|
||||
testname "check mode multiple"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
|
||||
|
||||
testname "check mode failure"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
|
||||
|
||||
$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
|
||||
# put some junk in the old file
|
||||
run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
|
||||
|
||||
testname "check mode cache (corrupted) single"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
|
||||
|
||||
testname "check mode cache (corrupted) multiple"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
|
||||
|
||||
testname "check mode cache (corrupted) failure"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
|
||||
|
||||
# remove the old file entirely
|
||||
run_command rm $WORK_DIR/old.file
|
||||
|
||||
testname "check mode cache (missing) single"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
|
||||
|
||||
testname "check mode cache (missing) multiple"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
|
||||
|
||||
testname "check mode cache (missing) failure"
|
||||
run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
|
||||
|
||||
|
||||
# --------------- apply patch ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $WORK_DIR
|
||||
$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
|
||||
echo hello > $tmpdir/foo
|
||||
$ADB push $tmpdir/foo $WORK_DIR
|
||||
|
||||
# Check that the partition has enough space to apply the patch without
|
||||
# copying. If it doesn't, we'll be testing the low-space condition
|
||||
# when we intend to test the not-low-space condition.
|
||||
testname "apply patches (with enough space)"
|
||||
free_kb=$(free_space $WORK_FS)
|
||||
echo "${free_kb}kb free on /$WORK_FS."
|
||||
if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
|
||||
echo "Not enough space on /$WORK_FS to patch test file."
|
||||
echo
|
||||
echo "This doesn't mean that applypatch is necessarily broken;"
|
||||
echo "just that /$WORK_FS doesn't have enough free space to"
|
||||
echo "properly run this test."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
testname "apply bsdiff patch"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
testname "reapply bsdiff patch"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
|
||||
# --------------- apply patch in new location ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $WORK_DIR
|
||||
$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
|
||||
|
||||
# Check that the partition has enough space to apply the patch without
|
||||
# copying. If it doesn't, we'll be testing the low-space condition
|
||||
# when we intend to test the not-low-space condition.
|
||||
testname "apply patch to new location (with enough space)"
|
||||
free_kb=$(free_space $WORK_FS)
|
||||
echo "${free_kb}kb free on /$WORK_FS."
|
||||
if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
|
||||
echo "Not enough space on /$WORK_FS to patch test file."
|
||||
echo
|
||||
echo "This doesn't mean that applypatch is necessarily broken;"
|
||||
echo "just that /$WORK_FS doesn't have enough free space to"
|
||||
echo "properly run this test."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_command rm $WORK_DIR/new.file
|
||||
run_command rm $CACHE_TEMP_SOURCE
|
||||
|
||||
testname "apply bsdiff patch to new location"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/new.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
testname "reapply bsdiff patch to new location"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/new.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
|
||||
# put some junk in the old file
|
||||
run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
|
||||
|
||||
testname "apply bsdiff patch to new location with corrupted source"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail
|
||||
$ADB pull $WORK_DIR/new.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
# put some junk in the cache copy, too
|
||||
run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail
|
||||
|
||||
run_command rm $WORK_DIR/new.file
|
||||
testname "apply bsdiff patch to new location with corrupted source and copy (no new file)"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
|
||||
|
||||
# put some junk in the new file
|
||||
run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail
|
||||
|
||||
testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
|
||||
|
||||
# --------------- apply patch with low space on /system ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $WORK_DIR
|
||||
$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
|
||||
|
||||
free_kb=$(free_space $WORK_FS)
|
||||
echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that."
|
||||
echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
|
||||
run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
|
||||
free_kb=$(free_space $WORK_FS)
|
||||
echo "${free_kb}kb free on /$WORK_FS now."
|
||||
|
||||
testname "apply bsdiff patch with low space"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
testname "reapply bsdiff patch with low space"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
# --------------- apply patch with low space on /system and /cache ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $WORK_DIR
|
||||
$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
|
||||
|
||||
free_kb=$(free_space $WORK_FS)
|
||||
echo "${free_kb}kb free on /$WORK_FS"
|
||||
|
||||
run_command mkdir /cache/subdir
|
||||
run_command 'echo > /cache/subdir/a.file'
|
||||
run_command 'echo > /cache/a.file'
|
||||
run_command mkdir /cache/recovery /cache/recovery/otatest
|
||||
run_command 'echo > /cache/recovery/otatest/b.file'
|
||||
run_command "echo > $CACHE_TEMP_SOURCE"
|
||||
free_kb=$(free_space cache)
|
||||
echo "${free_kb}kb free on /cache; we'll soon fix that."
|
||||
run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail
|
||||
run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail
|
||||
free_kb=$(free_space cache)
|
||||
echo "${free_kb}kb free on /cache now."
|
||||
|
||||
testname "apply bsdiff patch with low space, full cache, can't delete enough"
|
||||
$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$!
|
||||
echo "open_pid is $open_pid"
|
||||
|
||||
# size check should fail even though it deletes some stuff
|
||||
run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail
|
||||
run_command ls /cache/bloat_small.dat && fail # was deleted
|
||||
run_command ls /cache/a.file && fail # was deleted
|
||||
run_command ls /cache/recovery/otatest/b.file && fail # was deleted
|
||||
run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open
|
||||
run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir
|
||||
run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy
|
||||
|
||||
# should fail; not enough files can be deleted
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail
|
||||
run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open
|
||||
run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir
|
||||
run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy
|
||||
|
||||
kill $open_pid # /cache/bloat_large.dat is no longer open
|
||||
|
||||
testname "apply bsdiff patch with low space, full cache, can delete enough"
|
||||
|
||||
# should succeed after deleting /cache/bloat_large.dat
|
||||
run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail
|
||||
run_command ls /cache/bloat_large.dat && fail # was deleted
|
||||
run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir
|
||||
run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy
|
||||
|
||||
# should succeed
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir
|
||||
run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it
|
||||
|
||||
# --------------- apply patch from cache ----------------------
|
||||
|
||||
$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
|
||||
# put some junk in the old file
|
||||
run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
|
||||
|
||||
testname "apply bsdiff patch from cache (corrupted source) with low space"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
|
||||
# remove the old file entirely
|
||||
run_command rm $WORK_DIR/old.file
|
||||
|
||||
testname "apply bsdiff patch from cache (missing source) with low space"
|
||||
run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
|
||||
$ADB pull $WORK_DIR/old.file $tmpdir/patched
|
||||
diff -q $DATA_DIR/new.file $tmpdir/patched || fail
|
||||
|
||||
|
||||
# --------------- cleanup ----------------------
|
||||
|
||||
cleanup
|
||||
|
||||
echo
|
||||
echo PASS
|
||||
echo
|
||||
|
@ -1,410 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Most of this code comes from bsdiff.c from the bsdiff-4.3
|
||||
* distribution, which is:
|
||||
*/
|
||||
|
||||
/*-
|
||||
* Copyright 2003-2005 Colin Percival
|
||||
* All rights reserved
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted providing that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <bzlib.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MIN(x,y) (((x)<(y)) ? (x) : (y))
|
||||
|
||||
static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h)
|
||||
{
|
||||
off_t i,j,k,x,tmp,jj,kk;
|
||||
|
||||
if(len<16) {
|
||||
for(k=start;k<start+len;k+=j) {
|
||||
j=1;x=V[I[k]+h];
|
||||
for(i=1;k+i<start+len;i++) {
|
||||
if(V[I[k+i]+h]<x) {
|
||||
x=V[I[k+i]+h];
|
||||
j=0;
|
||||
};
|
||||
if(V[I[k+i]+h]==x) {
|
||||
tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp;
|
||||
j++;
|
||||
};
|
||||
};
|
||||
for(i=0;i<j;i++) V[I[k+i]]=k+j-1;
|
||||
if(j==1) I[k]=-1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
x=V[I[start+len/2]+h];
|
||||
jj=0;kk=0;
|
||||
for(i=start;i<start+len;i++) {
|
||||
if(V[I[i]+h]<x) jj++;
|
||||
if(V[I[i]+h]==x) kk++;
|
||||
};
|
||||
jj+=start;kk+=jj;
|
||||
|
||||
i=start;j=0;k=0;
|
||||
while(i<jj) {
|
||||
if(V[I[i]+h]<x) {
|
||||
i++;
|
||||
} else if(V[I[i]+h]==x) {
|
||||
tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp;
|
||||
j++;
|
||||
} else {
|
||||
tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp;
|
||||
k++;
|
||||
};
|
||||
};
|
||||
|
||||
while(jj+j<kk) {
|
||||
if(V[I[jj+j]+h]==x) {
|
||||
j++;
|
||||
} else {
|
||||
tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp;
|
||||
k++;
|
||||
};
|
||||
};
|
||||
|
||||
if(jj>start) split(I,V,start,jj-start,h);
|
||||
|
||||
for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1;
|
||||
if(jj==kk-1) I[jj]=-1;
|
||||
|
||||
if(start+len>kk) split(I,V,kk,start+len-kk,h);
|
||||
}
|
||||
|
||||
static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize)
|
||||
{
|
||||
off_t buckets[256];
|
||||
off_t i,h,len;
|
||||
|
||||
for(i=0;i<256;i++) buckets[i]=0;
|
||||
for(i=0;i<oldsize;i++) buckets[old[i]]++;
|
||||
for(i=1;i<256;i++) buckets[i]+=buckets[i-1];
|
||||
for(i=255;i>0;i--) buckets[i]=buckets[i-1];
|
||||
buckets[0]=0;
|
||||
|
||||
for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i;
|
||||
I[0]=oldsize;
|
||||
for(i=0;i<oldsize;i++) V[i]=buckets[old[i]];
|
||||
V[oldsize]=0;
|
||||
for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1;
|
||||
I[0]=-1;
|
||||
|
||||
for(h=1;I[0]!=-(oldsize+1);h+=h) {
|
||||
len=0;
|
||||
for(i=0;i<oldsize+1;) {
|
||||
if(I[i]<0) {
|
||||
len-=I[i];
|
||||
i-=I[i];
|
||||
} else {
|
||||
if(len) I[i-len]=-len;
|
||||
len=V[I[i]]+1-i;
|
||||
split(I,V,i,len,h);
|
||||
i+=len;
|
||||
len=0;
|
||||
};
|
||||
};
|
||||
if(len) I[i-len]=-len;
|
||||
};
|
||||
|
||||
for(i=0;i<oldsize+1;i++) I[V[i]]=i;
|
||||
}
|
||||
|
||||
static off_t matchlen(u_char *old,off_t oldsize,u_char *new,off_t newsize)
|
||||
{
|
||||
off_t i;
|
||||
|
||||
for(i=0;(i<oldsize)&&(i<newsize);i++)
|
||||
if(old[i]!=new[i]) break;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static off_t search(off_t *I,u_char *old,off_t oldsize,
|
||||
u_char *new,off_t newsize,off_t st,off_t en,off_t *pos)
|
||||
{
|
||||
off_t x,y;
|
||||
|
||||
if(en-st<2) {
|
||||
x=matchlen(old+I[st],oldsize-I[st],new,newsize);
|
||||
y=matchlen(old+I[en],oldsize-I[en],new,newsize);
|
||||
|
||||
if(x>y) {
|
||||
*pos=I[st];
|
||||
return x;
|
||||
} else {
|
||||
*pos=I[en];
|
||||
return y;
|
||||
}
|
||||
};
|
||||
|
||||
x=st+(en-st)/2;
|
||||
if(memcmp(old+I[x],new,MIN(oldsize-I[x],newsize))<0) {
|
||||
return search(I,old,oldsize,new,newsize,x,en,pos);
|
||||
} else {
|
||||
return search(I,old,oldsize,new,newsize,st,x,pos);
|
||||
};
|
||||
}
|
||||
|
||||
static void offtout(off_t x,u_char *buf)
|
||||
{
|
||||
off_t y;
|
||||
|
||||
if(x<0) y=-x; else y=x;
|
||||
|
||||
buf[0]=y%256;y-=buf[0];
|
||||
y=y/256;buf[1]=y%256;y-=buf[1];
|
||||
y=y/256;buf[2]=y%256;y-=buf[2];
|
||||
y=y/256;buf[3]=y%256;y-=buf[3];
|
||||
y=y/256;buf[4]=y%256;y-=buf[4];
|
||||
y=y/256;buf[5]=y%256;y-=buf[5];
|
||||
y=y/256;buf[6]=y%256;y-=buf[6];
|
||||
y=y/256;buf[7]=y%256;
|
||||
|
||||
if(x<0) buf[7]|=0x80;
|
||||
}
|
||||
|
||||
// This is main() from bsdiff.c, with the following changes:
|
||||
//
|
||||
// - old, oldsize, new, newsize are arguments; we don't load this
|
||||
// data from files. old and new are owned by the caller; we
|
||||
// don't free them at the end.
|
||||
//
|
||||
// - the "I" block of memory is owned by the caller, who passes a
|
||||
// pointer to *I, which can be NULL. This way if we call
|
||||
// bsdiff() multiple times with the same 'old' data, we only do
|
||||
// the qsufsort() step the first time.
|
||||
//
|
||||
int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize,
|
||||
const char* patch_filename)
|
||||
{
|
||||
int fd;
|
||||
off_t *I;
|
||||
off_t scan,pos,len;
|
||||
off_t lastscan,lastpos,lastoffset;
|
||||
off_t oldscore,scsc;
|
||||
off_t s,Sf,lenf,Sb,lenb;
|
||||
off_t overlap,Ss,lens;
|
||||
off_t i;
|
||||
off_t dblen,eblen;
|
||||
u_char *db,*eb;
|
||||
u_char buf[8];
|
||||
u_char header[32];
|
||||
FILE * pf;
|
||||
BZFILE * pfbz2;
|
||||
int bz2err;
|
||||
|
||||
if (*IP == NULL) {
|
||||
off_t* V;
|
||||
*IP = malloc((oldsize+1) * sizeof(off_t));
|
||||
V = malloc((oldsize+1) * sizeof(off_t));
|
||||
qsufsort(*IP, V, old, oldsize);
|
||||
free(V);
|
||||
}
|
||||
I = *IP;
|
||||
|
||||
if(((db=malloc(newsize+1))==NULL) ||
|
||||
((eb=malloc(newsize+1))==NULL)) err(1,NULL);
|
||||
dblen=0;
|
||||
eblen=0;
|
||||
|
||||
/* Create the patch file */
|
||||
if ((pf = fopen(patch_filename, "w")) == NULL)
|
||||
err(1, "%s", patch_filename);
|
||||
|
||||
/* Header is
|
||||
0 8 "BSDIFF40"
|
||||
8 8 length of bzip2ed ctrl block
|
||||
16 8 length of bzip2ed diff block
|
||||
24 8 length of new file */
|
||||
/* File is
|
||||
0 32 Header
|
||||
32 ?? Bzip2ed ctrl block
|
||||
?? ?? Bzip2ed diff block
|
||||
?? ?? Bzip2ed extra block */
|
||||
memcpy(header,"BSDIFF40",8);
|
||||
offtout(0, header + 8);
|
||||
offtout(0, header + 16);
|
||||
offtout(newsize, header + 24);
|
||||
if (fwrite(header, 32, 1, pf) != 1)
|
||||
err(1, "fwrite(%s)", patch_filename);
|
||||
|
||||
/* Compute the differences, writing ctrl as we go */
|
||||
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
|
||||
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
|
||||
scan=0;len=0;
|
||||
lastscan=0;lastpos=0;lastoffset=0;
|
||||
while(scan<newsize) {
|
||||
oldscore=0;
|
||||
|
||||
for(scsc=scan+=len;scan<newsize;scan++) {
|
||||
len=search(I,old,oldsize,new+scan,newsize-scan,
|
||||
0,oldsize,&pos);
|
||||
|
||||
for(;scsc<scan+len;scsc++)
|
||||
if((scsc+lastoffset<oldsize) &&
|
||||
(old[scsc+lastoffset] == new[scsc]))
|
||||
oldscore++;
|
||||
|
||||
if(((len==oldscore) && (len!=0)) ||
|
||||
(len>oldscore+8)) break;
|
||||
|
||||
if((scan+lastoffset<oldsize) &&
|
||||
(old[scan+lastoffset] == new[scan]))
|
||||
oldscore--;
|
||||
};
|
||||
|
||||
if((len!=oldscore) || (scan==newsize)) {
|
||||
s=0;Sf=0;lenf=0;
|
||||
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
|
||||
if(old[lastpos+i]==new[lastscan+i]) s++;
|
||||
i++;
|
||||
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
|
||||
};
|
||||
|
||||
lenb=0;
|
||||
if(scan<newsize) {
|
||||
s=0;Sb=0;
|
||||
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
|
||||
if(old[pos-i]==new[scan-i]) s++;
|
||||
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
|
||||
};
|
||||
};
|
||||
|
||||
if(lastscan+lenf>scan-lenb) {
|
||||
overlap=(lastscan+lenf)-(scan-lenb);
|
||||
s=0;Ss=0;lens=0;
|
||||
for(i=0;i<overlap;i++) {
|
||||
if(new[lastscan+lenf-overlap+i]==
|
||||
old[lastpos+lenf-overlap+i]) s++;
|
||||
if(new[scan-lenb+i]==
|
||||
old[pos-lenb+i]) s--;
|
||||
if(s>Ss) { Ss=s; lens=i+1; };
|
||||
};
|
||||
|
||||
lenf+=lens-overlap;
|
||||
lenb-=lens;
|
||||
};
|
||||
|
||||
for(i=0;i<lenf;i++)
|
||||
db[dblen+i]=new[lastscan+i]-old[lastpos+i];
|
||||
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
|
||||
eb[eblen+i]=new[lastscan+lenf+i];
|
||||
|
||||
dblen+=lenf;
|
||||
eblen+=(scan-lenb)-(lastscan+lenf);
|
||||
|
||||
offtout(lenf,buf);
|
||||
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
|
||||
|
||||
offtout((scan-lenb)-(lastscan+lenf),buf);
|
||||
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
|
||||
|
||||
offtout((pos-lenb)-(lastpos+lenf),buf);
|
||||
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
|
||||
|
||||
lastscan=scan-lenb;
|
||||
lastpos=pos-lenb;
|
||||
lastoffset=pos-scan;
|
||||
};
|
||||
};
|
||||
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
|
||||
|
||||
/* Compute size of compressed ctrl data */
|
||||
if ((len = ftello(pf)) == -1)
|
||||
err(1, "ftello");
|
||||
offtout(len-32, header + 8);
|
||||
|
||||
/* Write compressed diff data */
|
||||
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
|
||||
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
|
||||
BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
|
||||
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
|
||||
|
||||
/* Compute size of compressed diff data */
|
||||
if ((newsize = ftello(pf)) == -1)
|
||||
err(1, "ftello");
|
||||
offtout(newsize - len, header + 16);
|
||||
|
||||
/* Write compressed extra data */
|
||||
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
|
||||
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
|
||||
BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
|
||||
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
|
||||
if (bz2err != BZ_OK)
|
||||
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
|
||||
|
||||
/* Seek to the beginning, write the header, and close the file */
|
||||
if (fseeko(pf, 0, SEEK_SET))
|
||||
err(1, "fseeko");
|
||||
if (fwrite(header, 32, 1, pf) != 1)
|
||||
err(1, "fwrite(%s)", patch_filename);
|
||||
if (fclose(pf))
|
||||
err(1, "fclose");
|
||||
|
||||
/* Free the memory we used */
|
||||
free(db);
|
||||
free(eb);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file is a nearly line-for-line copy of bspatch.c from the
|
||||
// bsdiff-4.3 distribution; the primary differences being how the
|
||||
// input and output data are read and the error handling. Running
|
||||
// applypatch with the -l option will display the bsdiff license
|
||||
// notice.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <bzlib.h>
|
||||
|
||||
#include "mincrypt/sha.h"
|
||||
#include "applypatch.h"
|
||||
|
||||
void ShowBSDiffLicense() {
|
||||
puts("The bsdiff library used herein is:\n"
|
||||
"\n"
|
||||
"Copyright 2003-2005 Colin Percival\n"
|
||||
"All rights reserved\n"
|
||||
"\n"
|
||||
"Redistribution and use in source and binary forms, with or without\n"
|
||||
"modification, are permitted providing that the following conditions\n"
|
||||
"are met:\n"
|
||||
"1. Redistributions of source code must retain the above copyright\n"
|
||||
" notice, this list of conditions and the following disclaimer.\n"
|
||||
"2. Redistributions in binary form must reproduce the above copyright\n"
|
||||
" notice, this list of conditions and the following disclaimer in the\n"
|
||||
" documentation and/or other materials provided with the distribution.\n"
|
||||
"\n"
|
||||
"THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
|
||||
"IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
|
||||
"WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
|
||||
"ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n"
|
||||
"DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n"
|
||||
"DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n"
|
||||
"OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
|
||||
"HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
|
||||
"STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
|
||||
"IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
|
||||
"POSSIBILITY OF SUCH DAMAGE.\n"
|
||||
"\n------------------\n\n"
|
||||
"This program uses Julian R Seward's \"libbzip2\" library, available\n"
|
||||
"from http://www.bzip.org/.\n"
|
||||
);
|
||||
}
|
||||
|
||||
static off_t offtin(u_char *buf)
|
||||
{
|
||||
off_t y;
|
||||
|
||||
y=buf[7]&0x7F;
|
||||
y=y*256;y+=buf[6];
|
||||
y=y*256;y+=buf[5];
|
||||
y=y*256;y+=buf[4];
|
||||
y=y*256;y+=buf[3];
|
||||
y=y*256;y+=buf[2];
|
||||
y=y*256;y+=buf[1];
|
||||
y=y*256;y+=buf[0];
|
||||
|
||||
if(buf[7]&0x80) y=-y;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
int FillBuffer(unsigned char* buffer, int size, bz_stream* stream) {
|
||||
stream->next_out = (char*)buffer;
|
||||
stream->avail_out = size;
|
||||
while (stream->avail_out > 0) {
|
||||
int bzerr = BZ2_bzDecompress(stream);
|
||||
if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) {
|
||||
printf("bz error %d decompressing\n", bzerr);
|
||||
return -1;
|
||||
}
|
||||
if (stream->avail_out > 0) {
|
||||
printf("need %d more bytes\n", stream->avail_out);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx) {
|
||||
|
||||
unsigned char* new_data;
|
||||
ssize_t new_size;
|
||||
if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset,
|
||||
&new_data, &new_size) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sink(new_data, new_size, token) < new_size) {
|
||||
printf("short write of output: %d (%s)\n", errno, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (ctx) {
|
||||
SHA_update(ctx, new_data, new_size);
|
||||
}
|
||||
free(new_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
unsigned char** new_data, ssize_t* new_size) {
|
||||
// Patch data format:
|
||||
// 0 8 "BSDIFF40"
|
||||
// 8 8 X
|
||||
// 16 8 Y
|
||||
// 24 8 sizeof(newfile)
|
||||
// 32 X bzip2(control block)
|
||||
// 32+X Y bzip2(diff block)
|
||||
// 32+X+Y ??? bzip2(extra block)
|
||||
// with control block a set of triples (x,y,z) meaning "add x bytes
|
||||
// from oldfile to x bytes from the diff block; copy y bytes from the
|
||||
// extra block; seek forwards in oldfile by z bytes".
|
||||
|
||||
unsigned char* header = (unsigned char*) patch->data + patch_offset;
|
||||
if (memcmp(header, "BSDIFF40", 8) != 0) {
|
||||
printf("corrupt bsdiff patch file header (magic number)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ssize_t ctrl_len, data_len;
|
||||
ctrl_len = offtin(header+8);
|
||||
data_len = offtin(header+16);
|
||||
*new_size = offtin(header+24);
|
||||
|
||||
if (ctrl_len < 0 || data_len < 0 || *new_size < 0) {
|
||||
printf("corrupt patch file header (data lengths)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bzerr;
|
||||
|
||||
bz_stream cstream;
|
||||
cstream.next_in = patch->data + patch_offset + 32;
|
||||
cstream.avail_in = ctrl_len;
|
||||
cstream.bzalloc = NULL;
|
||||
cstream.bzfree = NULL;
|
||||
cstream.opaque = NULL;
|
||||
if ((bzerr = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) {
|
||||
printf("failed to bzinit control stream (%d)\n", bzerr);
|
||||
}
|
||||
|
||||
bz_stream dstream;
|
||||
dstream.next_in = patch->data + patch_offset + 32 + ctrl_len;
|
||||
dstream.avail_in = data_len;
|
||||
dstream.bzalloc = NULL;
|
||||
dstream.bzfree = NULL;
|
||||
dstream.opaque = NULL;
|
||||
if ((bzerr = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) {
|
||||
printf("failed to bzinit diff stream (%d)\n", bzerr);
|
||||
}
|
||||
|
||||
bz_stream estream;
|
||||
estream.next_in = patch->data + patch_offset + 32 + ctrl_len + data_len;
|
||||
estream.avail_in = patch->size - (patch_offset + 32 + ctrl_len + data_len);
|
||||
estream.bzalloc = NULL;
|
||||
estream.bzfree = NULL;
|
||||
estream.opaque = NULL;
|
||||
if ((bzerr = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) {
|
||||
printf("failed to bzinit extra stream (%d)\n", bzerr);
|
||||
}
|
||||
|
||||
*new_data = malloc(*new_size);
|
||||
if (*new_data == NULL) {
|
||||
printf("failed to allocate %ld bytes of memory for output file\n",
|
||||
(long)*new_size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
off_t oldpos = 0, newpos = 0;
|
||||
off_t ctrl[3];
|
||||
off_t len_read;
|
||||
int i;
|
||||
unsigned char buf[24];
|
||||
while (newpos < *new_size) {
|
||||
// Read control data
|
||||
if (FillBuffer(buf, 24, &cstream) != 0) {
|
||||
printf("error while reading control stream\n");
|
||||
return 1;
|
||||
}
|
||||
ctrl[0] = offtin(buf);
|
||||
ctrl[1] = offtin(buf+8);
|
||||
ctrl[2] = offtin(buf+16);
|
||||
|
||||
// Sanity check
|
||||
if (newpos + ctrl[0] > *new_size) {
|
||||
printf("corrupt patch (new file overrun)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read diff string
|
||||
if (FillBuffer(*new_data + newpos, ctrl[0], &dstream) != 0) {
|
||||
printf("error while reading diff stream\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add old data to diff string
|
||||
for (i = 0; i < ctrl[0]; ++i) {
|
||||
if ((oldpos+i >= 0) && (oldpos+i < old_size)) {
|
||||
(*new_data)[newpos+i] += old_data[oldpos+i];
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust pointers
|
||||
newpos += ctrl[0];
|
||||
oldpos += ctrl[0];
|
||||
|
||||
// Sanity check
|
||||
if (newpos + ctrl[1] > *new_size) {
|
||||
printf("corrupt patch (new file overrun)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read extra string
|
||||
if (FillBuffer(*new_data + newpos, ctrl[1], &estream) != 0) {
|
||||
printf("error while reading extra stream\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Adjust pointers
|
||||
newpos += ctrl[1];
|
||||
oldpos += ctrl[2];
|
||||
}
|
||||
|
||||
BZ2_bzDecompressEnd(&cstream);
|
||||
BZ2_bzDecompressEnd(&dstream);
|
||||
BZ2_bzDecompressEnd(&estream);
|
||||
return 0;
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "applypatch.h"
|
||||
|
||||
static int EliminateOpenFiles(char** files, int file_count) {
|
||||
DIR* d;
|
||||
struct dirent* de;
|
||||
d = opendir("/proc");
|
||||
if (d == NULL) {
|
||||
printf("error opening /proc: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
while ((de = readdir(d)) != 0) {
|
||||
int i;
|
||||
for (i = 0; de->d_name[i] != '\0' && isdigit(de->d_name[i]); ++i);
|
||||
if (de->d_name[i]) continue;
|
||||
|
||||
// de->d_name[i] is numeric
|
||||
|
||||
char path[FILENAME_MAX];
|
||||
strcpy(path, "/proc/");
|
||||
strcat(path, de->d_name);
|
||||
strcat(path, "/fd/");
|
||||
|
||||
DIR* fdd;
|
||||
struct dirent* fdde;
|
||||
fdd = opendir(path);
|
||||
if (fdd == NULL) {
|
||||
printf("error opening %s: %s\n", path, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
while ((fdde = readdir(fdd)) != 0) {
|
||||
char fd_path[FILENAME_MAX];
|
||||
char link[FILENAME_MAX];
|
||||
strcpy(fd_path, path);
|
||||
strcat(fd_path, fdde->d_name);
|
||||
|
||||
int count;
|
||||
count = readlink(fd_path, link, sizeof(link)-1);
|
||||
if (count >= 0) {
|
||||
link[count] = '\0';
|
||||
|
||||
// This is inefficient, but it should only matter if there are
|
||||
// lots of files in /cache, and lots of them are open (neither
|
||||
// of which should be true, especially in recovery).
|
||||
if (strncmp(link, "/cache/", 7) == 0) {
|
||||
int j;
|
||||
for (j = 0; j < file_count; ++j) {
|
||||
if (files[j] && strcmp(files[j], link) == 0) {
|
||||
printf("%s is open by %s\n", link, de->d_name);
|
||||
free(files[j]);
|
||||
files[j] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(fdd);
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FindExpendableFiles(char*** names, int* entries) {
|
||||
DIR* d;
|
||||
struct dirent* de;
|
||||
int size = 32;
|
||||
*entries = 0;
|
||||
*names = malloc(size * sizeof(char*));
|
||||
|
||||
char path[FILENAME_MAX];
|
||||
|
||||
// We're allowed to delete unopened regular files in any of these
|
||||
// directories.
|
||||
const char* dirs[2] = {"/cache", "/cache/recovery/otatest"};
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) {
|
||||
d = opendir(dirs[i]);
|
||||
if (d == NULL) {
|
||||
printf("error opening %s: %s\n", dirs[i], strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for regular files in the directory (not in any subdirectories).
|
||||
while ((de = readdir(d)) != 0) {
|
||||
strcpy(path, dirs[i]);
|
||||
strcat(path, "/");
|
||||
strcat(path, de->d_name);
|
||||
|
||||
// We can't delete CACHE_TEMP_SOURCE; if it's there we might have
|
||||
// restarted during installation and could be depending on it to
|
||||
// be there.
|
||||
if (strcmp(path, CACHE_TEMP_SOURCE) == 0) continue;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
if (*entries >= size) {
|
||||
size *= 2;
|
||||
*names = realloc(*names, size * sizeof(char*));
|
||||
}
|
||||
(*names)[(*entries)++] = strdup(path);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
printf("%d regular files in deletable directories\n", *entries);
|
||||
|
||||
if (EliminateOpenFiles(*names, *entries) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MakeFreeSpaceOnCache(size_t bytes_needed) {
|
||||
size_t free_now = FreeSpaceForFile("/cache");
|
||||
printf("%ld bytes free on /cache (%ld needed)\n",
|
||||
(long)free_now, (long)bytes_needed);
|
||||
|
||||
if (free_now >= bytes_needed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char** names;
|
||||
int entries;
|
||||
|
||||
if (FindExpendableFiles(&names, &entries) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (entries == 0) {
|
||||
// nothing we can delete to free up space!
|
||||
printf("no files can be deleted to free space on /cache\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We could try to be smarter about which files to delete: the
|
||||
// biggest ones? the smallest ones that will free up enough space?
|
||||
// the oldest? the newest?
|
||||
//
|
||||
// Instead, we'll be dumb.
|
||||
|
||||
int i;
|
||||
for (i = 0; i < entries && free_now < bytes_needed; ++i) {
|
||||
if (names[i]) {
|
||||
unlink(names[i]);
|
||||
free_now = FreeSpaceForFile("/cache");
|
||||
printf("deleted %s; now %ld bytes free\n", names[i], (long)free_now);
|
||||
free(names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < entries; ++i) {
|
||||
free(names[i]);
|
||||
}
|
||||
free(names);
|
||||
|
||||
return (free_now >= bytes_needed) ? 0 : -1;
|
||||
}
|
1010
applypatch/imgdiff.c
1010
applypatch/imgdiff.c
File diff suppressed because it is too large
Load Diff
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
// Image patch chunk types
|
||||
#define CHUNK_NORMAL 0
|
||||
#define CHUNK_GZIP 1 // version 1 only
|
||||
#define CHUNK_DEFLATE 2 // version 2 only
|
||||
#define CHUNK_RAW 3 // version 2 only
|
||||
|
||||
// The gzip header size is actually variable, but we currently don't
|
||||
// support gzipped data with any of the optional fields, so for now it
|
||||
// will always be ten bytes. See RFC 1952 for the definition of the
|
||||
// gzip format.
|
||||
#define GZIP_HEADER_LEN 10
|
||||
|
||||
// The gzip footer size really is fixed.
|
||||
#define GZIP_FOOTER_LEN 8
|
@ -1,118 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A script for testing imgdiff/applypatch. It takes two full OTA
|
||||
# packages as arguments. It generates (on the host) patches for all
|
||||
# the zip/jar/apk files they have in common, as well as boot and
|
||||
# recovery images. It then applies the patches on the device (or
|
||||
# emulator) and checks that the resulting file is correct.
|
||||
|
||||
EMULATOR_PORT=5580
|
||||
|
||||
# set to 0 to use a device instead
|
||||
USE_EMULATOR=0
|
||||
|
||||
# where on the device to do all the patching.
|
||||
WORK_DIR=/data/local/tmp
|
||||
|
||||
START_OTA_PACKAGE=$1
|
||||
END_OTA_PACKAGE=$2
|
||||
|
||||
# ------------------------
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
|
||||
if [ "$USE_EMULATOR" == 1 ]; then
|
||||
emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
|
||||
pid_emulator=$!
|
||||
ADB="adb -s emulator-$EMULATOR_PORT "
|
||||
else
|
||||
ADB="adb -d "
|
||||
fi
|
||||
|
||||
echo "waiting to connect to device"
|
||||
$ADB wait-for-device
|
||||
|
||||
# run a command on the device; exit with the exit status of the device
|
||||
# command.
|
||||
run_command() {
|
||||
$ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
|
||||
}
|
||||
|
||||
testname() {
|
||||
echo
|
||||
echo "$1"...
|
||||
testname="$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo
|
||||
echo FAIL: $testname
|
||||
echo
|
||||
[ "$open_pid" == "" ] || kill $open_pid
|
||||
[ "$pid_emulator" == "" ] || kill $pid_emulator
|
||||
exit 1
|
||||
}
|
||||
|
||||
sha1() {
|
||||
sha1sum $1 | awk '{print $1}'
|
||||
}
|
||||
|
||||
size() {
|
||||
stat -c %s $1 | tr -d '\n'
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# not necessary if we're about to kill the emulator, but nice for
|
||||
# running on real devices or already-running emulators.
|
||||
testname "removing test files"
|
||||
run_command rm $WORK_DIR/applypatch
|
||||
run_command rm $WORK_DIR/source
|
||||
run_command rm $WORK_DIR/target
|
||||
run_command rm $WORK_DIR/patch
|
||||
|
||||
[ "$pid_emulator" == "" ] || kill $pid_emulator
|
||||
|
||||
rm -rf $tmpdir
|
||||
}
|
||||
|
||||
$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
|
||||
|
||||
patch_and_apply() {
|
||||
local fn=$1
|
||||
shift
|
||||
|
||||
unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source
|
||||
unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target
|
||||
imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch
|
||||
bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs
|
||||
echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)"
|
||||
echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt
|
||||
$ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed"
|
||||
run_command rm /data/local/tmp/target
|
||||
$ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed"
|
||||
run_command /data/local/tmp/applypatch /data/local/tmp/source \
|
||||
/data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \
|
||||
$(sha1 $tmpdir/source):/data/local/tmp/patch \
|
||||
|| fail "applypatch of $fn failed"
|
||||
$ADB pull /data/local/tmp/target $tmpdir/result
|
||||
diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!"
|
||||
}
|
||||
|
||||
# --------------- basic execution ----------------------
|
||||
|
||||
for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \
|
||||
sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do
|
||||
patch_and_apply $i -z
|
||||
done
|
||||
patch_and_apply boot.img
|
||||
patch_and_apply system/recovery.img
|
||||
|
||||
|
||||
# --------------- cleanup ----------------------
|
||||
|
||||
cleanup
|
||||
|
||||
echo
|
||||
echo PASS
|
||||
echo
|
||||
|
@ -1,219 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
// See imgdiff.c in this directory for a description of the patch file
|
||||
// format.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "zlib.h"
|
||||
#include "mincrypt/sha.h"
|
||||
#include "applypatch.h"
|
||||
#include "imgdiff.h"
|
||||
#include "utils.h"
|
||||
|
||||
/*
|
||||
* Apply the patch given in 'patch_filename' to the source data given
|
||||
* by (old_data, old_size). Write the patched output to the 'output'
|
||||
* file, and update the SHA context with the output data as well.
|
||||
* Return 0 on success.
|
||||
*/
|
||||
int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx) {
|
||||
ssize_t pos = 12;
|
||||
char* header = patch->data;
|
||||
if (patch->size < 12) {
|
||||
printf("patch too short to contain header\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
|
||||
// (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
|
||||
// CHUNK_GZIP.)
|
||||
if (memcmp(header, "IMGDIFF2", 8) != 0) {
|
||||
printf("corrupt patch file header (magic number)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int num_chunks = Read4(header+8);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < num_chunks; ++i) {
|
||||
// each chunk's header record starts with 4 bytes.
|
||||
if (pos + 4 > patch->size) {
|
||||
printf("failed to read chunk %d record\n", i);
|
||||
return -1;
|
||||
}
|
||||
int type = Read4(patch->data + pos);
|
||||
pos += 4;
|
||||
|
||||
if (type == CHUNK_NORMAL) {
|
||||
char* normal_header = patch->data + pos;
|
||||
pos += 24;
|
||||
if (pos > patch->size) {
|
||||
printf("failed to read chunk %d normal header data\n", i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t src_start = Read8(normal_header);
|
||||
size_t src_len = Read8(normal_header+8);
|
||||
size_t patch_offset = Read8(normal_header+16);
|
||||
|
||||
ApplyBSDiffPatch(old_data + src_start, src_len,
|
||||
patch, patch_offset, sink, token, ctx);
|
||||
} else if (type == CHUNK_RAW) {
|
||||
char* raw_header = patch->data + pos;
|
||||
pos += 4;
|
||||
if (pos > patch->size) {
|
||||
printf("failed to read chunk %d raw header data\n", i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t data_len = Read4(raw_header);
|
||||
|
||||
if (pos + data_len > patch->size) {
|
||||
printf("failed to read chunk %d raw data\n", i);
|
||||
return -1;
|
||||
}
|
||||
SHA_update(ctx, patch->data + pos, data_len);
|
||||
if (sink((unsigned char*)patch->data + pos,
|
||||
data_len, token) != data_len) {
|
||||
printf("failed to write chunk %d raw data\n", i);
|
||||
return -1;
|
||||
}
|
||||
pos += data_len;
|
||||
} else if (type == CHUNK_DEFLATE) {
|
||||
// deflate chunks have an additional 60 bytes in their chunk header.
|
||||
char* deflate_header = patch->data + pos;
|
||||
pos += 60;
|
||||
if (pos > patch->size) {
|
||||
printf("failed to read chunk %d deflate header data\n", i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t src_start = Read8(deflate_header);
|
||||
size_t src_len = Read8(deflate_header+8);
|
||||
size_t patch_offset = Read8(deflate_header+16);
|
||||
size_t expanded_len = Read8(deflate_header+24);
|
||||
size_t target_len = Read8(deflate_header+32);
|
||||
int level = Read4(deflate_header+40);
|
||||
int method = Read4(deflate_header+44);
|
||||
int windowBits = Read4(deflate_header+48);
|
||||
int memLevel = Read4(deflate_header+52);
|
||||
int strategy = Read4(deflate_header+56);
|
||||
|
||||
// Decompress the source data; the chunk header tells us exactly
|
||||
// how big we expect it to be when decompressed.
|
||||
|
||||
unsigned char* expanded_source = malloc(expanded_len);
|
||||
if (expanded_source == NULL) {
|
||||
printf("failed to allocate %d bytes for expanded_source\n",
|
||||
expanded_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = src_len;
|
||||
strm.next_in = (unsigned char*)(old_data + src_start);
|
||||
strm.avail_out = expanded_len;
|
||||
strm.next_out = expanded_source;
|
||||
|
||||
int ret;
|
||||
ret = inflateInit2(&strm, -15);
|
||||
if (ret != Z_OK) {
|
||||
printf("failed to init source inflation: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Because we've provided enough room to accommodate the output
|
||||
// data, we expect one call to inflate() to suffice.
|
||||
ret = inflate(&strm, Z_SYNC_FLUSH);
|
||||
if (ret != Z_STREAM_END) {
|
||||
printf("source inflation returned %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
// We should have filled the output buffer exactly.
|
||||
if (strm.avail_out != 0) {
|
||||
printf("source inflation short by %d bytes\n", strm.avail_out);
|
||||
return -1;
|
||||
}
|
||||
inflateEnd(&strm);
|
||||
|
||||
// Next, apply the bsdiff patch (in memory) to the uncompressed
|
||||
// data.
|
||||
unsigned char* uncompressed_target_data;
|
||||
ssize_t uncompressed_target_size;
|
||||
if (ApplyBSDiffPatchMem(expanded_source, expanded_len,
|
||||
patch, patch_offset,
|
||||
&uncompressed_target_data,
|
||||
&uncompressed_target_size) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now compress the target data and append it to the output.
|
||||
|
||||
// we're done with the expanded_source data buffer, so we'll
|
||||
// reuse that memory to receive the output of deflate.
|
||||
unsigned char* temp_data = expanded_source;
|
||||
ssize_t temp_size = expanded_len;
|
||||
if (temp_size < 32768) {
|
||||
// ... unless the buffer is too small, in which case we'll
|
||||
// allocate a fresh one.
|
||||
free(temp_data);
|
||||
temp_data = malloc(32768);
|
||||
temp_size = 32768;
|
||||
}
|
||||
|
||||
// now the deflate stream
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = uncompressed_target_size;
|
||||
strm.next_in = uncompressed_target_data;
|
||||
ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
|
||||
do {
|
||||
strm.avail_out = temp_size;
|
||||
strm.next_out = temp_data;
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
ssize_t have = temp_size - strm.avail_out;
|
||||
|
||||
if (sink(temp_data, have, token) != have) {
|
||||
printf("failed to write %ld compressed bytes to output\n",
|
||||
(long)have);
|
||||
return -1;
|
||||
}
|
||||
SHA_update(ctx, temp_data, have);
|
||||
} while (ret != Z_STREAM_END);
|
||||
deflateEnd(&strm);
|
||||
|
||||
free(temp_data);
|
||||
free(uncompressed_target_data);
|
||||
} else {
|
||||
printf("patch chunk %d is unknown type %d\n", i, type);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "applypatch.h"
|
||||
#include "edify/expr.h"
|
||||
#include "mincrypt/sha.h"
|
||||
|
||||
int CheckMode(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
return 2;
|
||||
}
|
||||
return applypatch_check(argv[2], argc-3, argv+3);
|
||||
}
|
||||
|
||||
int SpaceMode(int argc, char** argv) {
|
||||
if (argc != 3) {
|
||||
return 2;
|
||||
}
|
||||
char* endptr;
|
||||
size_t bytes = strtol(argv[2], &endptr, 10);
|
||||
if (bytes == 0 && endptr == argv[2]) {
|
||||
printf("can't parse \"%s\" as byte count\n\n", argv[2]);
|
||||
return 1;
|
||||
}
|
||||
return CacheSizeCheck(bytes);
|
||||
}
|
||||
|
||||
// Parse arguments (which should be of the form "<sha1>" or
|
||||
// "<sha1>:<filename>" into the new parallel arrays *sha1s and
|
||||
// *patches (loading file contents into the patches). Returns 0 on
|
||||
// success.
|
||||
static int ParsePatchArgs(int argc, char** argv,
|
||||
char*** sha1s, Value*** patches, int* num_patches) {
|
||||
*num_patches = argc;
|
||||
*sha1s = malloc(*num_patches * sizeof(char*));
|
||||
*patches = malloc(*num_patches * sizeof(Value*));
|
||||
memset(*patches, 0, *num_patches * sizeof(Value*));
|
||||
|
||||
uint8_t digest[SHA_DIGEST_SIZE];
|
||||
|
||||
int i;
|
||||
for (i = 0; i < *num_patches; ++i) {
|
||||
char* colon = strchr(argv[i], ':');
|
||||
if (colon != NULL) {
|
||||
*colon = '\0';
|
||||
++colon;
|
||||
}
|
||||
|
||||
if (ParseSha1(argv[i], digest) != 0) {
|
||||
printf("failed to parse sha1 \"%s\"\n", argv[i]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
(*sha1s)[i] = argv[i];
|
||||
if (colon == NULL) {
|
||||
(*patches)[i] = NULL;
|
||||
} else {
|
||||
FileContents fc;
|
||||
if (LoadFileContents(colon, &fc, RETOUCH_DONT_MASK) != 0) {
|
||||
goto abort;
|
||||
}
|
||||
(*patches)[i] = malloc(sizeof(Value));
|
||||
(*patches)[i]->type = VAL_BLOB;
|
||||
(*patches)[i]->size = fc.size;
|
||||
(*patches)[i]->data = (char*)fc.data;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
for (i = 0; i < *num_patches; ++i) {
|
||||
Value* p = (*patches)[i];
|
||||
if (p != NULL) {
|
||||
free(p->data);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
free(*sha1s);
|
||||
free(*patches);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PatchMode(int argc, char** argv) {
|
||||
if (argc < 6) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
char* endptr;
|
||||
size_t target_size = strtol(argv[4], &endptr, 10);
|
||||
if (target_size == 0 && endptr == argv[4]) {
|
||||
printf("can't parse \"%s\" as byte count\n\n", argv[4]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char** sha1s;
|
||||
Value** patches;
|
||||
int num_patches;
|
||||
if (ParsePatchArgs(argc-5, argv+5, &sha1s, &patches, &num_patches) != 0) {
|
||||
printf("failed to parse patch args\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = applypatch(argv[1], argv[2], argv[3], target_size,
|
||||
num_patches, sha1s, patches);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < num_patches; ++i) {
|
||||
Value* p = patches[i];
|
||||
if (p != NULL) {
|
||||
free(p->data);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
free(sha1s);
|
||||
free(patches);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This program applies binary patches to files in a way that is safe
|
||||
// (the original file is not touched until we have the desired
|
||||
// replacement for it) and idempotent (it's okay to run this program
|
||||
// multiple times).
|
||||
//
|
||||
// - if the sha1 hash of <tgt-file> is <tgt-sha1>, does nothing and exits
|
||||
// successfully.
|
||||
//
|
||||
// - otherwise, if the sha1 hash of <src-file> is <src-sha1>, applies the
|
||||
// bsdiff <patch> to <src-file> to produce a new file (the type of patch
|
||||
// is automatically detected from the file header). If that new
|
||||
// file has sha1 hash <tgt-sha1>, moves it to replace <tgt-file>, and
|
||||
// exits successfully. Note that if <src-file> and <tgt-file> are
|
||||
// not the same, <src-file> is NOT deleted on success. <tgt-file>
|
||||
// may be the string "-" to mean "the same as src-file".
|
||||
//
|
||||
// - otherwise, or if any error is encountered, exits with non-zero
|
||||
// status.
|
||||
//
|
||||
// <src-file> (or <file> in check mode) may refer to an MTD partition
|
||||
// to read the source data. See the comments for the
|
||||
// LoadMTDContents() function above for the format of such a filename.
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
usage:
|
||||
printf(
|
||||
"usage: %s <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
|
||||
"[<src-sha1>:<patch> ...]\n"
|
||||
" or %s -c <file> [<sha1> ...]\n"
|
||||
" or %s -s <bytes>\n"
|
||||
" or %s -l\n"
|
||||
"\n"
|
||||
"Filenames may be of the form\n"
|
||||
" MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
|
||||
"to specify reading from or writing to an MTD partition.\n\n",
|
||||
argv[0], argv[0], argv[0], argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
int result;
|
||||
|
||||
if (strncmp(argv[1], "-l", 3) == 0) {
|
||||
result = ShowLicenses();
|
||||
} else if (strncmp(argv[1], "-c", 3) == 0) {
|
||||
result = CheckMode(argc, argv);
|
||||
} else if (strncmp(argv[1], "-s", 3) == 0) {
|
||||
result = SpaceMode(argc, argv);
|
||||
} else {
|
||||
result = PatchMode(argc, argv);
|
||||
}
|
||||
|
||||
if (result == 2) {
|
||||
goto usage;
|
||||
}
|
||||
return result;
|
||||
}
|
BIN
applypatch/testdata/new.file
vendored
BIN
applypatch/testdata/new.file
vendored
Binary file not shown.
BIN
applypatch/testdata/old.file
vendored
BIN
applypatch/testdata/old.file
vendored
Binary file not shown.
BIN
applypatch/testdata/patch.bsdiff
vendored
BIN
applypatch/testdata/patch.bsdiff
vendored
Binary file not shown.
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <stdio.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
/** Write a 4-byte value to f in little-endian order. */
|
||||
void Write4(int value, FILE* f) {
|
||||
fputc(value & 0xff, f);
|
||||
fputc((value >> 8) & 0xff, f);
|
||||
fputc((value >> 16) & 0xff, f);
|
||||
fputc((value >> 24) & 0xff, f);
|
||||
}
|
||||
|
||||
/** Write an 8-byte value to f in little-endian order. */
|
||||
void Write8(long long value, FILE* f) {
|
||||
fputc(value & 0xff, f);
|
||||
fputc((value >> 8) & 0xff, f);
|
||||
fputc((value >> 16) & 0xff, f);
|
||||
fputc((value >> 24) & 0xff, f);
|
||||
fputc((value >> 32) & 0xff, f);
|
||||
fputc((value >> 40) & 0xff, f);
|
||||
fputc((value >> 48) & 0xff, f);
|
||||
fputc((value >> 56) & 0xff, f);
|
||||
}
|
||||
|
||||
int Read2(void* pv) {
|
||||
unsigned char* p = pv;
|
||||
return (int)(((unsigned int)p[1] << 8) |
|
||||
(unsigned int)p[0]);
|
||||
}
|
||||
|
||||
int Read4(void* pv) {
|
||||
unsigned char* p = pv;
|
||||
return (int)(((unsigned int)p[3] << 24) |
|
||||
((unsigned int)p[2] << 16) |
|
||||
((unsigned int)p[1] << 8) |
|
||||
(unsigned int)p[0]);
|
||||
}
|
||||
|
||||
long long Read8(void* pv) {
|
||||
unsigned char* p = pv;
|
||||
return (long long)(((unsigned long long)p[7] << 56) |
|
||||
((unsigned long long)p[6] << 48) |
|
||||
((unsigned long long)p[5] << 40) |
|
||||
((unsigned long long)p[4] << 32) |
|
||||
((unsigned long long)p[3] << 24) |
|
||||
((unsigned long long)p[2] << 16) |
|
||||
((unsigned long long)p[1] << 8) |
|
||||
(unsigned long long)p[0]);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
|
||||
|
||||
$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
|
||||
$(if $($(board_define)), \
|
||||
$(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
|
||||
) \
|
||||
)
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libcrecovery
|
||||
LOCAL_C_INCLUDES := bootable/recovery/libcrecovery
|
||||
|
||||
LOCAL_SRC_FILES := bmlutils.c
|
||||
LOCAL_MODULE := libbmlutils
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
include $(BUILD_STATIC_LIBRARY)
|
@ -1,206 +0,0 @@
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/limits.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <common.h>
|
||||
|
||||
#define BML_UNLOCK_ALL 0x8A29 ///< unlock all partition RO -> RW
|
||||
|
||||
#ifndef BOARD_BML_BOOT
|
||||
#define BOARD_BML_BOOT "/dev/block/bml7"
|
||||
#endif
|
||||
|
||||
#ifndef BOARD_BML_RECOVERY
|
||||
#define BOARD_BML_RECOVERY "/dev/block/bml8"
|
||||
#endif
|
||||
|
||||
static int restore_internal(const char* bml, const char* filename)
|
||||
{
|
||||
char buf[4096];
|
||||
int dstfd, srcfd, bytes_read, bytes_written, total_read = 0;
|
||||
if (filename == NULL)
|
||||
srcfd = 0;
|
||||
else {
|
||||
srcfd = open(filename, O_RDONLY | O_LARGEFILE);
|
||||
if (srcfd < 0)
|
||||
return 2;
|
||||
}
|
||||
dstfd = open(bml, O_RDWR | O_LARGEFILE);
|
||||
if (dstfd < 0)
|
||||
return 3;
|
||||
if (ioctl(dstfd, BML_UNLOCK_ALL, 0))
|
||||
return 4;
|
||||
do {
|
||||
total_read += bytes_read = read(srcfd, buf, 4096);
|
||||
if (!bytes_read)
|
||||
break;
|
||||
if (bytes_read < 4096)
|
||||
memset(&buf[bytes_read], 0, 4096 - bytes_read);
|
||||
if (write(dstfd, buf, 4096) < 4096)
|
||||
return 5;
|
||||
} while(bytes_read == 4096);
|
||||
|
||||
close(dstfd);
|
||||
close(srcfd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_bml_restore_raw_partition(const char *partition, const char *filename)
|
||||
{
|
||||
if (strcmp(partition, "boot") != 0 && strcmp(partition, "recovery") != 0 && strcmp(partition, "recoveryonly") != 0 && partition[0] != '/')
|
||||
return 6;
|
||||
|
||||
int ret = -1;
|
||||
if (strcmp(partition, "recoveryonly") != 0) {
|
||||
// always restore boot, regardless of whether recovery or boot is flashed.
|
||||
// this is because boot and recovery are the same on some samsung phones.
|
||||
// unless of course, recoveryonly is explictly chosen (bml8)
|
||||
ret = restore_internal(BOARD_BML_BOOT, filename);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (strcmp(partition, "recovery") == 0 || strcmp(partition, "recoveryonly") == 0)
|
||||
ret = restore_internal(BOARD_BML_RECOVERY, filename);
|
||||
|
||||
// support explicitly provided device paths
|
||||
if (partition[0] == '/')
|
||||
ret = restore_internal(partition, filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cmd_bml_backup_raw_partition(const char *partition, const char *out_file)
|
||||
{
|
||||
char* bml;
|
||||
if (strcmp("boot", partition) == 0)
|
||||
bml = BOARD_BML_BOOT;
|
||||
else if (strcmp("recovery", partition) == 0)
|
||||
bml = BOARD_BML_RECOVERY;
|
||||
else if (partition[0] == '/') {
|
||||
// support explicitly provided device paths
|
||||
bml = partition;
|
||||
}
|
||||
else {
|
||||
printf("Invalid partition.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ch;
|
||||
FILE *in;
|
||||
FILE *out;
|
||||
int val = 0;
|
||||
char buf[512];
|
||||
unsigned sz = 0;
|
||||
unsigned i;
|
||||
int ret = -1;
|
||||
char *in_file = bml;
|
||||
|
||||
in = fopen ( in_file, "r" );
|
||||
if (in == NULL)
|
||||
goto ERROR3;
|
||||
|
||||
out = fopen ( out_file, "w" );
|
||||
if (out == NULL)
|
||||
goto ERROR2;
|
||||
|
||||
fseek(in, 0L, SEEK_END);
|
||||
sz = ftell(in);
|
||||
fseek(in, 0L, SEEK_SET);
|
||||
|
||||
if (sz % 512)
|
||||
{
|
||||
while ( ( ch = fgetc ( in ) ) != EOF )
|
||||
fputc ( ch, out );
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i=0; i< (sz/512); i++)
|
||||
{
|
||||
if ((fread(buf, 512, 1, in)) != 1)
|
||||
goto ERROR1;
|
||||
if ((fwrite(buf, 512, 1, out)) != 1)
|
||||
goto ERROR1;
|
||||
}
|
||||
}
|
||||
|
||||
fsync(out);
|
||||
ret = 0;
|
||||
ERROR1:
|
||||
fclose ( out );
|
||||
ERROR2:
|
||||
fclose ( in );
|
||||
ERROR3:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cmd_bml_erase_raw_partition(const char *partition)
|
||||
{
|
||||
// TODO: implement raw wipe
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_bml_erase_partition(const char *partition, const char *filesystem)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cmd_bml_get_partition_device(const char *partition, char *device)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int format_rfs_device (const char *device, const char *path) {
|
||||
const char *fatsize = "32";
|
||||
const char *sectorsize = "1";
|
||||
|
||||
if (strcmp(path, "/datadata") == 0 || strcmp(path, "/cache") == 0) {
|
||||
fatsize = "16";
|
||||
}
|
||||
|
||||
// Just in case /data sector size needs to be altered
|
||||
else if (strcmp(path, "/data") == 0 ) {
|
||||
sectorsize = "1";
|
||||
}
|
||||
|
||||
// dump 10KB of zeros to partition before format due to fat.format bug
|
||||
char cmd[PATH_MAX];
|
||||
|
||||
sprintf(cmd, "/sbin/dd if=/dev/zero of=%s bs=4096 count=10", device);
|
||||
if(__system(cmd)) {
|
||||
printf("failure while zeroing rfs partition.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Run fat.format
|
||||
sprintf(cmd, "/sbin/fat.format -F %s -S 4096 -s %s %s", fatsize, sectorsize, device);
|
||||
if(__system(cmd)) {
|
||||
printf("failure while running fat.format\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#ifndef BMLUTILS_H_
|
||||
#define BMLUTILS_H_
|
||||
|
||||
int format_rfs_device (const char *device, const char *path);
|
||||
|
||||
#endif // BMLUTILS_H_
|
253
bootloader.c
253
bootloader.c
@ -22,114 +22,97 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v);
|
||||
static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v);
|
||||
static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v);
|
||||
static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v);
|
||||
|
||||
int get_bootloader_message(struct bootloader_message *out) {
|
||||
Volume* v = volume_for_path("/misc");
|
||||
if(v)
|
||||
{
|
||||
if (strcmp(v->fs_type, "mtd") == 0) {
|
||||
return get_bootloader_message_mtd(out, v);
|
||||
} else if (strcmp(v->fs_type, "emmc") == 0) {
|
||||
return get_bootloader_message_block(out, v);
|
||||
}
|
||||
LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int set_bootloader_message(const struct bootloader_message *in) {
|
||||
Volume* v = volume_for_path("/misc");
|
||||
if(v)
|
||||
{
|
||||
if (strcmp(v->fs_type, "mtd") == 0) {
|
||||
return set_bootloader_message_mtd(in, v);
|
||||
} else if (strcmp(v->fs_type, "emmc") == 0) {
|
||||
return set_bootloader_message_block(in, v);
|
||||
}
|
||||
LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// for misc partitions on MTD
|
||||
// ------------------------------
|
||||
|
||||
static const char *CACHE_NAME = "CACHE:";
|
||||
static const char *MISC_NAME = "MISC:";
|
||||
static const int MISC_PAGES = 3; // number of pages to save
|
||||
static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page
|
||||
|
||||
static int get_bootloader_message_mtd(struct bootloader_message *out,
|
||||
const Volume* v) {
|
||||
#ifdef LOG_VERBOSE
|
||||
static void dump_data(const char *data, int len) {
|
||||
int pos;
|
||||
for (pos = 0; pos < len; ) {
|
||||
printf("%05x: %02x", pos, data[pos]);
|
||||
for (++pos; pos < len && (pos % 24) != 0; ++pos) {
|
||||
printf(" %02x", data[pos]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int get_bootloader_message(struct bootloader_message *out) {
|
||||
size_t write_size;
|
||||
mtd_scan_partitions();
|
||||
const MtdPartition *part = mtd_find_partition_by_name(v->device);
|
||||
const MtdPartition *part = get_root_mtd_partition(MISC_NAME);
|
||||
if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
|
||||
LOGE("Can't find %s\n", v->device);
|
||||
LOGE("Can't find %s\n", MISC_NAME);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MtdReadContext *read = mtd_read_partition(part);
|
||||
if (read == NULL) {
|
||||
LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
|
||||
LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
const ssize_t size = write_size * MISC_PAGES;
|
||||
char data[size];
|
||||
ssize_t r = mtd_read_data(read, data, size);
|
||||
if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
|
||||
if (r != size) LOGE("Can't read %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
mtd_read_close(read);
|
||||
if (r != size) return -1;
|
||||
|
||||
#ifdef LOG_VERBOSE
|
||||
printf("\n--- get_bootloader_message ---\n");
|
||||
dump_data(data, size);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out));
|
||||
return 0;
|
||||
}
|
||||
static int set_bootloader_message_mtd(const struct bootloader_message *in,
|
||||
const Volume* v) {
|
||||
|
||||
int set_bootloader_message(const struct bootloader_message *in) {
|
||||
size_t write_size;
|
||||
mtd_scan_partitions();
|
||||
const MtdPartition *part = mtd_find_partition_by_name(v->device);
|
||||
const MtdPartition *part = get_root_mtd_partition(MISC_NAME);
|
||||
if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
|
||||
LOGE("Can't find %s\n", v->device);
|
||||
LOGE("Can't find %s\n", MISC_NAME);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MtdReadContext *read = mtd_read_partition(part);
|
||||
if (read == NULL) {
|
||||
LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
|
||||
LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t size = write_size * MISC_PAGES;
|
||||
char data[size];
|
||||
ssize_t r = mtd_read_data(read, data, size);
|
||||
if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
|
||||
if (r != size) LOGE("Can't read %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
mtd_read_close(read);
|
||||
if (r != size) return -1;
|
||||
|
||||
memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in));
|
||||
|
||||
#ifdef LOG_VERBOSE
|
||||
printf("\n--- set_bootloader_message ---\n");
|
||||
dump_data(data, size);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
MtdWriteContext *write = mtd_write_partition(part);
|
||||
if (write == NULL) {
|
||||
LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
|
||||
LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (mtd_write_data(write, data, size) != size) {
|
||||
LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno));
|
||||
LOGE("Can't write %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
if (mtd_write_close(write)) {
|
||||
LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno));
|
||||
LOGE("Can't finish %s\n(%s)\n", MISC_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -137,70 +120,6 @@ static int set_bootloader_message_mtd(const struct bootloader_message *in,
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------
|
||||
// for misc partitions on block devices
|
||||
// ------------------------------------
|
||||
|
||||
static void wait_for_device(const char* fn) {
|
||||
int tries = 0;
|
||||
int ret;
|
||||
struct stat buf;
|
||||
do {
|
||||
++tries;
|
||||
ret = stat(fn, &buf);
|
||||
if (ret) {
|
||||
printf("stat %s try %d: %s\n", fn, tries, strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
} while (ret && tries < 10);
|
||||
if (ret) {
|
||||
printf("failed to stat %s\n", fn);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_bootloader_message_block(struct bootloader_message *out,
|
||||
const Volume* v) {
|
||||
wait_for_device(v->device);
|
||||
FILE* f = fopen(v->device, "rb");
|
||||
if (f == NULL) {
|
||||
LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
struct bootloader_message temp;
|
||||
int count = fread(&temp, sizeof(temp), 1, f);
|
||||
if (count != 1) {
|
||||
LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (fclose(f) != 0) {
|
||||
LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
memcpy(out, &temp, sizeof(temp));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_bootloader_message_block(const struct bootloader_message *in,
|
||||
const Volume* v) {
|
||||
wait_for_device(v->device);
|
||||
FILE* f = fopen(v->device, "wb");
|
||||
if (f == NULL) {
|
||||
LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
int count = fwrite(in, sizeof(*in), 1, f);
|
||||
if (count != 1) {
|
||||
LOGE("Failed writing %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (fclose(f) != 0) {
|
||||
LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Update Image
|
||||
*
|
||||
* - will be stored in the "cache" partition
|
||||
@ -211,27 +130,27 @@ static int set_bootloader_message_block(const struct bootloader_message *in,
|
||||
* - two raw bitmaps will be included, the "busy" and "fail" bitmaps
|
||||
* - for dream, the bitmaps will be 320x480x16bpp RGB565
|
||||
*/
|
||||
|
||||
|
||||
#define UPDATE_MAGIC "MSM-RADIO-UPDATE"
|
||||
#define UPDATE_MAGIC_SIZE 16
|
||||
#define UPDATE_VERSION 0x00010000
|
||||
|
||||
|
||||
struct update_header {
|
||||
unsigned char MAGIC[UPDATE_MAGIC_SIZE];
|
||||
|
||||
|
||||
unsigned version;
|
||||
unsigned size;
|
||||
|
||||
|
||||
unsigned image_offset;
|
||||
unsigned image_length;
|
||||
|
||||
|
||||
unsigned bitmap_width;
|
||||
unsigned bitmap_height;
|
||||
unsigned bitmap_bpp;
|
||||
|
||||
|
||||
unsigned busy_bitmap_offset;
|
||||
unsigned busy_bitmap_length;
|
||||
|
||||
|
||||
unsigned fail_bitmap_offset;
|
||||
unsigned fail_bitmap_length;
|
||||
};
|
||||
@ -240,113 +159,109 @@ int write_update_for_bootloader(
|
||||
const char *update, int update_length,
|
||||
int bitmap_width, int bitmap_height, int bitmap_bpp,
|
||||
const char *busy_bitmap, const char *fail_bitmap) {
|
||||
if (ensure_path_unmounted("/cache")) {
|
||||
LOGE("Can't unmount /cache\n");
|
||||
if (ensure_root_path_unmounted(CACHE_NAME)) {
|
||||
LOGE("Can't unmount %s\n", CACHE_NAME);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const MtdPartition *part = mtd_find_partition_by_name("cache");
|
||||
|
||||
const MtdPartition *part = get_root_mtd_partition(CACHE_NAME);
|
||||
if (part == NULL) {
|
||||
LOGE("Can't find cache\n");
|
||||
LOGE("Can't find %s\n", CACHE_NAME);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
MtdWriteContext *write = mtd_write_partition(part);
|
||||
if (write == NULL) {
|
||||
LOGE("Can't open cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't open %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Write an invalid (zero) header first, to disable any previous
|
||||
* update and any other structured contents (like a filesystem),
|
||||
* and as a placeholder for the amount of space required.
|
||||
*/
|
||||
|
||||
|
||||
struct update_header header;
|
||||
memset(&header, 0, sizeof(header));
|
||||
const ssize_t header_size = sizeof(header);
|
||||
if (mtd_write_data(write, (char*) &header, header_size) != header_size) {
|
||||
LOGE("Can't write header to cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't write header to %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Write each section individually block-aligned, so we can write
|
||||
* each block independently without complicated buffering.
|
||||
*/
|
||||
|
||||
|
||||
memcpy(&header.MAGIC, UPDATE_MAGIC, UPDATE_MAGIC_SIZE);
|
||||
header.version = UPDATE_VERSION;
|
||||
header.size = header_size;
|
||||
|
||||
off_t image_start_pos = mtd_erase_blocks(write, 0);
|
||||
|
||||
header.image_offset = mtd_erase_blocks(write, 0);
|
||||
header.image_length = update_length;
|
||||
if ((int) header.image_offset == -1 ||
|
||||
mtd_write_data(write, update, update_length) != update_length) {
|
||||
LOGE("Can't write update to cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't write update to %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
off_t busy_start_pos = mtd_erase_blocks(write, 0);
|
||||
header.image_offset = mtd_find_write_start(write, image_start_pos);
|
||||
|
||||
|
||||
header.bitmap_width = bitmap_width;
|
||||
header.bitmap_height = bitmap_height;
|
||||
header.bitmap_bpp = bitmap_bpp;
|
||||
|
||||
|
||||
int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height;
|
||||
|
||||
|
||||
header.busy_bitmap_offset = mtd_erase_blocks(write, 0);
|
||||
header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0;
|
||||
if ((int) header.busy_bitmap_offset == -1 ||
|
||||
mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) {
|
||||
LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
off_t fail_start_pos = mtd_erase_blocks(write, 0);
|
||||
header.busy_bitmap_offset = mtd_find_write_start(write, busy_start_pos);
|
||||
|
||||
|
||||
header.fail_bitmap_offset = mtd_erase_blocks(write, 0);
|
||||
header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0;
|
||||
if ((int) header.fail_bitmap_offset == -1 ||
|
||||
mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) {
|
||||
LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
mtd_erase_blocks(write, 0);
|
||||
header.fail_bitmap_offset = mtd_find_write_start(write, fail_start_pos);
|
||||
|
||||
|
||||
/* Write the header last, after all the blocks it refers to, so that
|
||||
* when the magic number is installed everything is valid.
|
||||
*/
|
||||
|
||||
|
||||
if (mtd_write_close(write)) {
|
||||
LOGE("Can't finish writing cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't finish writing %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
write = mtd_write_partition(part);
|
||||
if (write == NULL) {
|
||||
LOGE("Can't reopen cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't reopen %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (mtd_write_data(write, (char*) &header, header_size) != header_size) {
|
||||
LOGE("Can't rewrite header to cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't rewrite header to %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mtd_erase_blocks(write, 0) != image_start_pos) {
|
||||
LOGE("Misalignment rewriting cache\n(%s)\n", strerror(errno));
|
||||
|
||||
if (mtd_erase_blocks(write, 0) != (off_t) header.image_offset) {
|
||||
LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
mtd_write_close(write);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (mtd_write_close(write)) {
|
||||
LOGE("Can't finish header of cache\n(%s)\n", strerror(errno));
|
||||
LOGE("Can't finish header of %s\n(%s)\n", CACHE_NAME, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
1148
commands.c
Normal file
1148
commands.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
* 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.
|
||||
@ -14,16 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _UPDATER_UPDATER_H_
|
||||
#define _UPDATER_UPDATER_H_
|
||||
#ifndef RECOVERY_COMMANDS_H_
|
||||
#define RECOVERY_COMMANDS_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include "minzip/Zip.h"
|
||||
|
||||
typedef struct {
|
||||
FILE* cmd_pipe;
|
||||
ZipArchive* package_zip;
|
||||
int version;
|
||||
} UpdaterInfo;
|
||||
ZipArchive *package;
|
||||
} RecoveryCommandContext;
|
||||
|
||||
#endif
|
||||
int register_update_commands(RecoveryCommandContext *ctx);
|
||||
|
||||
#endif // RECOVERY_COMMANDS_H_
|
75
common.h
75
common.h
@ -26,23 +26,17 @@ void ui_init();
|
||||
int ui_wait_key(); // waits for a key/button press, returns the code
|
||||
int ui_key_pressed(int key); // returns >0 if the code is currently pressed
|
||||
int ui_text_visible(); // returns >0 if text log is currently visible
|
||||
int ui_text_ever_visible(); // returns >0 if text log was ever visible
|
||||
void ui_show_text(int visible);
|
||||
void ui_clear_key_queue();
|
||||
|
||||
// Write a message to the on-screen log shown with Alt-L (also to stderr).
|
||||
// The screen is small, and users may need to report these messages to support,
|
||||
// so keep the output short and not too cryptic.
|
||||
void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void ui_printlogtail(int nb_lines);
|
||||
|
||||
void ui_reset_text_col();
|
||||
void ui_set_show_text(int value);
|
||||
void ui_print(const char *fmt, ...);
|
||||
|
||||
// Display some header text followed by a menu of items, which appears
|
||||
// at the top of the screen (in place of any scrolling ui_print()
|
||||
// output, if necessary).
|
||||
int ui_start_menu(char** headers, char** items, int initial_selection);
|
||||
void ui_start_menu(char** headers, char** items);
|
||||
// Set the menu highlight to the given index, and return it (capped to
|
||||
// the range [0..numitems).
|
||||
int ui_menu_select(int sel);
|
||||
@ -50,15 +44,12 @@ int ui_menu_select(int sel);
|
||||
// statements will be displayed.
|
||||
void ui_end_menu();
|
||||
|
||||
int ui_get_showing_back_button();
|
||||
void ui_set_showing_back_button(int showBackButton);
|
||||
|
||||
// Set the icon (normally the only thing visible besides the progress bar).
|
||||
enum {
|
||||
BACKGROUND_ICON_NONE,
|
||||
BACKGROUND_ICON_UNPACKING,
|
||||
BACKGROUND_ICON_INSTALLING,
|
||||
BACKGROUND_ICON_ERROR,
|
||||
BACKGROUND_ICON_CLOCKWORK,
|
||||
BACKGROUND_ICON_FIRMWARE_INSTALLING,
|
||||
BACKGROUND_ICON_FIRMWARE_ERROR,
|
||||
NUM_BACKGROUND_ICONS
|
||||
@ -78,7 +69,7 @@ void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope
|
||||
|
||||
// Default allocation of progress bar segments to operations
|
||||
static const int VERIFICATION_PROGRESS_TIME = 60;
|
||||
static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
|
||||
static const float VERIFICATION_PROGRESS_FRACTION = 0.5;
|
||||
static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
|
||||
static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
|
||||
|
||||
@ -89,65 +80,15 @@ void ui_show_indeterminate_progress();
|
||||
void ui_reset_progress();
|
||||
|
||||
#define LOGE(...) ui_print("E:" __VA_ARGS__)
|
||||
#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
|
||||
#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
|
||||
#define LOGW(...) fprintf(stderr, "W:" __VA_ARGS__)
|
||||
#define LOGI(...) fprintf(stderr, "I:" __VA_ARGS__)
|
||||
|
||||
#if 0
|
||||
#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__)
|
||||
#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__)
|
||||
#define LOGV(...) fprintf(stderr, "V:" __VA_ARGS__)
|
||||
#define LOGD(...) fprintf(stderr, "D:" __VA_ARGS__)
|
||||
#else
|
||||
#define LOGV(...) do {} while (0)
|
||||
#define LOGD(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define EXPAND(x) STRINGIFY(x)
|
||||
|
||||
typedef struct {
|
||||
const char* mount_point; // eg. "/cache". must live in the root directory.
|
||||
|
||||
const char* fs_type; // "yaffs2" or "ext4" or "vfat"
|
||||
|
||||
const char* device; // MTD partition name if fs_type == "yaffs"
|
||||
// block device if fs_type == "ext4" or "vfat"
|
||||
|
||||
const char* device2; // alternative device to try if fs_type
|
||||
// == "ext4" or "vfat" and mounting
|
||||
// 'device' fails
|
||||
|
||||
long long length; // (ext4 partition only) when
|
||||
// formatting, size to use for the
|
||||
// partition. 0 or negative number
|
||||
// means to format all but the last
|
||||
// (that much).
|
||||
|
||||
const char* fs_type2;
|
||||
|
||||
const char* fs_options;
|
||||
|
||||
const char* fs_options2;
|
||||
} Volume;
|
||||
|
||||
typedef struct {
|
||||
// number of frames in indeterminate progress bar animation
|
||||
int indeterminate_frames;
|
||||
|
||||
// number of frames per second to try to maintain when animating
|
||||
int update_fps;
|
||||
|
||||
// number of frames in installing animation. may be zero for a
|
||||
// static installation icon.
|
||||
int installing_frames;
|
||||
|
||||
// the install icon is animated by drawing images containing the
|
||||
// changing part over the base icon. These specify the
|
||||
// coordinates of the upper-left corner.
|
||||
int install_overlay_offset_x;
|
||||
int install_overlay_offset_y;
|
||||
|
||||
} UIParameters;
|
||||
|
||||
// fopen a file, mounting volumes and making parent dirs as necessary.
|
||||
FILE* fopen_path(const char *path, const char *mode);
|
||||
|
||||
#endif // RECOVERY_COMMON_H
|
||||
|
@ -1,68 +0,0 @@
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "recovery_ui.h"
|
||||
#include "common.h"
|
||||
#include "extendedcommands.h"
|
||||
|
||||
|
||||
int device_toggle_display(volatile char* key_pressed, int key_code) {
|
||||
int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT];
|
||||
if (alt && key_code == KEY_L)
|
||||
return 1;
|
||||
// allow toggling of the display if the correct key is pressed, and the display toggle is allowed or the display is currently off
|
||||
if (ui_get_showing_back_button()) {
|
||||
return 0;
|
||||
//return get_allow_toggle_display() && (key_code == KEY_HOME || key_code == KEY_MENU || key_code == KEY_END);
|
||||
}
|
||||
return get_allow_toggle_display() && (key_code == KEY_HOME || key_code == KEY_MENU || key_code == KEY_POWER || key_code == KEY_END);
|
||||
}
|
||||
|
||||
int device_handle_key(int key_code, int visible) {
|
||||
if (visible) {
|
||||
switch (key_code) {
|
||||
case KEY_CAPSLOCK:
|
||||
case KEY_DOWN:
|
||||
case KEY_VOLUMEDOWN:
|
||||
case KEY_MENU:
|
||||
return HIGHLIGHT_DOWN;
|
||||
|
||||
case KEY_LEFTSHIFT:
|
||||
case KEY_UP:
|
||||
case KEY_VOLUMEUP:
|
||||
case KEY_HOME:
|
||||
return HIGHLIGHT_UP;
|
||||
|
||||
case KEY_POWER:
|
||||
if (ui_get_showing_back_button()) {
|
||||
return SELECT_ITEM;
|
||||
}
|
||||
if (!get_allow_toggle_display() && ui_menu_level > 0) {
|
||||
return GO_BACK;
|
||||
}
|
||||
break;
|
||||
case KEY_LEFTBRACE:
|
||||
case KEY_ENTER:
|
||||
case BTN_MOUSE:
|
||||
case KEY_CAMERA:
|
||||
case KEY_F21:
|
||||
case KEY_SEND:
|
||||
return SELECT_ITEM;
|
||||
|
||||
case KEY_END:
|
||||
case KEY_BACKSPACE:
|
||||
case KEY_SEARCH:
|
||||
if (ui_get_showing_back_button()) {
|
||||
return SELECT_ITEM;
|
||||
}
|
||||
if (!get_allow_toggle_display() && ui_menu_level > 0) {
|
||||
return GO_BACK;
|
||||
}
|
||||
case KEY_BACK:
|
||||
if (ui_menu_level > 0) {
|
||||
return GO_BACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO_ACTION;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <linux/input.h>
|
||||
|
||||
#include "recovery_ui.h"
|
||||
#include "common.h"
|
||||
#include "extendedcommands.h"
|
||||
|
||||
char* MENU_HEADERS[] = { NULL };
|
||||
|
||||
char* MENU_ITEMS[] = { "reboot system now",
|
||||
"install zip from sdcard",
|
||||
"wipe data/factory reset",
|
||||
"wipe cache partition",
|
||||
"backup and restore",
|
||||
"mounts and storage",
|
||||
"advanced",
|
||||
NULL };
|
||||
|
||||
void device_ui_init(UIParameters* ui_parameters) {
|
||||
}
|
||||
|
||||
int device_recovery_start() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_reboot_now(volatile char* key_pressed, int key_code) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_perform_action(int which) {
|
||||
return which;
|
||||
}
|
||||
|
||||
int device_wipe_data() {
|
||||
return 0;
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
edify_src_files := \
|
||||
lexer.l \
|
||||
parser.y \
|
||||
expr.c
|
||||
|
||||
# "-x c" forces the lex/yacc files to be compiled as c;
|
||||
# the build system otherwise forces them to be c++.
|
||||
edify_cflags := -x c
|
||||
|
||||
#
|
||||
# Build the host-side command line tool
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(edify_src_files) \
|
||||
main.c
|
||||
|
||||
LOCAL_CFLAGS := $(edify_cflags) -g -O0
|
||||
LOCAL_MODULE := edify
|
||||
LOCAL_YACCFLAGS := -v
|
||||
|
||||
include $(BUILD_HOST_EXECUTABLE)
|
||||
|
||||
#
|
||||
# Build the device-side library
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(edify_src_files)
|
||||
|
||||
LOCAL_CFLAGS := $(edify_cflags)
|
||||
LOCAL_MODULE := libedify
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
108
edify/README
108
edify/README
@ -1,108 +0,0 @@
|
||||
Update scripts (from donut onwards) are written in a new little
|
||||
scripting language ("edify") that is superficially somewhat similar to
|
||||
the old one ("amend"). This is a brief overview of the new language.
|
||||
|
||||
- The entire script is a single expression.
|
||||
|
||||
- All expressions are string-valued.
|
||||
|
||||
- String literals appear in double quotes. \n, \t, \", and \\ are
|
||||
understood, as are hexadecimal escapes like \x4a.
|
||||
|
||||
- String literals consisting of only letters, numbers, colons,
|
||||
underscores, slashes, and periods don't need to be in double quotes.
|
||||
|
||||
- The following words are reserved:
|
||||
|
||||
if then else endif
|
||||
|
||||
They have special meaning when unquoted. (In quotes, they are just
|
||||
string literals.)
|
||||
|
||||
- When used as a boolean, the empty string is "false" and all other
|
||||
strings are "true".
|
||||
|
||||
- All functions are actually macros (in the Lisp sense); the body of
|
||||
the function can control which (if any) of the arguments are
|
||||
evaluated. This means that functions can act as control
|
||||
structures.
|
||||
|
||||
- Operators (like "&&" and "||") are just syntactic sugar for builtin
|
||||
functions, so they can act as control structures as well.
|
||||
|
||||
- ";" is a binary operator; evaluating it just means to first evaluate
|
||||
the left side, then the right. It can also appear after any
|
||||
expression.
|
||||
|
||||
- Comments start with "#" and run to the end of the line.
|
||||
|
||||
|
||||
|
||||
Some examples:
|
||||
|
||||
- There's no distinction between quoted and unquoted strings; the
|
||||
quotes are only needed if you want characters like whitespace to
|
||||
appear in the string. The following expressions all evaluate to the
|
||||
same string.
|
||||
|
||||
"a b"
|
||||
a + " " + b
|
||||
"a" + " " + "b"
|
||||
"a\x20b"
|
||||
a + "\x20b"
|
||||
concat(a, " ", "b")
|
||||
"concat"(a, " ", "b")
|
||||
|
||||
As shown in the last example, function names are just strings,
|
||||
too. They must be string *literals*, however. This is not legal:
|
||||
|
||||
("con" + "cat")(a, " ", b) # syntax error!
|
||||
|
||||
|
||||
- The ifelse() builtin takes three arguments: it evaluates exactly
|
||||
one of the second and third, depending on whether the first one is
|
||||
true. There is also some syntactic sugar to make expressions that
|
||||
look like if/else statements:
|
||||
|
||||
# these are all equivalent
|
||||
ifelse(something(), "yes", "no")
|
||||
if something() then yes else no endif
|
||||
if something() then "yes" else "no" endif
|
||||
|
||||
The else part is optional.
|
||||
|
||||
if something() then "yes" endif # if something() is false,
|
||||
# evaluates to false
|
||||
|
||||
ifelse(condition(), "", abort()) # abort() only called if
|
||||
# condition() is false
|
||||
|
||||
The last example is equivalent to:
|
||||
|
||||
assert(condition())
|
||||
|
||||
|
||||
- The && and || operators can be used similarly; they evaluate their
|
||||
second argument only if it's needed to determine the truth of the
|
||||
expression. Their value is the value of the last-evaluated
|
||||
argument:
|
||||
|
||||
file_exists("/data/system/bad") && delete("/data/system/bad")
|
||||
|
||||
file_exists("/data/system/missing") || create("/data/system/missing")
|
||||
|
||||
get_it() || "xxx" # returns value of get_it() if that value is
|
||||
# true, otherwise returns "xxx"
|
||||
|
||||
|
||||
- The purpose of ";" is to simulate imperative statements, of course,
|
||||
but the operator can be used anywhere. Its value is the value of
|
||||
its right side:
|
||||
|
||||
concat(a;b;c, d, e;f) # evaluates to "cdf"
|
||||
|
||||
A more useful example might be something like:
|
||||
|
||||
ifelse(condition(),
|
||||
(first_step(); second_step();), # second ; is optional
|
||||
alternative_procedure())
|
507
edify/expr.c
507
edify/expr.c
@ -1,507 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "expr.h"
|
||||
|
||||
// Functions should:
|
||||
//
|
||||
// - return a malloc()'d string
|
||||
// - if Evaluate() on any argument returns NULL, return NULL.
|
||||
|
||||
int BooleanString(const char* s) {
|
||||
return s[0] != '\0';
|
||||
}
|
||||
|
||||
char* Evaluate(State* state, Expr* expr) {
|
||||
Value* v = expr->fn(expr->name, state, expr->argc, expr->argv);
|
||||
if (v == NULL) return NULL;
|
||||
if (v->type != VAL_STRING) {
|
||||
ErrorAbort(state, "expecting string, got value type %d", v->type);
|
||||
FreeValue(v);
|
||||
return NULL;
|
||||
}
|
||||
char* result = v->data;
|
||||
free(v);
|
||||
return result;
|
||||
}
|
||||
|
||||
Value* EvaluateValue(State* state, Expr* expr) {
|
||||
return expr->fn(expr->name, state, expr->argc, expr->argv);
|
||||
}
|
||||
|
||||
Value* StringValue(char* str) {
|
||||
if (str == NULL) return NULL;
|
||||
Value* v = malloc(sizeof(Value));
|
||||
v->type = VAL_STRING;
|
||||
v->size = strlen(str);
|
||||
v->data = str;
|
||||
return v;
|
||||
}
|
||||
|
||||
void FreeValue(Value* v) {
|
||||
if (v == NULL) return;
|
||||
free(v->data);
|
||||
free(v);
|
||||
}
|
||||
|
||||
Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc == 0) {
|
||||
return StringValue(strdup(""));
|
||||
}
|
||||
char** strings = malloc(argc * sizeof(char*));
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
strings[i] = NULL;
|
||||
}
|
||||
char* result = NULL;
|
||||
int length = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
strings[i] = Evaluate(state, argv[i]);
|
||||
if (strings[i] == NULL) {
|
||||
goto done;
|
||||
}
|
||||
length += strlen(strings[i]);
|
||||
}
|
||||
|
||||
result = malloc(length+1);
|
||||
int p = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
strcpy(result+p, strings[i]);
|
||||
p += strlen(strings[i]);
|
||||
}
|
||||
result[p] = '\0';
|
||||
|
||||
done:
|
||||
for (i = 0; i < argc; ++i) {
|
||||
free(strings[i]);
|
||||
}
|
||||
free(strings);
|
||||
return StringValue(result);
|
||||
}
|
||||
|
||||
Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc != 2 && argc != 3) {
|
||||
free(state->errmsg);
|
||||
state->errmsg = strdup("ifelse expects 2 or 3 arguments");
|
||||
return NULL;
|
||||
}
|
||||
char* cond = Evaluate(state, argv[0]);
|
||||
if (cond == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (BooleanString(cond) == true) {
|
||||
free(cond);
|
||||
return EvaluateValue(state, argv[1]);
|
||||
} else {
|
||||
if (argc == 3) {
|
||||
free(cond);
|
||||
return EvaluateValue(state, argv[2]);
|
||||
} else {
|
||||
return StringValue(cond);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* msg = NULL;
|
||||
if (argc > 0) {
|
||||
msg = Evaluate(state, argv[0]);
|
||||
}
|
||||
free(state->errmsg);
|
||||
if (msg) {
|
||||
state->errmsg = msg;
|
||||
} else {
|
||||
state->errmsg = strdup("called abort()");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
char* v = Evaluate(state, argv[i]);
|
||||
if (v == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
int b = BooleanString(v);
|
||||
free(v);
|
||||
if (!b) {
|
||||
int prefix_len;
|
||||
int len = argv[i]->end - argv[i]->start;
|
||||
char* err_src = malloc(len + 20);
|
||||
strcpy(err_src, "assert failed: ");
|
||||
prefix_len = strlen(err_src);
|
||||
memcpy(err_src + prefix_len, state->script + argv[i]->start, len);
|
||||
err_src[prefix_len + len] = '\0';
|
||||
free(state->errmsg);
|
||||
state->errmsg = err_src;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return StringValue(strdup(""));
|
||||
}
|
||||
|
||||
Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* val = Evaluate(state, argv[0]);
|
||||
if (val == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
int v = strtol(val, NULL, 10);
|
||||
sleep(v);
|
||||
return StringValue(val);
|
||||
}
|
||||
|
||||
Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
char* v = Evaluate(state, argv[i]);
|
||||
if (v == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
fputs(v, stdout);
|
||||
free(v);
|
||||
}
|
||||
return StringValue(strdup(""));
|
||||
}
|
||||
|
||||
Value* LogicalAndFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
char* left = Evaluate(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
if (BooleanString(left) == true) {
|
||||
free(left);
|
||||
return EvaluateValue(state, argv[1]);
|
||||
} else {
|
||||
return StringValue(left);
|
||||
}
|
||||
}
|
||||
|
||||
Value* LogicalOrFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
char* left = Evaluate(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
if (BooleanString(left) == false) {
|
||||
free(left);
|
||||
return EvaluateValue(state, argv[1]);
|
||||
} else {
|
||||
return StringValue(left);
|
||||
}
|
||||
}
|
||||
|
||||
Value* LogicalNotFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
char* val = Evaluate(state, argv[0]);
|
||||
if (val == NULL) return NULL;
|
||||
bool bv = BooleanString(val);
|
||||
free(val);
|
||||
return StringValue(strdup(bv ? "" : "t"));
|
||||
}
|
||||
|
||||
Value* SubstringFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
char* needle = Evaluate(state, argv[0]);
|
||||
if (needle == NULL) return NULL;
|
||||
char* haystack = Evaluate(state, argv[1]);
|
||||
if (haystack == NULL) {
|
||||
free(needle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* result = strdup(strstr(haystack, needle) ? "t" : "");
|
||||
free(needle);
|
||||
free(haystack);
|
||||
return StringValue(result);
|
||||
}
|
||||
|
||||
Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* left = Evaluate(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
char* right = Evaluate(state, argv[1]);
|
||||
if (right == NULL) {
|
||||
free(left);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* result = strdup(strcmp(left, right) == 0 ? "t" : "");
|
||||
free(left);
|
||||
free(right);
|
||||
return StringValue(result);
|
||||
}
|
||||
|
||||
Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* left = Evaluate(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
char* right = Evaluate(state, argv[1]);
|
||||
if (right == NULL) {
|
||||
free(left);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* result = strdup(strcmp(left, right) != 0 ? "t" : "");
|
||||
free(left);
|
||||
free(right);
|
||||
return StringValue(result);
|
||||
}
|
||||
|
||||
Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
Value* left = EvaluateValue(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
FreeValue(left);
|
||||
return EvaluateValue(state, argv[1]);
|
||||
}
|
||||
|
||||
Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc != 2) {
|
||||
free(state->errmsg);
|
||||
state->errmsg = strdup("less_than_int expects 2 arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* left;
|
||||
char* right;
|
||||
if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL;
|
||||
|
||||
bool result = false;
|
||||
char* end;
|
||||
|
||||
long l_int = strtol(left, &end, 10);
|
||||
if (left[0] == '\0' || *end != '\0') {
|
||||
fprintf(stderr, "[%s] is not an int\n", left);
|
||||
goto done;
|
||||
}
|
||||
|
||||
long r_int = strtol(right, &end, 10);
|
||||
if (right[0] == '\0' || *end != '\0') {
|
||||
fprintf(stderr, "[%s] is not an int\n", right);
|
||||
goto done;
|
||||
}
|
||||
|
||||
result = l_int < r_int;
|
||||
|
||||
done:
|
||||
free(left);
|
||||
free(right);
|
||||
return StringValue(strdup(result ? "t" : ""));
|
||||
}
|
||||
|
||||
Value* GreaterThanIntFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
if (argc != 2) {
|
||||
free(state->errmsg);
|
||||
state->errmsg = strdup("greater_than_int expects 2 arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Expr* temp[2];
|
||||
temp[0] = argv[1];
|
||||
temp[1] = argv[0];
|
||||
|
||||
return LessThanIntFn(name, state, 2, temp);
|
||||
}
|
||||
|
||||
Value* Literal(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
return StringValue(strdup(name));
|
||||
}
|
||||
|
||||
Expr* Build(Function fn, YYLTYPE loc, int count, ...) {
|
||||
va_list v;
|
||||
va_start(v, count);
|
||||
Expr* e = malloc(sizeof(Expr));
|
||||
e->fn = fn;
|
||||
e->name = "(operator)";
|
||||
e->argc = count;
|
||||
e->argv = malloc(count * sizeof(Expr*));
|
||||
int i;
|
||||
for (i = 0; i < count; ++i) {
|
||||
e->argv[i] = va_arg(v, Expr*);
|
||||
}
|
||||
va_end(v);
|
||||
e->start = loc.start;
|
||||
e->end = loc.end;
|
||||
return e;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// the function table
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static int fn_entries = 0;
|
||||
static int fn_size = 0;
|
||||
NamedFunction* fn_table = NULL;
|
||||
|
||||
void RegisterFunction(const char* name, Function fn) {
|
||||
if (fn_entries >= fn_size) {
|
||||
fn_size = fn_size*2 + 1;
|
||||
fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction));
|
||||
}
|
||||
fn_table[fn_entries].name = name;
|
||||
fn_table[fn_entries].fn = fn;
|
||||
++fn_entries;
|
||||
}
|
||||
|
||||
static int fn_entry_compare(const void* a, const void* b) {
|
||||
const char* na = ((const NamedFunction*)a)->name;
|
||||
const char* nb = ((const NamedFunction*)b)->name;
|
||||
return strcmp(na, nb);
|
||||
}
|
||||
|
||||
void FinishRegistration() {
|
||||
qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare);
|
||||
}
|
||||
|
||||
Function FindFunction(const char* name) {
|
||||
NamedFunction key;
|
||||
key.name = name;
|
||||
NamedFunction* nf = bsearch(&key, fn_table, fn_entries,
|
||||
sizeof(NamedFunction), fn_entry_compare);
|
||||
if (nf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return nf->fn;
|
||||
}
|
||||
|
||||
void RegisterBuiltins() {
|
||||
RegisterFunction("ifelse", IfElseFn);
|
||||
RegisterFunction("abort", AbortFn);
|
||||
RegisterFunction("assert", AssertFn);
|
||||
RegisterFunction("concat", ConcatFn);
|
||||
RegisterFunction("is_substring", SubstringFn);
|
||||
RegisterFunction("stdout", StdoutFn);
|
||||
RegisterFunction("sleep", SleepFn);
|
||||
|
||||
RegisterFunction("less_than_int", LessThanIntFn);
|
||||
RegisterFunction("greater_than_int", GreaterThanIntFn);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// convenience methods for functions
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Evaluate the expressions in argv, giving 'count' char* (the ... is
|
||||
// zero or more char** to put them in). If any expression evaluates
|
||||
// to NULL, free the rest and return -1. Return 0 on success.
|
||||
int ReadArgs(State* state, Expr* argv[], int count, ...) {
|
||||
char** args = malloc(count * sizeof(char*));
|
||||
va_list v;
|
||||
va_start(v, count);
|
||||
int i;
|
||||
for (i = 0; i < count; ++i) {
|
||||
args[i] = Evaluate(state, argv[i]);
|
||||
if (args[i] == NULL) {
|
||||
va_end(v);
|
||||
int j;
|
||||
for (j = 0; j < i; ++j) {
|
||||
free(args[j]);
|
||||
}
|
||||
free(args);
|
||||
return -1;
|
||||
}
|
||||
*(va_arg(v, char**)) = args[i];
|
||||
}
|
||||
va_end(v);
|
||||
free(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Evaluate the expressions in argv, giving 'count' Value* (the ... is
|
||||
// zero or more Value** to put them in). If any expression evaluates
|
||||
// to NULL, free the rest and return -1. Return 0 on success.
|
||||
int ReadValueArgs(State* state, Expr* argv[], int count, ...) {
|
||||
Value** args = malloc(count * sizeof(Value*));
|
||||
va_list v;
|
||||
va_start(v, count);
|
||||
int i;
|
||||
for (i = 0; i < count; ++i) {
|
||||
args[i] = EvaluateValue(state, argv[i]);
|
||||
if (args[i] == NULL) {
|
||||
va_end(v);
|
||||
int j;
|
||||
for (j = 0; j < i; ++j) {
|
||||
FreeValue(args[j]);
|
||||
}
|
||||
free(args);
|
||||
return -1;
|
||||
}
|
||||
*(va_arg(v, Value**)) = args[i];
|
||||
}
|
||||
va_end(v);
|
||||
free(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Evaluate the expressions in argv, returning an array of char*
|
||||
// results. If any evaluate to NULL, free the rest and return NULL.
|
||||
// The caller is responsible for freeing the returned array and the
|
||||
// strings it contains.
|
||||
char** ReadVarArgs(State* state, int argc, Expr* argv[]) {
|
||||
char** args = (char**)malloc(argc * sizeof(char*));
|
||||
int i = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
args[i] = Evaluate(state, argv[i]);
|
||||
if (args[i] == NULL) {
|
||||
int j;
|
||||
for (j = 0; j < i; ++j) {
|
||||
free(args[j]);
|
||||
}
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// Evaluate the expressions in argv, returning an array of Value*
|
||||
// results. If any evaluate to NULL, free the rest and return NULL.
|
||||
// The caller is responsible for freeing the returned array and the
|
||||
// Values it contains.
|
||||
Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) {
|
||||
Value** args = (Value**)malloc(argc * sizeof(Value*));
|
||||
int i = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
args[i] = EvaluateValue(state, argv[i]);
|
||||
if (args[i] == NULL) {
|
||||
int j;
|
||||
for (j = 0; j < i; ++j) {
|
||||
FreeValue(args[j]);
|
||||
}
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// Use printf-style arguments to compose an error message to put into
|
||||
// *state. Returns NULL.
|
||||
Value* ErrorAbort(State* state, char* format, ...) {
|
||||
char* buffer = malloc(4096);
|
||||
va_list v;
|
||||
va_start(v, format);
|
||||
vsnprintf(buffer, 4096, format, v);
|
||||
va_end(v);
|
||||
free(state->errmsg);
|
||||
state->errmsg = buffer;
|
||||
return NULL;
|
||||
}
|
163
edify/expr.h
163
edify/expr.h
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
#ifndef _EXPRESSION_H
|
||||
#define _EXPRESSION_H
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "yydefs.h"
|
||||
|
||||
#define MAX_STRING_LEN 1024
|
||||
|
||||
typedef struct Expr Expr;
|
||||
|
||||
typedef struct {
|
||||
// Optional pointer to app-specific data; the core of edify never
|
||||
// uses this value.
|
||||
void* cookie;
|
||||
|
||||
// The source of the original script. Must be NULL-terminated,
|
||||
// and in writable memory (Evaluate may make temporary changes to
|
||||
// it but will restore it when done).
|
||||
char* script;
|
||||
|
||||
// The error message (if any) returned if the evaluation aborts.
|
||||
// Should be NULL initially, will be either NULL or a malloc'd
|
||||
// pointer after Evaluate() returns.
|
||||
char* errmsg;
|
||||
} State;
|
||||
|
||||
#define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null
|
||||
#define VAL_BLOB 2
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
ssize_t size;
|
||||
char* data;
|
||||
} Value;
|
||||
|
||||
typedef Value* (*Function)(const char* name, State* state,
|
||||
int argc, Expr* argv[]);
|
||||
|
||||
struct Expr {
|
||||
Function fn;
|
||||
char* name;
|
||||
int argc;
|
||||
Expr** argv;
|
||||
int start, end;
|
||||
};
|
||||
|
||||
// Take one of the Expr*s passed to the function as an argument,
|
||||
// evaluate it, return the resulting Value. The caller takes
|
||||
// ownership of the returned Value.
|
||||
Value* EvaluateValue(State* state, Expr* expr);
|
||||
|
||||
// Take one of the Expr*s passed to the function as an argument,
|
||||
// evaluate it, assert that it is a string, and return the resulting
|
||||
// char*. The caller takes ownership of the returned char*. This is
|
||||
// a convenience function for older functions that want to deal only
|
||||
// with strings.
|
||||
char* Evaluate(State* state, Expr* expr);
|
||||
|
||||
// Glue to make an Expr out of a literal.
|
||||
Value* Literal(const char* name, State* state, int argc, Expr* argv[]);
|
||||
|
||||
// Functions corresponding to various syntactic sugar operators.
|
||||
// ("concat" is also available as a builtin function, to concatenate
|
||||
// more than two strings.)
|
||||
Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
|
||||
// Convenience function for building expressions with a fixed number
|
||||
// of arguments.
|
||||
Expr* Build(Function fn, YYLTYPE loc, int count, ...);
|
||||
|
||||
// Global builtins, registered by RegisterBuiltins().
|
||||
Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
|
||||
|
||||
// For setting and getting the global error string (when returning
|
||||
// NULL from a function).
|
||||
void SetError(const char* message); // makes a copy
|
||||
const char* GetError(); // retains ownership
|
||||
void ClearError();
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
Function fn;
|
||||
} NamedFunction;
|
||||
|
||||
// Register a new function. The same Function may be registered under
|
||||
// multiple names, but a given name should only be used once.
|
||||
void RegisterFunction(const char* name, Function fn);
|
||||
|
||||
// Register all the builtins.
|
||||
void RegisterBuiltins();
|
||||
|
||||
// Call this after all calls to RegisterFunction() but before parsing
|
||||
// any scripts to finish building the function table.
|
||||
void FinishRegistration();
|
||||
|
||||
// Find the Function for a given name; return NULL if no such function
|
||||
// exists.
|
||||
Function FindFunction(const char* name);
|
||||
|
||||
|
||||
// --- convenience functions for use in functions ---
|
||||
|
||||
// Evaluate the expressions in argv, giving 'count' char* (the ... is
|
||||
// zero or more char** to put them in). If any expression evaluates
|
||||
// to NULL, free the rest and return -1. Return 0 on success.
|
||||
int ReadArgs(State* state, Expr* argv[], int count, ...);
|
||||
|
||||
// Evaluate the expressions in argv, giving 'count' Value* (the ... is
|
||||
// zero or more Value** to put them in). If any expression evaluates
|
||||
// to NULL, free the rest and return -1. Return 0 on success.
|
||||
int ReadValueArgs(State* state, Expr* argv[], int count, ...);
|
||||
|
||||
// Evaluate the expressions in argv, returning an array of char*
|
||||
// results. If any evaluate to NULL, free the rest and return NULL.
|
||||
// The caller is responsible for freeing the returned array and the
|
||||
// strings it contains.
|
||||
char** ReadVarArgs(State* state, int argc, Expr* argv[]);
|
||||
|
||||
// Evaluate the expressions in argv, returning an array of Value*
|
||||
// results. If any evaluate to NULL, free the rest and return NULL.
|
||||
// The caller is responsible for freeing the returned array and the
|
||||
// Values it contains.
|
||||
Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]);
|
||||
|
||||
// Use printf-style arguments to compose an error message to put into
|
||||
// *state. Returns NULL.
|
||||
Value* ErrorAbort(State* state, char* format, ...);
|
||||
|
||||
// Wrap a string into a Value, taking ownership of the string.
|
||||
Value* StringValue(char* str);
|
||||
|
||||
// Free a Value object.
|
||||
void FreeValue(Value* v);
|
||||
|
||||
#endif // _EXPRESSION_H
|
112
edify/lexer.l
112
edify/lexer.l
@ -1,112 +0,0 @@
|
||||
%{
|
||||
/*
|
||||
* Copyright (C) 2009 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 <string.h>
|
||||
|
||||
#include "expr.h"
|
||||
#include "yydefs.h"
|
||||
#include "parser.h"
|
||||
|
||||
int gLine = 1;
|
||||
int gColumn = 1;
|
||||
int gPos = 0;
|
||||
|
||||
// TODO: enforce MAX_STRING_LEN during lexing
|
||||
char string_buffer[MAX_STRING_LEN];
|
||||
char* string_pos;
|
||||
|
||||
#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \
|
||||
gColumn+=yyleng; gPos+=yyleng;} while(0)
|
||||
|
||||
%}
|
||||
|
||||
%x STR
|
||||
|
||||
%option noyywrap
|
||||
|
||||
%%
|
||||
|
||||
|
||||
\" {
|
||||
BEGIN(STR);
|
||||
string_pos = string_buffer;
|
||||
yylloc.start = gPos;
|
||||
++gColumn;
|
||||
++gPos;
|
||||
}
|
||||
|
||||
<STR>{
|
||||
\" {
|
||||
++gColumn;
|
||||
++gPos;
|
||||
BEGIN(INITIAL);
|
||||
*string_pos = '\0';
|
||||
yylval.str = strdup(string_buffer);
|
||||
yylloc.end = gPos;
|
||||
return STRING;
|
||||
}
|
||||
|
||||
\\n { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; }
|
||||
\\t { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\t'; }
|
||||
\\\" { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\"'; }
|
||||
\\\\ { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\\'; }
|
||||
|
||||
\\x[0-9a-fA-F]{2} {
|
||||
gColumn += yyleng;
|
||||
gPos += yyleng;
|
||||
int val;
|
||||
sscanf(yytext+2, "%x", &val);
|
||||
*string_pos++ = val;
|
||||
}
|
||||
|
||||
\n {
|
||||
++gLine;
|
||||
++gPos;
|
||||
gColumn = 1;
|
||||
*string_pos++ = yytext[0];
|
||||
}
|
||||
|
||||
. {
|
||||
++gColumn;
|
||||
++gPos;
|
||||
*string_pos++ = yytext[0];
|
||||
}
|
||||
}
|
||||
|
||||
if ADVANCE; return IF;
|
||||
then ADVANCE; return THEN;
|
||||
else ADVANCE; return ELSE;
|
||||
endif ADVANCE; return ENDIF;
|
||||
|
||||
[a-zA-Z0-9_:/.]+ {
|
||||
ADVANCE;
|
||||
yylval.str = strdup(yytext);
|
||||
return STRING;
|
||||
}
|
||||
|
||||
\&\& ADVANCE; return AND;
|
||||
\|\| ADVANCE; return OR;
|
||||
== ADVANCE; return EQ;
|
||||
!= ADVANCE; return NE;
|
||||
|
||||
[+(),!;] ADVANCE; return yytext[0];
|
||||
|
||||
[ \t]+ ADVANCE;
|
||||
|
||||
(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1;
|
||||
|
||||
. return BAD;
|
218
edify/main.c
218
edify/main.c
@ -1,218 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "expr.h"
|
||||
#include "parser.h"
|
||||
|
||||
extern int yyparse(Expr** root, int* error_count);
|
||||
|
||||
int expect(const char* expr_str, const char* expected, int* errors) {
|
||||
Expr* e;
|
||||
int error;
|
||||
char* result;
|
||||
|
||||
printf(".");
|
||||
|
||||
yy_scan_string(expr_str);
|
||||
int error_count = 0;
|
||||
error = yyparse(&e, &error_count);
|
||||
if (error > 0 || error_count > 0) {
|
||||
fprintf(stderr, "error parsing \"%s\" (%d errors)\n",
|
||||
expr_str, error_count);
|
||||
++*errors;
|
||||
return 0;
|
||||
}
|
||||
|
||||
State state;
|
||||
state.cookie = NULL;
|
||||
state.script = strdup(expr_str);
|
||||
state.errmsg = NULL;
|
||||
|
||||
result = Evaluate(&state, e);
|
||||
free(state.errmsg);
|
||||
free(state.script);
|
||||
if (result == NULL && expected != NULL) {
|
||||
fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
|
||||
++*errors;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (result == NULL && expected == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(result, expected) != 0) {
|
||||
fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
|
||||
expr_str, expected, result);
|
||||
++*errors;
|
||||
free(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int test() {
|
||||
int errors = 0;
|
||||
|
||||
expect("a", "a", &errors);
|
||||
expect("\"a\"", "a", &errors);
|
||||
expect("\"\\x61\"", "a", &errors);
|
||||
expect("# this is a comment\n"
|
||||
" a\n"
|
||||
" \n",
|
||||
"a", &errors);
|
||||
|
||||
|
||||
// sequence operator
|
||||
expect("a; b; c", "c", &errors);
|
||||
|
||||
// string concat operator
|
||||
expect("a + b", "ab", &errors);
|
||||
expect("a + \n \"b\"", "ab", &errors);
|
||||
expect("a + b +\nc\n", "abc", &errors);
|
||||
|
||||
// string concat function
|
||||
expect("concat(a, b)", "ab", &errors);
|
||||
expect("concat(a,\n \"b\")", "ab", &errors);
|
||||
expect("concat(a + b,\nc,\"d\")", "abcd", &errors);
|
||||
expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors);
|
||||
|
||||
// logical and
|
||||
expect("a && b", "b", &errors);
|
||||
expect("a && \"\"", "", &errors);
|
||||
expect("\"\" && b", "", &errors);
|
||||
expect("\"\" && \"\"", "", &errors);
|
||||
expect("\"\" && abort()", "", &errors); // test short-circuiting
|
||||
expect("t && abort()", NULL, &errors);
|
||||
|
||||
// logical or
|
||||
expect("a || b", "a", &errors);
|
||||
expect("a || \"\"", "a", &errors);
|
||||
expect("\"\" || b", "b", &errors);
|
||||
expect("\"\" || \"\"", "", &errors);
|
||||
expect("a || abort()", "a", &errors); // test short-circuiting
|
||||
expect("\"\" || abort()", NULL, &errors);
|
||||
|
||||
// logical not
|
||||
expect("!a", "", &errors);
|
||||
expect("! \"\"", "t", &errors);
|
||||
expect("!!a", "t", &errors);
|
||||
|
||||
// precedence
|
||||
expect("\"\" == \"\" && b", "b", &errors);
|
||||
expect("a + b == ab", "t", &errors);
|
||||
expect("ab == a + b", "t", &errors);
|
||||
expect("a + (b == ab)", "a", &errors);
|
||||
expect("(ab == a) + b", "b", &errors);
|
||||
|
||||
// substring function
|
||||
expect("is_substring(cad, abracadabra)", "t", &errors);
|
||||
expect("is_substring(abrac, abracadabra)", "t", &errors);
|
||||
expect("is_substring(dabra, abracadabra)", "t", &errors);
|
||||
expect("is_substring(cad, abracxadabra)", "", &errors);
|
||||
expect("is_substring(abrac, axbracadabra)", "", &errors);
|
||||
expect("is_substring(dabra, abracadabrxa)", "", &errors);
|
||||
|
||||
// ifelse function
|
||||
expect("ifelse(t, yes, no)", "yes", &errors);
|
||||
expect("ifelse(!t, yes, no)", "no", &errors);
|
||||
expect("ifelse(t, yes, abort())", "yes", &errors);
|
||||
expect("ifelse(!t, abort(), no)", "no", &errors);
|
||||
|
||||
// if "statements"
|
||||
expect("if t then yes else no endif", "yes", &errors);
|
||||
expect("if \"\" then yes else no endif", "no", &errors);
|
||||
expect("if \"\" then yes endif", "", &errors);
|
||||
expect("if \"\"; t then yes endif", "yes", &errors);
|
||||
|
||||
// numeric comparisons
|
||||
expect("less_than_int(3, 14)", "t", &errors);
|
||||
expect("less_than_int(14, 3)", "", &errors);
|
||||
expect("less_than_int(x, 3)", "", &errors);
|
||||
expect("less_than_int(3, x)", "", &errors);
|
||||
expect("greater_than_int(3, 14)", "", &errors);
|
||||
expect("greater_than_int(14, 3)", "t", &errors);
|
||||
expect("greater_than_int(x, 3)", "", &errors);
|
||||
expect("greater_than_int(3, x)", "", &errors);
|
||||
|
||||
printf("\n");
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
void ExprDump(int depth, Expr* n, char* script) {
|
||||
printf("%*s", depth*2, "");
|
||||
char temp = script[n->end];
|
||||
script[n->end] = '\0';
|
||||
printf("%s %p (%d-%d) \"%s\"\n",
|
||||
n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end,
|
||||
script+n->start);
|
||||
script[n->end] = temp;
|
||||
int i;
|
||||
for (i = 0; i < n->argc; ++i) {
|
||||
ExprDump(depth+1, n->argv[i], script);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
RegisterBuiltins();
|
||||
FinishRegistration();
|
||||
|
||||
if (argc == 1) {
|
||||
return test() != 0;
|
||||
}
|
||||
|
||||
FILE* f = fopen(argv[1], "r");
|
||||
if (f == NULL) {
|
||||
printf("%s: %s: No such file or directory\n", argv[0], argv[1]);
|
||||
return 1;
|
||||
}
|
||||
char buffer[8192];
|
||||
int size = fread(buffer, 1, 8191, f);
|
||||
fclose(f);
|
||||
buffer[size] = '\0';
|
||||
|
||||
Expr* root;
|
||||
int error_count = 0;
|
||||
yy_scan_bytes(buffer, size);
|
||||
int error = yyparse(&root, &error_count);
|
||||
printf("parse returned %d; %d errors encountered\n", error, error_count);
|
||||
if (error == 0 || error_count > 0) {
|
||||
|
||||
ExprDump(0, root, buffer);
|
||||
|
||||
State state;
|
||||
state.cookie = NULL;
|
||||
state.script = buffer;
|
||||
state.errmsg = NULL;
|
||||
|
||||
char* result = Evaluate(&state, root);
|
||||
if (result == NULL) {
|
||||
printf("result was NULL, message is: %s\n",
|
||||
(state.errmsg == NULL ? "(NULL)" : state.errmsg));
|
||||
free(state.errmsg);
|
||||
} else {
|
||||
printf("result is [%s]\n", result);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user