Merge korg/donut into korg/master
28
Android.mk
@ -22,6 +22,9 @@ LOCAL_MODULE := recovery
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
|
||||
RECOVERY_API_VERSION := 2
|
||||
LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
|
||||
|
||||
# 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.
|
||||
@ -30,31 +33,20 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libminzip libunz libamend libmtdutils libmincrypt
|
||||
LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libcutils
|
||||
LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils
|
||||
LOCAL_STATIC_LIBRARIES += libstdc++ libc
|
||||
|
||||
# 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)
|
||||
|
||||
# Depend on the generated keys.inc containing the OTA public keys.
|
||||
$(intermediates)/install.o: $(RECOVERY_INSTALL_OTA_KEYS_INC)
|
||||
|
||||
include $(commands_recovery_local_path)/minui/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)/tools/Android.mk
|
||||
include $(commands_recovery_local_path)/edify/Android.mk
|
||||
include $(commands_recovery_local_path)/updater/Android.mk
|
||||
commands_recovery_local_path :=
|
||||
|
||||
endif # TARGET_ARCH == arm
|
||||
endif # !TARGET_SIMULATOR
|
||||
|
||||
|
@ -10,13 +10,11 @@ amend_src_files := \
|
||||
ast.c \
|
||||
symtab.c \
|
||||
commands.c \
|
||||
permissions.c \
|
||||
execute.c
|
||||
|
||||
amend_test_files := \
|
||||
test_symtab.c \
|
||||
test_commands.c \
|
||||
test_permissions.c
|
||||
test_commands.c
|
||||
|
||||
# "-x c" forces the lex/yacc files to be compiled as c;
|
||||
# the build system otherwise forces them to be c++.
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "amend.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
|
||||
extern const AmCommandList *gCommands;
|
||||
|
||||
|
@ -152,37 +152,30 @@ getCommandArgumentType(Command *cmd)
|
||||
}
|
||||
|
||||
static int
|
||||
callCommandInternal(CommandEntry *entry, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
callCommandInternal(CommandEntry *entry, int argc, const char *argv[])
|
||||
{
|
||||
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;
|
||||
}
|
||||
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.
|
||||
return entry->hook(entry->name, entry->cookie, argc, argv);
|
||||
}
|
||||
bail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
callBooleanCommandInternal(CommandEntry *entry, bool arg,
|
||||
PermissionRequestList *permissions)
|
||||
callBooleanCommandInternal(CommandEntry *entry, bool arg)
|
||||
{
|
||||
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 entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -190,63 +183,37 @@ callBooleanCommandInternal(CommandEntry *entry, bool arg,
|
||||
int
|
||||
callCommand(Command *cmd, int argc, const char *argv[])
|
||||
{
|
||||
return callCommandInternal((CommandEntry *)cmd, argc, argv, NULL);
|
||||
return callCommandInternal((CommandEntry *)cmd, argc, argv);
|
||||
}
|
||||
|
||||
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;
|
||||
return callBooleanCommandInternal((CommandEntry *)cmd, arg);
|
||||
}
|
||||
|
||||
int
|
||||
callFunctionInternal(CommandEntry *entry, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen, PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
|
||||
(argc == 0 || (argc > 0 && argv != NULL)))
|
||||
{
|
||||
if ((permissions == NULL && result != NULL) ||
|
||||
(permissions != NULL && result == NULL))
|
||||
if (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;
|
||||
}
|
||||
/* 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.
|
||||
argc, argv, result, resultLen);
|
||||
}
|
||||
}
|
||||
bail:
|
||||
@ -258,16 +225,5 @@ 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;
|
||||
result, resultLen);
|
||||
}
|
||||
|
@ -14,31 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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.
|
||||
/* Invoke a command.
|
||||
*
|
||||
* 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 argc, const char *argv[]);
|
||||
|
||||
int commandInit(void);
|
||||
void commandCleanup(void);
|
||||
@ -66,19 +53,13 @@ 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);
|
||||
char **result, size_t *resultLen);
|
||||
|
||||
struct Function;
|
||||
typedef struct Function Function;
|
||||
@ -90,7 +71,4 @@ 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_
|
||||
|
@ -86,12 +86,6 @@ main(int argc, char *argv[])
|
||||
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--;
|
||||
|
@ -1,270 +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.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
@ -1,111 +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.
|
||||
*/
|
||||
|
||||
#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_
|
@ -39,40 +39,15 @@
|
||||
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; \
|
||||
} \
|
||||
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
|
||||
@ -81,13 +56,11 @@
|
||||
/* assert <boolexpr>
|
||||
*/
|
||||
static int
|
||||
cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_assert(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
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)
|
||||
@ -102,8 +75,7 @@ cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* format <root>
|
||||
*/
|
||||
static int
|
||||
cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_format(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -115,8 +87,7 @@ cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* copy_dir <srcdir> <dstdir>
|
||||
*/
|
||||
static int
|
||||
cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -128,8 +99,7 @@ cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* mark <resource> dirty|clean
|
||||
*/
|
||||
static int
|
||||
cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_mark(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -144,8 +114,7 @@ cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* done
|
||||
*/
|
||||
static int
|
||||
cmd_done(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_done(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -174,11 +143,6 @@ registerUpdateCommands()
|
||||
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;
|
||||
}
|
||||
|
||||
@ -194,13 +158,11 @@ registerUpdateCommands()
|
||||
*/
|
||||
static int
|
||||
fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 0) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
@ -228,13 +190,11 @@ fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
@ -255,8 +215,7 @@ fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
@ -273,23 +232,12 @@ fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
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;
|
||||
*result = strdup("hashvalue");
|
||||
if (resultLen != NULL) {
|
||||
*resultLen = strlen(*result);
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -302,13 +250,11 @@ fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
|
||||
@ -339,13 +285,11 @@ fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_concat(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
size_t totalLen = 0;
|
||||
int i;
|
||||
|
@ -27,34 +27,30 @@ static struct {
|
||||
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)
|
||||
testCommand(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
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)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
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) {
|
||||
@ -187,7 +183,6 @@ test_commands()
|
||||
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);
|
||||
@ -196,7 +191,6 @@ test_commands()
|
||||
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.
|
||||
*/
|
||||
@ -206,7 +200,6 @@ test_commands()
|
||||
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);
|
||||
@ -214,12 +207,10 @@ test_commands()
|
||||
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);
|
||||
@ -227,45 +218,6 @@ test_commands()
|
||||
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().
|
||||
*/
|
||||
@ -365,7 +317,6 @@ test_functions()
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 25;
|
||||
gTestCommandState.functionResult = "1234";
|
||||
gTestCommandState.permissions = (PermissionRequestList *)1;
|
||||
functionResult = NULL;
|
||||
functionResultLen = 55;
|
||||
ret = callFunction(fn, argc, argv,
|
||||
@ -378,29 +329,9 @@ test_functions()
|
||||
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();
|
||||
@ -470,7 +401,6 @@ test_interaction()
|
||||
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);
|
||||
@ -478,7 +408,6 @@ test_interaction()
|
||||
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.
|
||||
*/
|
||||
@ -490,7 +419,6 @@ test_interaction()
|
||||
gTestCommandState.called = false;
|
||||
gTestCommandState.returnValue = 125;
|
||||
gTestCommandState.functionResult = "5678";
|
||||
gTestCommandState.permissions = (PermissionRequestList *)2;
|
||||
functionResult = NULL;
|
||||
functionResultLen = 66;
|
||||
ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
|
||||
@ -500,7 +428,6 @@ test_interaction()
|
||||
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));
|
||||
|
||||
|
@ -1,347 +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.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
117
commands.c
@ -57,39 +57,13 @@ static int gDidShowProgress = 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_; \
|
||||
} \
|
||||
} \
|
||||
assert(result != NULL); \
|
||||
if (result == NULL) return -1; \
|
||||
} while (false)
|
||||
|
||||
/*
|
||||
@ -99,13 +73,11 @@ static int gDidShowProgress = 0;
|
||||
/* assert <boolexpr>
|
||||
*/
|
||||
static int
|
||||
cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_assert(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
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)
|
||||
@ -120,8 +92,7 @@ cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* format <root>
|
||||
*/
|
||||
static int
|
||||
cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_format(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -151,8 +122,7 @@ cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
||||
* give up early.
|
||||
*/
|
||||
static int
|
||||
cmd_delete(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_delete(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -166,7 +136,6 @@ cmd_delete(const char *name, void *cookie, int argc, const char *argv[],
|
||||
|
||||
recurse = (strcmp(name, "delete_recursive") == 0);
|
||||
ui_print("Deleting files...\n");
|
||||
//xxx permissions
|
||||
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
@ -233,13 +202,11 @@ static void extract_cb(const char *fn, void *cookie)
|
||||
* or a fixed default timestamp will be supplied otherwise.
|
||||
*/
|
||||
static int
|
||||
cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx permissions
|
||||
|
||||
// To create a consistent system image, never use the clock for timestamps.
|
||||
struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default
|
||||
@ -331,8 +298,7 @@ cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
* Run an external program included in the update package.
|
||||
*/
|
||||
static int
|
||||
cmd_run_program(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_run_program(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -407,8 +373,7 @@ cmd_run_program(const char *name, void *cookie, int argc, const char *argv[],
|
||||
* User, group, and modes must all be integer values (hex or octal OK).
|
||||
*/
|
||||
static int
|
||||
cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -461,8 +426,7 @@ cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[],
|
||||
* if the actual rate of progress can be determined).
|
||||
*/
|
||||
static int
|
||||
cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -499,8 +463,7 @@ cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[],
|
||||
* for the target filesystem (and may be relative).
|
||||
*/
|
||||
static int
|
||||
cmd_symlink(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_symlink(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -554,7 +517,7 @@ static bool firmware_fn(const unsigned char *data, int data_len, void *cookie)
|
||||
*/
|
||||
static int
|
||||
cmd_write_firmware_image(const char *name, void *cookie,
|
||||
int argc, const char *argv[], PermissionRequestList *permissions)
|
||||
int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
@ -634,11 +597,10 @@ static bool write_raw_image_process_fn(
|
||||
*/
|
||||
static int
|
||||
cmd_write_raw_image(const char *name, void *cookie,
|
||||
int argc, const char *argv[], PermissionRequestList *permissions)
|
||||
int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_WORDS();
|
||||
//xxx permissions
|
||||
|
||||
if (argc != 2) {
|
||||
LOGE("Command %s requires exactly two arguments\n", name);
|
||||
@ -726,8 +688,7 @@ cmd_write_raw_image(const char *name, void *cookie,
|
||||
/* mark <resource> dirty|clean
|
||||
*/
|
||||
static int
|
||||
cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_mark(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -742,8 +703,7 @@ cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
/* done
|
||||
*/
|
||||
static int
|
||||
cmd_done(const char *name, void *cookie, int argc, const char *argv[],
|
||||
PermissionRequestList *permissions)
|
||||
cmd_done(const char *name, void *cookie, int argc, const char *argv[])
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
@ -764,13 +724,11 @@ cmd_done(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
@ -796,13 +754,11 @@ fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 0) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
@ -830,13 +786,11 @@ fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
||||
@ -857,8 +811,7 @@ fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
@ -875,24 +828,6 @@ fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
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;
|
||||
}
|
||||
|
||||
@ -904,13 +839,11 @@ fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
|
||||
@ -941,13 +874,11 @@ fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_concat(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(name);
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
size_t totalLen = 0;
|
||||
int i;
|
||||
@ -977,12 +908,10 @@ fn_concat(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_getprop(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 1) {
|
||||
LOGE("Command %s requires exactly one argument\n", name);
|
||||
@ -1005,12 +934,10 @@ fn_getprop(const char *name, void *cookie, int argc, const char *argv[],
|
||||
*/
|
||||
static int
|
||||
fn_file_contains(const char *name, void *cookie, int argc, const char *argv[],
|
||||
char **result, size_t *resultLen,
|
||||
PermissionRequestList *permissions)
|
||||
char **result, size_t *resultLen)
|
||||
{
|
||||
UNUSED(cookie);
|
||||
CHECK_FN();
|
||||
NO_PERMS(permissions);
|
||||
|
||||
if (argc != 2) {
|
||||
LOGE("Command %s requires exactly two arguments\n", name);
|
||||
|
4
common.h
@ -47,7 +47,6 @@ void ui_end_menu();
|
||||
// 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_FIRMWARE_INSTALLING,
|
||||
@ -91,4 +90,7 @@ void ui_reset_progress();
|
||||
#define LOGD(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define EXPAND(x) STRINGIFY(x)
|
||||
|
||||
#endif // RECOVERY_COMMON_H
|
||||
|
39
edify/Android.mk
Normal file
@ -0,0 +1,39 @@
|
||||
# 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
Normal file
@ -0,0 +1,108 @@
|
||||
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())
|
432
edify/expr.c
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
* 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) {
|
||||
return expr->fn(expr->name, state, expr->argc, expr->argv);
|
||||
}
|
||||
|
||||
char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc == 0) {
|
||||
return 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]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
char* 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 Evaluate(state, argv[1]);
|
||||
} else {
|
||||
if (argc == 3) {
|
||||
free(cond);
|
||||
return Evaluate(state, argv[2]);
|
||||
} else {
|
||||
return cond;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* 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;
|
||||
}
|
||||
|
||||
char* 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 strdup("");
|
||||
}
|
||||
|
||||
char* 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 val;
|
||||
}
|
||||
|
||||
char* 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 strdup("");
|
||||
}
|
||||
|
||||
char* 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 Evaluate(state, argv[1]);
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
char* 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 Evaluate(state, argv[1]);
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
char* 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);
|
||||
if (bv) {
|
||||
return strdup("");
|
||||
} else {
|
||||
return strdup("t");
|
||||
}
|
||||
}
|
||||
|
||||
char* 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 result;
|
||||
}
|
||||
|
||||
char* 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 result;
|
||||
}
|
||||
|
||||
char* 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 result;
|
||||
}
|
||||
|
||||
char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* left = Evaluate(state, argv[0]);
|
||||
if (left == NULL) return NULL;
|
||||
free(left);
|
||||
return Evaluate(state, argv[1]);
|
||||
}
|
||||
|
||||
char* 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 strdup(result ? "t" : "");
|
||||
}
|
||||
|
||||
char* 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);
|
||||
}
|
||||
|
||||
char* Literal(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
return 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]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
*(va_arg(v, char**)) = args[i];
|
||||
}
|
||||
va_end(v);
|
||||
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;
|
||||
}
|
||||
|
||||
// Use printf-style arguments to compose an error message to put into
|
||||
// *state. Returns NULL.
|
||||
char* 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;
|
||||
}
|
126
edify/expr.h
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 "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;
|
||||
|
||||
typedef char* (*Function)(const char* name, State* state,
|
||||
int argc, Expr* argv[]);
|
||||
|
||||
struct Expr {
|
||||
Function fn;
|
||||
char* name;
|
||||
int argc;
|
||||
Expr** argv;
|
||||
int start, end;
|
||||
};
|
||||
|
||||
char* Evaluate(State* state, Expr* expr);
|
||||
|
||||
// Glue to make an Expr out of a literal.
|
||||
char* 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.)
|
||||
char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* SubstringFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* 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().
|
||||
char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* AssertFn(const char* name, State* state, int argc, Expr* argv[]);
|
||||
char* 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, 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[]);
|
||||
|
||||
// Use printf-style arguments to compose an error message to put into
|
||||
// *state. Returns NULL.
|
||||
char* ErrorAbort(State* state, char* format, ...);
|
||||
|
||||
|
||||
#endif // _EXPRESSION_H
|
110
edify/lexer.l
Normal file
@ -0,0 +1,110 @@
|
||||
%{
|
||||
/*
|
||||
* 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 "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;
|
213
edify/main.c
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 = expr_str;
|
||||
state.errmsg = NULL;
|
||||
|
||||
result = Evaluate(&state, e);
|
||||
free(state.errmsg);
|
||||
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");
|
||||
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;
|
||||
}
|
130
edify/parser.y
Normal file
@ -0,0 +1,130 @@
|
||||
%{
|
||||
/*
|
||||
* 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 "yydefs.h"
|
||||
#include "parser.h"
|
||||
|
||||
extern int gLine;
|
||||
extern int gColumn;
|
||||
|
||||
void yyerror(Expr** root, int* error_count, const char* s);
|
||||
int yyparse(Expr** root, int* error_count);
|
||||
|
||||
%}
|
||||
|
||||
%locations
|
||||
|
||||
%union {
|
||||
char* str;
|
||||
Expr* expr;
|
||||
struct {
|
||||
int argc;
|
||||
Expr** argv;
|
||||
} args;
|
||||
}
|
||||
|
||||
%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
|
||||
%token <str> STRING BAD
|
||||
%type <expr> expr
|
||||
%type <args> arglist
|
||||
|
||||
%parse-param {Expr** root}
|
||||
%parse-param {int* error_count}
|
||||
%error-verbose
|
||||
|
||||
/* declarations in increasing order of precedence */
|
||||
%left ';'
|
||||
%left ','
|
||||
%left OR
|
||||
%left AND
|
||||
%left EQ NE
|
||||
%left '+'
|
||||
%right '!'
|
||||
|
||||
%%
|
||||
|
||||
input: expr { *root = $1; }
|
||||
;
|
||||
|
||||
expr: STRING {
|
||||
$$ = malloc(sizeof(Expr));
|
||||
$$->fn = Literal;
|
||||
$$->name = $1;
|
||||
$$->argc = 0;
|
||||
$$->argv = NULL;
|
||||
$$->start = @$.start;
|
||||
$$->end = @$.end;
|
||||
}
|
||||
| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
|
||||
| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
|
||||
| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); }
|
||||
| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; }
|
||||
| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); }
|
||||
| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); }
|
||||
| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); }
|
||||
| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); }
|
||||
| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); }
|
||||
| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); }
|
||||
| IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); }
|
||||
| IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
|
||||
| STRING '(' arglist ')' {
|
||||
$$ = malloc(sizeof(Expr));
|
||||
$$->fn = FindFunction($1);
|
||||
if ($$->fn == NULL) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
|
||||
yyerror(root, error_count, buffer);
|
||||
YYERROR;
|
||||
}
|
||||
$$->name = $1;
|
||||
$$->argc = $3.argc;
|
||||
$$->argv = $3.argv;
|
||||
$$->start = @$.start;
|
||||
$$->end = @$.end;
|
||||
}
|
||||
;
|
||||
|
||||
arglist: /* empty */ {
|
||||
$$.argc = 0;
|
||||
$$.argv = NULL;
|
||||
}
|
||||
| expr {
|
||||
$$.argc = 1;
|
||||
$$.argv = malloc(sizeof(Expr*));
|
||||
$$.argv[0] = $1;
|
||||
}
|
||||
| arglist ',' expr {
|
||||
$$.argc = $1.argc + 1;
|
||||
$$.argv = realloc($$.argv, $$.argc * sizeof(Expr*));
|
||||
$$.argv[$$.argc-1] = $3;
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
void yyerror(Expr** root, int* error_count, const char* s) {
|
||||
if (strlen(s) == 0) {
|
||||
s = "syntax error";
|
||||
}
|
||||
printf("line %d col %d: %s\n", gLine, gColumn, s);
|
||||
++*error_count;
|
||||
}
|
36
edify/yydefs.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 _YYDEFS_H_
|
||||
#define _YYDEFS_H_
|
||||
|
||||
#define YYLTYPE YYLTYPE
|
||||
typedef struct {
|
||||
int start, end;
|
||||
} YYLTYPE;
|
||||
|
||||
#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)
|
||||
|
||||
#endif
|
@ -39,6 +39,10 @@ int remember_firmware_update(const char *type, const char *data, int length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return true if there is a firmware update pending.
|
||||
int firmware_update_pending() {
|
||||
return update_data != NULL && update_length > 0;
|
||||
}
|
||||
|
||||
/* Bootloader / Recovery Flow
|
||||
*
|
||||
|
@ -23,6 +23,9 @@
|
||||
*/
|
||||
int remember_firmware_update(const char *type, const char *data, int length);
|
||||
|
||||
/* Returns true if a firmware update has been saved. */
|
||||
int firmware_update_pending();
|
||||
|
||||
/* If an update was saved, reboot into the bootloader now to install it.
|
||||
* Returns 0 if no radio image was defined, nonzero on error,
|
||||
* doesn't return at all on success...
|
||||
|
313
install.c
@ -14,10 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "amend/amend.h"
|
||||
#include "common.h"
|
||||
@ -30,13 +33,11 @@
|
||||
#include "mtdutils/mtdutils.h"
|
||||
#include "roots.h"
|
||||
#include "verifier.h"
|
||||
|
||||
/* List of public keys */
|
||||
static const RSAPublicKey keys[] = {
|
||||
#include "keys.inc"
|
||||
};
|
||||
#include "firmware.h"
|
||||
|
||||
#define ASSUMED_UPDATE_SCRIPT_NAME "META-INF/com/google/android/update-script"
|
||||
#define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary"
|
||||
#define PUBLIC_KEYS_FILE "/res/keys"
|
||||
|
||||
static const ZipEntry *
|
||||
find_update_script(ZipArchive *zip)
|
||||
@ -99,7 +100,7 @@ handle_update_script(ZipArchive *zip, const ZipEntry *update_script_entry)
|
||||
int ret = execCommandList((ExecContext *)1, commands);
|
||||
if (ret != 0) {
|
||||
int num = ret;
|
||||
char *line, *next = script_data;
|
||||
char *line = NULL, *next = script_data;
|
||||
while (next != NULL && ret-- > 0) {
|
||||
line = next;
|
||||
next = memchr(line, '\n', script_data + script_len - line);
|
||||
@ -109,12 +110,211 @@ handle_update_script(ZipArchive *zip, const ZipEntry *update_script_entry)
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
|
||||
ui_print("Installation complete.\n");
|
||||
LOGI("Installation complete.\n");
|
||||
return INSTALL_SUCCESS;
|
||||
}
|
||||
|
||||
// The update binary ask us to install a firmware file on reboot. Set
|
||||
// that up. Takes ownership of type and filename.
|
||||
static int
|
||||
handle_update_package(const char *path, ZipArchive *zip)
|
||||
handle_firmware_update(char* type, char* filename, ZipArchive* zip) {
|
||||
unsigned int data_size;
|
||||
const ZipEntry* entry = NULL;
|
||||
|
||||
if (strncmp(filename, "PACKAGE:", 8) == 0) {
|
||||
entry = mzFindZipEntry(zip, filename+8);
|
||||
if (entry == NULL) {
|
||||
LOGE("Failed to find \"%s\" in package", filename+8);
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
data_size = entry->uncompLen;
|
||||
} else {
|
||||
struct stat st_data;
|
||||
if (stat(filename, &st_data) < 0) {
|
||||
LOGE("Error stat'ing %s: %s\n", filename, strerror(errno));
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
data_size = st_data.st_size;
|
||||
}
|
||||
|
||||
LOGI("type is %s; size is %d; file is %s\n",
|
||||
type, data_size, filename);
|
||||
|
||||
char* data = malloc(data_size);
|
||||
if (data == NULL) {
|
||||
LOGI("Can't allocate %d bytes for firmware data\n", data_size);
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
if (mzReadZipEntry(zip, entry, data, data_size) == false) {
|
||||
LOGE("Failed to read \"%s\" from package", filename+8);
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
} else {
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
LOGE("Failed to open %s: %s\n", filename, strerror(errno));
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
if (fread(data, 1, data_size, f) != data_size) {
|
||||
LOGE("Failed to read firmware data: %s\n", strerror(errno));
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (remember_firmware_update(type, data, data_size)) {
|
||||
LOGE("Can't store %s image\n", type);
|
||||
free(data);
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
free(filename);
|
||||
|
||||
return INSTALL_SUCCESS;
|
||||
}
|
||||
|
||||
// If the package contains an update binary, extract it and run it.
|
||||
static int
|
||||
try_update_binary(const char *path, ZipArchive *zip) {
|
||||
const ZipEntry* binary_entry =
|
||||
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
|
||||
if (binary_entry == NULL) {
|
||||
return INSTALL_CORRUPT;
|
||||
}
|
||||
|
||||
char* binary = "/tmp/update_binary";
|
||||
unlink(binary);
|
||||
int fd = creat(binary, 0755);
|
||||
if (fd < 0) {
|
||||
LOGE("Can't make %s\n", binary);
|
||||
return 1;
|
||||
}
|
||||
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
|
||||
close(fd);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pipefd[2];
|
||||
pipe(pipefd);
|
||||
|
||||
// When executing the update binary contained in the package, the
|
||||
// arguments passed are:
|
||||
//
|
||||
// - the version number for this interface
|
||||
//
|
||||
// - an fd to which the program can write in order to update the
|
||||
// progress bar. The program can write single-line commands:
|
||||
//
|
||||
// progress <frac> <secs>
|
||||
// fill up the next <frac> part of of the progress bar
|
||||
// over <secs> seconds. If <secs> is zero, use
|
||||
// set_progress commands to manually control the
|
||||
// progress of this segment of the bar
|
||||
//
|
||||
// set_progress <frac>
|
||||
// <frac> should be between 0.0 and 1.0; sets the
|
||||
// progress bar within the segment defined by the most
|
||||
// recent progress command.
|
||||
//
|
||||
// firmware <"hboot"|"radio"> <filename>
|
||||
// arrange to install the contents of <filename> in the
|
||||
// given partition on reboot. (API v2: <filename> may
|
||||
// start with "PACKAGE:" to indicate taking a file from
|
||||
// the OTA package.)
|
||||
//
|
||||
// ui_print <string>
|
||||
// display <string> on the screen.
|
||||
//
|
||||
// - the name of the package zip file.
|
||||
//
|
||||
|
||||
char** args = malloc(sizeof(char*) * 5);
|
||||
args[0] = binary;
|
||||
args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
|
||||
args[2] = malloc(10);
|
||||
sprintf(args[2], "%d", pipefd[1]);
|
||||
args[3] = (char*)path;
|
||||
args[4] = NULL;
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(pipefd[0]);
|
||||
execv(binary, args);
|
||||
fprintf(stderr, "E:Can't run %s (%s)\n", binary, strerror(errno));
|
||||
_exit(-1);
|
||||
}
|
||||
close(pipefd[1]);
|
||||
|
||||
char* firmware_type = NULL;
|
||||
char* firmware_filename = NULL;
|
||||
|
||||
char buffer[81];
|
||||
FILE* from_child = fdopen(pipefd[0], "r");
|
||||
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
|
||||
LOGI("read: %s", buffer);
|
||||
|
||||
char* command = strtok(buffer, " \n");
|
||||
if (command == NULL) {
|
||||
continue;
|
||||
} else if (strcmp(command, "progress") == 0) {
|
||||
char* fraction_s = strtok(NULL, " \n");
|
||||
char* seconds_s = strtok(NULL, " \n");
|
||||
|
||||
float fraction = strtof(fraction_s, NULL);
|
||||
int seconds = strtol(seconds_s, NULL, 10);
|
||||
|
||||
ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
|
||||
seconds);
|
||||
} else if (strcmp(command, "set_progress") == 0) {
|
||||
char* fraction_s = strtok(NULL, " \n");
|
||||
float fraction = strtof(fraction_s, NULL);
|
||||
ui_set_progress(fraction);
|
||||
} else if (strcmp(command, "firmware") == 0) {
|
||||
char* type = strtok(NULL, " \n");
|
||||
char* filename = strtok(NULL, " \n");
|
||||
|
||||
if (type != NULL && filename != NULL) {
|
||||
if (firmware_type != NULL) {
|
||||
LOGE("ignoring attempt to do multiple firmware updates");
|
||||
} else {
|
||||
firmware_type = strdup(type);
|
||||
firmware_filename = strdup(filename);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(command, "ui_print") == 0) {
|
||||
char* str = strtok(NULL, "\n");
|
||||
if (str) {
|
||||
ui_print(str);
|
||||
} else {
|
||||
ui_print("\n");
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown command [%s]\n", command);
|
||||
}
|
||||
}
|
||||
fclose(from_child);
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
|
||||
if (firmware_type != NULL) {
|
||||
return handle_firmware_update(firmware_type, firmware_filename, zip);
|
||||
} else {
|
||||
return INSTALL_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
handle_update_package(const char *path, ZipArchive *zip,
|
||||
const RSAPublicKey *keys, int numKeys)
|
||||
{
|
||||
// Give verification half the progress bar...
|
||||
ui_print("Verifying update package...\n");
|
||||
@ -122,7 +322,7 @@ handle_update_package(const char *path, ZipArchive *zip)
|
||||
VERIFICATION_PROGRESS_FRACTION,
|
||||
VERIFICATION_PROGRESS_TIME);
|
||||
|
||||
if (!verify_jar_signature(zip, keys, sizeof(keys) / sizeof(keys[0]))) {
|
||||
if (!verify_jar_signature(zip, keys, numKeys)) {
|
||||
LOGE("Verification failed\n");
|
||||
return INSTALL_CORRUPT;
|
||||
}
|
||||
@ -130,6 +330,16 @@ handle_update_package(const char *path, ZipArchive *zip)
|
||||
// Update should take the rest of the progress bar.
|
||||
ui_print("Installing update...\n");
|
||||
|
||||
int result = try_update_binary(path, zip);
|
||||
if (result == INSTALL_SUCCESS || result == INSTALL_ERROR) {
|
||||
register_package_root(NULL, NULL); // Unregister package root
|
||||
return result;
|
||||
}
|
||||
|
||||
// if INSTALL_CORRUPT is returned, this package doesn't have an
|
||||
// update binary. Fall back to the older mechanism of looking for
|
||||
// an update script.
|
||||
|
||||
const ZipEntry *script_entry;
|
||||
script_entry = find_update_script(zip);
|
||||
if (script_entry == NULL) {
|
||||
@ -147,6 +357,80 @@ handle_update_package(const char *path, ZipArchive *zip)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Reads a file containing one or more public keys as produced by
|
||||
// DumpPublicKey: this is an RSAPublicKey struct as it would appear
|
||||
// as a C source literal, eg:
|
||||
//
|
||||
// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
|
||||
//
|
||||
// (Note that the braces and commas in this example are actual
|
||||
// characters the parser expects to find in the file; the ellipses
|
||||
// indicate more numbers omitted from this example.)
|
||||
//
|
||||
// The file may contain multiple keys in this format, separated by
|
||||
// commas. The last key must not be followed by a comma.
|
||||
//
|
||||
// Returns NULL if the file failed to parse, or if it contain zero keys.
|
||||
static RSAPublicKey*
|
||||
load_keys(const char* filename, int* numKeys) {
|
||||
RSAPublicKey* out = NULL;
|
||||
*numKeys = 0;
|
||||
|
||||
FILE* f = fopen(filename, "r");
|
||||
if (f == NULL) {
|
||||
LOGE("opening %s: %s\n", filename, strerror(errno));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
int i;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
++*numKeys;
|
||||
out = realloc(out, *numKeys * sizeof(RSAPublicKey));
|
||||
RSAPublicKey* key = out + (*numKeys - 1);
|
||||
if (fscanf(f, " { %i , %i , { %i",
|
||||
&(key->len), &(key->n0inv), &(key->n[0])) != 3) {
|
||||
goto exit;
|
||||
}
|
||||
if (key->len != RSANUMWORDS) {
|
||||
LOGE("key length (%d) does not match expected size\n", key->len);
|
||||
goto exit;
|
||||
}
|
||||
for (i = 1; i < key->len; ++i) {
|
||||
if (fscanf(f, " , %i", &(key->n[i])) != 1) goto exit;
|
||||
}
|
||||
if (fscanf(f, " } , { %i", &(key->rr[0])) != 1) goto exit;
|
||||
for (i = 1; i < key->len; ++i) {
|
||||
if (fscanf(f, " , %i", &(key->rr[i])) != 1) goto exit;
|
||||
}
|
||||
fscanf(f, " } } ");
|
||||
|
||||
// if the line ends in a comma, this file has more keys.
|
||||
switch (fgetc(f)) {
|
||||
case ',':
|
||||
// more keys to come.
|
||||
break;
|
||||
|
||||
case EOF:
|
||||
done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGE("unexpected character between keys\n");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return out;
|
||||
|
||||
exit:
|
||||
if (f) fclose(f);
|
||||
free(out);
|
||||
*numKeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
install_package(const char *root_path)
|
||||
{
|
||||
@ -169,6 +453,14 @@ install_package(const char *root_path)
|
||||
ui_print("Opening update package...\n");
|
||||
LOGI("Update file path: %s\n", path);
|
||||
|
||||
int numKeys;
|
||||
RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
|
||||
if (loadedKeys == NULL) {
|
||||
LOGE("Failed to load keys\n");
|
||||
return INSTALL_CORRUPT;
|
||||
}
|
||||
LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
|
||||
|
||||
/* Try to open the package.
|
||||
*/
|
||||
ZipArchive zip;
|
||||
@ -180,7 +472,8 @@ install_package(const char *root_path)
|
||||
|
||||
/* Verify and install the contents of the package.
|
||||
*/
|
||||
int status = handle_update_package(path, &zip);
|
||||
int status = handle_update_package(path, &zip, loadedKeys, numKeys);
|
||||
mzCloseZipArchive(&zip);
|
||||
free(loadedKeys);
|
||||
return status;
|
||||
}
|
||||
|
@ -29,97 +29,84 @@
|
||||
|
||||
#include <pixelflinger/pixelflinger.h>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
#include "minui.h"
|
||||
|
||||
// File signature for BMP files.
|
||||
// The letters 'BM' as a little-endian unsigned short.
|
||||
|
||||
#define BMP_SIGNATURE 0x4d42
|
||||
|
||||
typedef struct {
|
||||
// constant, value should equal BMP_SIGNATURE
|
||||
unsigned short bfType;
|
||||
// size of the file in bytes.
|
||||
unsigned long bfSize;
|
||||
// must always be set to zero.
|
||||
unsigned short bfReserved1;
|
||||
// must always be set to zero.
|
||||
unsigned short bfReserved2;
|
||||
// offset from the beginning of the file to the bitmap data.
|
||||
unsigned long bfOffBits;
|
||||
|
||||
// The BITMAPINFOHEADER:
|
||||
// size of the BITMAPINFOHEADER structure, in bytes.
|
||||
unsigned long biSize;
|
||||
// width of the image, in pixels.
|
||||
unsigned long biWidth;
|
||||
// height of the image, in pixels.
|
||||
unsigned long biHeight;
|
||||
// number of planes of the target device, must be set to 1.
|
||||
unsigned short biPlanes;
|
||||
// number of bits per pixel.
|
||||
unsigned short biBitCount;
|
||||
// type of compression, zero means no compression.
|
||||
unsigned long biCompression;
|
||||
// size of the image data, in bytes. If there is no compression,
|
||||
// it is valid to set this member to zero.
|
||||
unsigned long biSizeImage;
|
||||
// horizontal pixels per meter on the designated targer device,
|
||||
// usually set to zero.
|
||||
unsigned long biXPelsPerMeter;
|
||||
// vertical pixels per meter on the designated targer device,
|
||||
// usually set to zero.
|
||||
unsigned long biYPelsPerMeter;
|
||||
// number of colors used in the bitmap, if set to zero the
|
||||
// number of colors is calculated using the biBitCount member.
|
||||
unsigned long biClrUsed;
|
||||
// number of color that are 'important' for the bitmap,
|
||||
// if set to zero, all colors are important.
|
||||
unsigned long biClrImportant;
|
||||
} __attribute__((packed)) BitMapFileHeader;
|
||||
// libpng gives "undefined reference to 'pow'" errors, and I have no
|
||||
// idea how to convince the build system to link with -lm. We don't
|
||||
// need this functionality (it's used for gamma adjustment) so provide
|
||||
// a dummy implementation to satisfy the linker.
|
||||
double pow(double x, double y) {
|
||||
return x;
|
||||
}
|
||||
|
||||
int res_create_surface(const char* name, gr_surface* pSurface) {
|
||||
char resPath[256];
|
||||
BitMapFileHeader header;
|
||||
GGLSurface* surface = NULL;
|
||||
int result = 0;
|
||||
|
||||
snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.bmp", name);
|
||||
unsigned char header[8];
|
||||
png_structp png_ptr = NULL;
|
||||
png_infop info_ptr = NULL;
|
||||
|
||||
snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
|
||||
resPath[sizeof(resPath)-1] = '\0';
|
||||
int fd = open(resPath, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
FILE* fp = fopen(resPath, "rb");
|
||||
if (fp == NULL) {
|
||||
result = -1;
|
||||
goto exit;
|
||||
}
|
||||
size_t bytesRead = read(fd, &header, sizeof(header));
|
||||
|
||||
size_t bytesRead = fread(header, 1, sizeof(header), fp);
|
||||
if (bytesRead != sizeof(header)) {
|
||||
result = -2;
|
||||
goto exit;
|
||||
}
|
||||
if (header.bfType != BMP_SIGNATURE) {
|
||||
result = -3; // Not a legal header
|
||||
|
||||
if (png_sig_cmp(header, 0, sizeof(header))) {
|
||||
result = -3;
|
||||
goto exit;
|
||||
}
|
||||
if (header.biPlanes != 1) {
|
||||
|
||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (!png_ptr) {
|
||||
result = -4;
|
||||
goto exit;
|
||||
}
|
||||
if (!(header.biBitCount == 24 || header.biBitCount == 32)) {
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
result = -5;
|
||||
goto exit;
|
||||
}
|
||||
if (header.biCompression != 0) {
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
result = -6;
|
||||
goto exit;
|
||||
}
|
||||
size_t width = header.biWidth;
|
||||
size_t height = header.biHeight;
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
png_set_sig_bytes(png_ptr, sizeof(header));
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
size_t width = info_ptr->width;
|
||||
size_t height = info_ptr->height;
|
||||
size_t stride = 4 * width;
|
||||
size_t pixelSize = stride * height;
|
||||
|
||||
|
||||
int color_type = info_ptr->color_type;
|
||||
int bit_depth = info_ptr->bit_depth;
|
||||
int channels = info_ptr->channels;
|
||||
if (bit_depth != 8 || (channels != 3 && channels != 4) ||
|
||||
(color_type != PNG_COLOR_TYPE_RGB &&
|
||||
color_type != PNG_COLOR_TYPE_RGBA)) {
|
||||
return -7;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
surface = malloc(sizeof(GGLSurface) + pixelSize);
|
||||
if (surface == NULL) {
|
||||
result = -7;
|
||||
result = -8;
|
||||
goto exit;
|
||||
}
|
||||
unsigned char* pData = (unsigned char*) (surface + 1);
|
||||
@ -128,63 +115,43 @@ int res_create_surface(const char* name, gr_surface* pSurface) {
|
||||
surface->height = height;
|
||||
surface->stride = width; /* Yes, pixels, not bytes */
|
||||
surface->data = pData;
|
||||
surface->format = (header.biBitCount == 24) ?
|
||||
surface->format = (channels == 3) ?
|
||||
GGL_PIXEL_FORMAT_RGBX_8888 : GGL_PIXEL_FORMAT_RGBA_8888;
|
||||
|
||||
// Source pixel bytes are stored B G R {A}
|
||||
|
||||
lseek(fd, header.bfOffBits, SEEK_SET);
|
||||
size_t y;
|
||||
if (header.biBitCount == 24) { // RGB
|
||||
size_t inputStride = (((3 * width + 3) >> 2) << 2);
|
||||
for (y = 0; y < height; y++) {
|
||||
unsigned char* pRow = pData + (height - (y + 1)) * stride;
|
||||
bytesRead = read(fd, pRow, inputStride);
|
||||
if (bytesRead != inputStride) {
|
||||
result = -8;
|
||||
goto exit;
|
||||
}
|
||||
int y;
|
||||
if (channels == 3) {
|
||||
for (y = 0; y < height; ++y) {
|
||||
unsigned char* pRow = pData + y * stride;
|
||||
png_read_row(png_ptr, pRow, NULL);
|
||||
|
||||
int x;
|
||||
for(x = width - 1; x >= 0; x--) {
|
||||
int sx = x * 3;
|
||||
int dx = x * 4;
|
||||
unsigned char b = pRow[sx];
|
||||
unsigned char r = pRow[sx];
|
||||
unsigned char g = pRow[sx + 1];
|
||||
unsigned char r = pRow[sx + 2];
|
||||
unsigned char b = pRow[sx + 2];
|
||||
unsigned char a = 0xff;
|
||||
pRow[dx ] = r; // r
|
||||
pRow[dx + 1] = g; // g
|
||||
pRow[dx + 2] = b; // b;
|
||||
pRow[dx + 2] = b; // b
|
||||
pRow[dx + 3] = a;
|
||||
}
|
||||
}
|
||||
} else { // RGBA
|
||||
for (y = 0; y < height; y++) {
|
||||
unsigned char* pRow = pData + (height - (y + 1)) * stride;
|
||||
bytesRead = read(fd, pRow, stride);
|
||||
if (bytesRead != stride) {
|
||||
result = -9;
|
||||
goto exit;
|
||||
}
|
||||
size_t x;
|
||||
for(x = 0; x < width; x++) {
|
||||
size_t xx = x * 4;
|
||||
unsigned char b = pRow[xx];
|
||||
unsigned char g = pRow[xx + 1];
|
||||
unsigned char r = pRow[xx + 2];
|
||||
unsigned char a = pRow[xx + 3];
|
||||
pRow[xx ] = r;
|
||||
pRow[xx + 1] = g;
|
||||
pRow[xx + 2] = b;
|
||||
pRow[xx + 3] = a;
|
||||
}
|
||||
} else {
|
||||
for (y = 0; y < height; ++y) {
|
||||
unsigned char* pRow = pData + y * stride;
|
||||
png_read_row(png_ptr, pRow, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
*pSurface = (gr_surface) surface;
|
||||
|
||||
exit:
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
if (fp != NULL) {
|
||||
fclose(fp);
|
||||
}
|
||||
if (result < 0) {
|
||||
if (surface) {
|
||||
|
38
minzip/Zip.c
@ -41,7 +41,7 @@ enum {
|
||||
CENSIZ = 20,
|
||||
CENLEN = 24,
|
||||
CENNAM = 28,
|
||||
CENEXT = 30,
|
||||
CENEXT = 30,
|
||||
CENCOM = 32,
|
||||
CENDSK = 34,
|
||||
CENATT = 36,
|
||||
@ -66,13 +66,13 @@ enum {
|
||||
|
||||
LOCSIG = 0x04034b50, // PK34
|
||||
LOCHDR = 30,
|
||||
|
||||
|
||||
LOCVER = 4,
|
||||
LOCFLG = 6,
|
||||
LOCHOW = 8,
|
||||
LOCTIM = 10,
|
||||
LOCCRC = 14,
|
||||
LOCSIZ = 18,
|
||||
LOCSIZ = 18,
|
||||
LOCLEN = 22,
|
||||
LOCNAM = 26,
|
||||
LOCEXT = 28,
|
||||
@ -757,7 +757,7 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
|
||||
{
|
||||
CopyProcessArgs args;
|
||||
bool ret;
|
||||
|
||||
|
||||
args.buf = buf;
|
||||
args.bufLen = bufLen;
|
||||
ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
|
||||
@ -770,15 +770,29 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
|
||||
}
|
||||
|
||||
static bool writeProcessFunction(const unsigned char *data, int dataLen,
|
||||
void *fd)
|
||||
void *cookie)
|
||||
{
|
||||
ssize_t n = write((int)fd, data, dataLen);
|
||||
if (n != dataLen) {
|
||||
LOGE("Can't write %d bytes (only %ld) from zip file: %s\n",
|
||||
dataLen, n, strerror(errno));
|
||||
return false;
|
||||
int fd = (int)cookie;
|
||||
|
||||
ssize_t soFar = 0;
|
||||
while (true) {
|
||||
ssize_t n = write(fd, data+soFar, dataLen-soFar);
|
||||
if (n <= 0) {
|
||||
LOGE("Error writing %ld bytes from zip file from %p: %s\n",
|
||||
dataLen-soFar, data+soFar, strerror(errno));
|
||||
if (errno != EINTR) {
|
||||
return false;
|
||||
}
|
||||
} else if (n > 0) {
|
||||
soFar += n;
|
||||
if (soFar == dataLen) return true;
|
||||
if (soFar > dataLen) {
|
||||
LOGE("write overrun? (%ld bytes instead of %d)\n",
|
||||
soFar, dataLen);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -788,7 +802,7 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
|
||||
const ZipEntry *pEntry, int fd)
|
||||
{
|
||||
bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
|
||||
(void *)fd);
|
||||
(void*)fd);
|
||||
if (!ret) {
|
||||
LOGE("Can't extract entry to file.\n");
|
||||
return false;
|
||||
|
@ -297,7 +297,14 @@ static int read_block(const MtdPartition *partition, int fd, char *data)
|
||||
after.corrected - before.corrected,
|
||||
after.failed - before.failed, pos);
|
||||
} else {
|
||||
return 0; // Success!
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
if (data[i] != 0) {
|
||||
return 0; // Success!
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "mtd: read all-zero block at 0x%08lx; skipping\n",
|
||||
pos);
|
||||
}
|
||||
|
||||
pos += partition->erase_size;
|
||||
@ -326,6 +333,10 @@ ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
|
||||
read += ctx->partition->erase_size;
|
||||
}
|
||||
|
||||
if (read >= len) {
|
||||
return read;
|
||||
}
|
||||
|
||||
// Read the next block into the buffer
|
||||
if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
|
||||
if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
|
||||
|
23
recovery.c
@ -265,7 +265,6 @@ test_amend()
|
||||
{
|
||||
extern int test_symtab(void);
|
||||
extern int test_cmd_fn(void);
|
||||
extern int test_permissions(void);
|
||||
int ret;
|
||||
LOGD("Testing symtab...\n");
|
||||
ret = test_symtab();
|
||||
@ -273,9 +272,6 @@ test_amend()
|
||||
LOGD("Testing cmd_fn...\n");
|
||||
ret = test_cmd_fn();
|
||||
LOGD(" returned %d\n", ret);
|
||||
LOGD("Testing permissions...\n");
|
||||
ret = test_permissions();
|
||||
LOGD(" returned %d\n", ret);
|
||||
}
|
||||
#endif // TEST_AMEND
|
||||
|
||||
@ -291,7 +287,8 @@ erase_root(const char *root)
|
||||
static void
|
||||
prompt_and_wait()
|
||||
{
|
||||
char* headers[] = { "Android system recovery utility",
|
||||
char* headers[] = { "Android system recovery <"
|
||||
EXPAND(RECOVERY_API_VERSION) ">",
|
||||
"",
|
||||
"Use trackball to highlight;",
|
||||
"click to select.",
|
||||
@ -302,9 +299,11 @@ prompt_and_wait()
|
||||
#define ITEM_REBOOT 0
|
||||
#define ITEM_APPLY_SDCARD 1
|
||||
#define ITEM_WIPE_DATA 2
|
||||
#define ITEM_WIPE_CACHE 3
|
||||
char* items[] = { "reboot system now [Home+Back]",
|
||||
"apply sdcard:update.zip [Alt+S]",
|
||||
"wipe data/factory reset [Alt+W]",
|
||||
"wipe cache partition",
|
||||
NULL };
|
||||
|
||||
ui_start_menu(headers, items);
|
||||
@ -357,6 +356,13 @@ prompt_and_wait()
|
||||
if (!ui_text_visible()) return;
|
||||
break;
|
||||
|
||||
case ITEM_WIPE_CACHE:
|
||||
ui_print("\n-- Wiping cache...\n");
|
||||
erase_root("CACHE:");
|
||||
ui_print("Cache wipe complete.\n");
|
||||
if (!ui_text_visible()) return;
|
||||
break;
|
||||
|
||||
case ITEM_APPLY_SDCARD:
|
||||
ui_print("\n-- Install from sdcard...\n");
|
||||
int status = install_package(SDCARD_PACKAGE_FILE);
|
||||
@ -366,7 +372,12 @@ prompt_and_wait()
|
||||
} else if (!ui_text_visible()) {
|
||||
return; // reboot if logs aren't visible
|
||||
} else {
|
||||
ui_print("Install from sdcard complete.\n");
|
||||
if (firmware_update_pending()) {
|
||||
ui_print("\nReboot via home+back or menu\n"
|
||||
"to complete installation.\n");
|
||||
} else {
|
||||
ui_print("\nInstall from sdcard complete.\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 89 KiB |
BIN
res/images/icon_error.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 89 KiB |
BIN
res/images/icon_firmware_error.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 89 KiB |
BIN
res/images/icon_firmware_install.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 89 KiB |
BIN
res/images/icon_installing.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate1.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate2.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate3.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate4.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate5.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
res/images/indeterminate6.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 136 B |
BIN
res/images/progress_bar_empty.png
Normal file
After Width: | Height: | Size: 148 B |
Before Width: | Height: | Size: 294 B |
BIN
res/images/progress_bar_empty_left_round.png
Normal file
After Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 294 B |
BIN
res/images/progress_bar_empty_right_round.png
Normal file
After Width: | Height: | Size: 211 B |
Before Width: | Height: | Size: 136 B |
BIN
res/images/progress_bar_fill.png
Normal file
After Width: | Height: | Size: 117 B |
Before Width: | Height: | Size: 294 B |
BIN
res/images/progress_bar_left_round.png
Normal file
After Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 294 B |
BIN
res/images/progress_bar_right_round.png
Normal file
After Width: | Height: | Size: 192 B |
1
ui.c
@ -46,7 +46,6 @@ static gr_surface gProgressBarEmpty[NUM_SIDES];
|
||||
static gr_surface gProgressBarFill[NUM_SIDES];
|
||||
|
||||
static const struct { gr_surface* surface; const char *name; } BITMAPS[] = {
|
||||
{ &gBackgroundIcon[BACKGROUND_ICON_UNPACKING], "icon_unpacking" },
|
||||
{ &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" },
|
||||
{ &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" },
|
||||
{ &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING],
|
||||
|
30
updater/Android.mk
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
updater_src_files := \
|
||||
install.c \
|
||||
updater.c
|
||||
|
||||
#
|
||||
# Build a statically-linked binary to include in OTA packages
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# Build only in eng, so we don't end up with a copy of this in /system
|
||||
# on user builds. (TODO: find a better way to build device binaries
|
||||
# needed only for OTA packages.)
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
|
||||
LOCAL_SRC_FILES := $(updater_src_files)
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libapplypatch libedify libmtdutils libminzip libz
|
||||
LOCAL_STATIC_LIBRARIES += libmincrypt libbz
|
||||
LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
|
||||
|
||||
LOCAL_MODULE := updater
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
788
updater/install.c
Normal file
@ -0,0 +1,788 @@
|
||||
/*
|
||||
* 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 <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cutils/misc.h"
|
||||
#include "cutils/properties.h"
|
||||
#include "edify/expr.h"
|
||||
#include "minzip/DirUtil.h"
|
||||
#include "mtdutils/mounts.h"
|
||||
#include "mtdutils/mtdutils.h"
|
||||
#include "updater.h"
|
||||
|
||||
|
||||
// mount(type, location, mount_point)
|
||||
//
|
||||
// what: type="MTD" location="<partition>" to mount a yaffs2 filesystem
|
||||
// type="vfat" location="/dev/block/<whatever>" to mount a device
|
||||
char* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
if (argc != 3) {
|
||||
return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
|
||||
}
|
||||
char* type;
|
||||
char* location;
|
||||
char* mount_point;
|
||||
if (ReadArgs(state, argv, 3, &type, &location, &mount_point) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strlen(type) == 0) {
|
||||
ErrorAbort(state, "type argument to %s() can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
if (strlen(location) == 0) {
|
||||
ErrorAbort(state, "location argument to %s() can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
if (strlen(mount_point) == 0) {
|
||||
ErrorAbort(state, "mount_point argument to %s() can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
mkdir(mount_point, 0755);
|
||||
|
||||
if (strcmp(type, "MTD") == 0) {
|
||||
mtd_scan_partitions();
|
||||
const MtdPartition* mtd;
|
||||
mtd = mtd_find_partition_by_name(location);
|
||||
if (mtd == NULL) {
|
||||
fprintf(stderr, "%s: no mtd partition named \"%s\"",
|
||||
name, location);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
if (mtd_mount_partition(mtd, mount_point, "yaffs2", 0 /* rw */) != 0) {
|
||||
fprintf(stderr, "mtd mount of %s failed: %s\n",
|
||||
location, strerror(errno));
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
result = mount_point;
|
||||
} else {
|
||||
if (mount(location, mount_point, type,
|
||||
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
|
||||
result = strdup("");
|
||||
} else {
|
||||
result = mount_point;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
free(type);
|
||||
free(location);
|
||||
if (result != mount_point) free(mount_point);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// is_mounted(mount_point)
|
||||
char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
if (argc != 1) {
|
||||
return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
|
||||
}
|
||||
char* mount_point;
|
||||
if (ReadArgs(state, argv, 1, &mount_point) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (strlen(mount_point) == 0) {
|
||||
ErrorAbort(state, "mount_point argument to unmount() can't be empty");
|
||||
goto done;
|
||||
}
|
||||
|
||||
scan_mounted_volumes();
|
||||
const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
|
||||
if (vol == NULL) {
|
||||
result = strdup("");
|
||||
} else {
|
||||
result = mount_point;
|
||||
}
|
||||
|
||||
done:
|
||||
if (result != mount_point) free(mount_point);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
char* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
if (argc != 1) {
|
||||
return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
|
||||
}
|
||||
char* mount_point;
|
||||
if (ReadArgs(state, argv, 1, &mount_point) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (strlen(mount_point) == 0) {
|
||||
ErrorAbort(state, "mount_point argument to unmount() can't be empty");
|
||||
goto done;
|
||||
}
|
||||
|
||||
scan_mounted_volumes();
|
||||
const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
|
||||
if (vol == NULL) {
|
||||
fprintf(stderr, "unmount of %s failed; no such volume\n", mount_point);
|
||||
result = strdup("");
|
||||
} else {
|
||||
unmount_mounted_volume(vol);
|
||||
result = mount_point;
|
||||
}
|
||||
|
||||
done:
|
||||
if (result != mount_point) free(mount_point);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// format(type, location)
|
||||
//
|
||||
// type="MTD" location=partition
|
||||
char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
if (argc != 2) {
|
||||
return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
|
||||
}
|
||||
char* type;
|
||||
char* location;
|
||||
if (ReadArgs(state, argv, 2, &type, &location) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strlen(type) == 0) {
|
||||
ErrorAbort(state, "type argument to %s() can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
if (strlen(location) == 0) {
|
||||
ErrorAbort(state, "location argument to %s() can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (strcmp(type, "MTD") == 0) {
|
||||
mtd_scan_partitions();
|
||||
const MtdPartition* mtd = mtd_find_partition_by_name(location);
|
||||
if (mtd == NULL) {
|
||||
fprintf(stderr, "%s: no mtd partition named \"%s\"",
|
||||
name, location);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
MtdWriteContext* ctx = mtd_write_partition(mtd);
|
||||
if (ctx == NULL) {
|
||||
fprintf(stderr, "%s: can't write \"%s\"", name, location);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
if (mtd_erase_blocks(ctx, -1) == -1) {
|
||||
mtd_write_close(ctx);
|
||||
fprintf(stderr, "%s: failed to erase \"%s\"", name, location);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
if (mtd_write_close(ctx) != 0) {
|
||||
fprintf(stderr, "%s: failed to close \"%s\"", name, location);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
result = location;
|
||||
} else {
|
||||
fprintf(stderr, "%s: unsupported type \"%s\"", name, type);
|
||||
}
|
||||
|
||||
done:
|
||||
free(type);
|
||||
if (result != location) free(location);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
char* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char** paths = malloc(argc * sizeof(char*));
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
paths[i] = Evaluate(state, argv[i]);
|
||||
if (paths[i] == NULL) {
|
||||
int j;
|
||||
for (j = 0; j < i; ++i) {
|
||||
free(paths[j]);
|
||||
}
|
||||
free(paths);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool recursive = (strcmp(name, "delete_recursive") == 0);
|
||||
|
||||
int success = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0)
|
||||
++success;
|
||||
free(paths[i]);
|
||||
}
|
||||
free(paths);
|
||||
|
||||
char buffer[10];
|
||||
sprintf(buffer, "%d", success);
|
||||
return strdup(buffer);
|
||||
}
|
||||
|
||||
|
||||
char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc != 2) {
|
||||
return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
|
||||
}
|
||||
char* frac_str;
|
||||
char* sec_str;
|
||||
if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double frac = strtod(frac_str, NULL);
|
||||
int sec = strtol(sec_str, NULL, 10);
|
||||
|
||||
UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
|
||||
fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
|
||||
|
||||
free(sec_str);
|
||||
return frac_str;
|
||||
}
|
||||
|
||||
char* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc != 1) {
|
||||
return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
|
||||
}
|
||||
char* frac_str;
|
||||
if (ReadArgs(state, argv, 1, &frac_str) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double frac = strtod(frac_str, NULL);
|
||||
|
||||
UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
|
||||
fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
|
||||
|
||||
return frac_str;
|
||||
}
|
||||
|
||||
// package_extract_dir(package_path, destination_path)
|
||||
char* PackageExtractDirFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
if (argc != 2) {
|
||||
return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
|
||||
}
|
||||
char* zip_path;
|
||||
char* dest_path;
|
||||
if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
|
||||
|
||||
ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
|
||||
|
||||
// To create a consistent system image, never use the clock for timestamps.
|
||||
struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default
|
||||
|
||||
bool success = mzExtractRecursive(za, zip_path, dest_path,
|
||||
MZ_EXTRACT_FILES_ONLY, ×tamp,
|
||||
NULL, NULL);
|
||||
free(zip_path);
|
||||
free(dest_path);
|
||||
return strdup(success ? "t" : "");
|
||||
}
|
||||
|
||||
|
||||
// package_extract_file(package_path, destination_path)
|
||||
char* PackageExtractFileFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
if (argc != 2) {
|
||||
return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
|
||||
}
|
||||
char* zip_path;
|
||||
char* dest_path;
|
||||
if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
|
||||
|
||||
bool success = false;
|
||||
|
||||
ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
|
||||
const ZipEntry* entry = mzFindZipEntry(za, zip_path);
|
||||
if (entry == NULL) {
|
||||
fprintf(stderr, "%s: no %s in package\n", name, zip_path);
|
||||
goto done;
|
||||
}
|
||||
|
||||
FILE* f = fopen(dest_path, "wb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "%s: can't open %s for write: %s\n",
|
||||
name, dest_path, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
success = mzExtractZipEntryToFile(za, entry, fileno(f));
|
||||
fclose(f);
|
||||
|
||||
done:
|
||||
free(zip_path);
|
||||
free(dest_path);
|
||||
return strdup(success ? "t" : "");
|
||||
}
|
||||
|
||||
|
||||
// symlink target src1 src2 ...
|
||||
char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc == 0) {
|
||||
return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc);
|
||||
}
|
||||
char* target;
|
||||
target = Evaluate(state, argv[0]);
|
||||
if (target == NULL) return NULL;
|
||||
|
||||
char** srcs = ReadVarArgs(state, argc-1, argv+1);
|
||||
if (srcs == NULL) {
|
||||
free(target);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < argc-1; ++i) {
|
||||
symlink(target, srcs[i]);
|
||||
free(srcs[i]);
|
||||
}
|
||||
free(srcs);
|
||||
return strdup("");
|
||||
}
|
||||
|
||||
|
||||
char* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
bool recursive = (strcmp(name, "set_perm_recursive") == 0);
|
||||
|
||||
int min_args = 4 + (recursive ? 1 : 0);
|
||||
if (argc < min_args) {
|
||||
return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc);
|
||||
}
|
||||
|
||||
char** args = ReadVarArgs(state, argc, argv);
|
||||
if (args == NULL) return NULL;
|
||||
|
||||
char* end;
|
||||
int i;
|
||||
|
||||
int uid = strtoul(args[0], &end, 0);
|
||||
if (*end != '\0' || args[0][0] == 0) {
|
||||
ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
|
||||
goto done;
|
||||
}
|
||||
|
||||
int gid = strtoul(args[1], &end, 0);
|
||||
if (*end != '\0' || args[1][0] == 0) {
|
||||
ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
int dir_mode = strtoul(args[2], &end, 0);
|
||||
if (*end != '\0' || args[2][0] == 0) {
|
||||
ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
|
||||
goto done;
|
||||
}
|
||||
|
||||
int file_mode = strtoul(args[3], &end, 0);
|
||||
if (*end != '\0' || args[3][0] == 0) {
|
||||
ErrorAbort(state, "%s: \"%s\" not a valid filemode",
|
||||
name, args[3]);
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 4; i < argc; ++i) {
|
||||
dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
|
||||
}
|
||||
} else {
|
||||
int mode = strtoul(args[2], &end, 0);
|
||||
if (*end != '\0' || args[2][0] == 0) {
|
||||
ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 3; i < argc; ++i) {
|
||||
chown(args[i], uid, gid);
|
||||
chmod(args[i], mode);
|
||||
}
|
||||
}
|
||||
result = strdup("");
|
||||
|
||||
done:
|
||||
for (i = 0; i < argc; ++i) {
|
||||
free(args[i]);
|
||||
}
|
||||
free(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
if (argc != 1) {
|
||||
return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
|
||||
}
|
||||
char* key;
|
||||
key = Evaluate(state, argv[0]);
|
||||
if (key == NULL) return NULL;
|
||||
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
property_get(key, value, "");
|
||||
free(key);
|
||||
|
||||
return strdup(value);
|
||||
}
|
||||
|
||||
|
||||
// file_getprop(file, key)
|
||||
//
|
||||
// interprets 'file' as a getprop-style file (key=value pairs, one
|
||||
// per line, # comment lines and blank lines okay), and returns the value
|
||||
// for 'key' (or "" if it isn't defined).
|
||||
char* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
char* buffer = NULL;
|
||||
char* filename;
|
||||
char* key;
|
||||
if (ReadArgs(state, argv, 2, &filename, &key) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) < 0) {
|
||||
ErrorAbort(state, "%s: failed to stat \"%s\": %s",
|
||||
name, filename, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
#define MAX_FILE_GETPROP_SIZE 65536
|
||||
|
||||
if (st.st_size > MAX_FILE_GETPROP_SIZE) {
|
||||
ErrorAbort(state, "%s too large for %s (max %d)",
|
||||
filename, name, MAX_FILE_GETPROP_SIZE);
|
||||
goto done;
|
||||
}
|
||||
|
||||
buffer = malloc(st.st_size+1);
|
||||
if (buffer == NULL) {
|
||||
ErrorAbort(state, "%s: failed to alloc %d bytes", name, st.st_size+1);
|
||||
goto done;
|
||||
}
|
||||
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
ErrorAbort(state, "%s: failed to open %s: %s",
|
||||
name, filename, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (fread(buffer, 1, st.st_size, f) != st.st_size) {
|
||||
ErrorAbort(state, "%s: failed to read %d bytes from %s",
|
||||
name, st.st_size+1, filename);
|
||||
fclose(f);
|
||||
goto done;
|
||||
}
|
||||
buffer[st.st_size] = '\0';
|
||||
|
||||
fclose(f);
|
||||
|
||||
char* line = strtok(buffer, "\n");
|
||||
do {
|
||||
// skip whitespace at start of line
|
||||
while (*line && isspace(*line)) ++line;
|
||||
|
||||
// comment or blank line: skip to next line
|
||||
if (*line == '\0' || *line == '#') continue;
|
||||
|
||||
char* equal = strchr(line, '=');
|
||||
if (equal == NULL) {
|
||||
ErrorAbort(state, "%s: malformed line \"%s\": %s not a prop file?",
|
||||
name, line, filename);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// trim whitespace between key and '='
|
||||
char* key_end = equal-1;
|
||||
while (key_end > line && isspace(*key_end)) --key_end;
|
||||
key_end[1] = '\0';
|
||||
|
||||
// not the key we're looking for
|
||||
if (strcmp(key, line) != 0) continue;
|
||||
|
||||
// skip whitespace after the '=' to the start of the value
|
||||
char* val_start = equal+1;
|
||||
while(*val_start && isspace(*val_start)) ++val_start;
|
||||
|
||||
// trim trailing whitespace
|
||||
char* val_end = val_start + strlen(val_start)-1;
|
||||
while (val_end > val_start && isspace(*val_end)) --val_end;
|
||||
val_end[1] = '\0';
|
||||
|
||||
result = strdup(val_start);
|
||||
break;
|
||||
|
||||
} while ((line = strtok(NULL, "\n")));
|
||||
|
||||
if (result == NULL) result = strdup("");
|
||||
|
||||
done:
|
||||
free(filename);
|
||||
free(key);
|
||||
free(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static bool write_raw_image_cb(const unsigned char* data,
|
||||
int data_len, void* ctx) {
|
||||
int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len);
|
||||
if (r == data_len) return true;
|
||||
fprintf(stderr, "%s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// write_raw_image(file, partition)
|
||||
char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
|
||||
char* partition;
|
||||
char* filename;
|
||||
if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strlen(partition) == 0) {
|
||||
ErrorAbort(state, "partition argument to %s can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
if (strlen(filename) == 0) {
|
||||
ErrorAbort(state, "file argument to %s can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
mtd_scan_partitions();
|
||||
const MtdPartition* mtd = mtd_find_partition_by_name(partition);
|
||||
if (mtd == NULL) {
|
||||
fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
|
||||
MtdWriteContext* ctx = mtd_write_partition(mtd);
|
||||
if (ctx == NULL) {
|
||||
fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
|
||||
name, partition);
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
|
||||
bool success;
|
||||
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "%s: can't open %s: %s\n",
|
||||
name, filename, strerror(errno));
|
||||
result = strdup("");
|
||||
goto done;
|
||||
}
|
||||
|
||||
success = true;
|
||||
char* buffer = malloc(BUFSIZ);
|
||||
int read;
|
||||
while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
|
||||
int wrote = mtd_write_data(ctx, buffer, read);
|
||||
success = success && (wrote == read);
|
||||
if (!success) {
|
||||
fprintf(stderr, "mtd_write_data to %s failed: %s\n",
|
||||
partition, strerror(errno));
|
||||
}
|
||||
}
|
||||
free(buffer);
|
||||
fclose(f);
|
||||
|
||||
if (mtd_erase_blocks(ctx, -1) == -1) {
|
||||
fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
|
||||
}
|
||||
if (mtd_write_close(ctx) != 0) {
|
||||
fprintf(stderr, "%s: error closing write of %s\n", name, partition);
|
||||
}
|
||||
|
||||
printf("%s %s partition from %s\n",
|
||||
success ? "wrote" : "failed to write", partition, filename);
|
||||
|
||||
result = success ? partition : strdup("");
|
||||
|
||||
done:
|
||||
if (result != partition) free(partition);
|
||||
free(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
// write_firmware_image(file, partition)
|
||||
//
|
||||
// partition is "radio" or "hboot"
|
||||
// file is not used until after updater exits
|
||||
//
|
||||
// TODO: this should live in some HTC-specific library
|
||||
char* WriteFirmwareImageFn(const char* name, State* state,
|
||||
int argc, Expr* argv[]) {
|
||||
char* result = NULL;
|
||||
|
||||
char* partition;
|
||||
char* filename;
|
||||
if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strlen(partition) == 0) {
|
||||
ErrorAbort(state, "partition argument to %s can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
if (strlen(filename) == 0) {
|
||||
ErrorAbort(state, "file argument to %s can't be empty", name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
FILE* cmd = ((UpdaterInfo*)(state->cookie))->cmd_pipe;
|
||||
fprintf(cmd, "firmware %s %s\n", partition, filename);
|
||||
|
||||
printf("will write %s firmware from %s\n", partition, filename);
|
||||
result = partition;
|
||||
|
||||
done:
|
||||
if (result != partition) free(partition);
|
||||
free(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
extern int applypatch(int argc, char** argv);
|
||||
|
||||
// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1:patch, ...)
|
||||
// apply_patch_check(file, sha1, ...)
|
||||
// apply_patch_space(bytes)
|
||||
char* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
printf("in applypatchfn (%s)\n", name);
|
||||
|
||||
char* prepend = NULL;
|
||||
if (strstr(name, "check") != NULL) {
|
||||
prepend = "-c";
|
||||
} else if (strstr(name, "space") != NULL) {
|
||||
prepend = "-s";
|
||||
}
|
||||
|
||||
char** args = ReadVarArgs(state, argc, argv);
|
||||
if (args == NULL) return NULL;
|
||||
|
||||
// insert the "program name" argv[0] and a copy of the "prepend"
|
||||
// string (if any) at the start of the args.
|
||||
|
||||
int extra = 1 + (prepend != NULL ? 1 : 0);
|
||||
char** temp = malloc((argc+extra) * sizeof(char*));
|
||||
memcpy(temp+extra, args, argc * sizeof(char*));
|
||||
temp[0] = strdup("updater");
|
||||
if (prepend) {
|
||||
temp[1] = strdup(prepend);
|
||||
}
|
||||
free(args);
|
||||
args = temp;
|
||||
argc += extra;
|
||||
|
||||
printf("calling applypatch\n");
|
||||
fflush(stdout);
|
||||
int result = applypatch(argc, args);
|
||||
printf("applypatch returned %d\n", result);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
free(args[i]);
|
||||
}
|
||||
free(args);
|
||||
|
||||
switch (result) {
|
||||
case 0: return strdup("t");
|
||||
case 1: return strdup("");
|
||||
default: return ErrorAbort(state, "applypatch couldn't parse args");
|
||||
}
|
||||
}
|
||||
|
||||
char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
|
||||
char** args = ReadVarArgs(state, argc, argv);
|
||||
if (args == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int size = 0;
|
||||
int i;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
size += strlen(args[i]);
|
||||
}
|
||||
char* buffer = malloc(size+1);
|
||||
size = 0;
|
||||
for (i = 0; i < argc; ++i) {
|
||||
strcpy(buffer+size, args[i]);
|
||||
size += strlen(args[i]);
|
||||
free(args[i]);
|
||||
}
|
||||
free(args);
|
||||
buffer[size] = '\0';
|
||||
|
||||
char* line = strtok(buffer, "\n");
|
||||
while (line) {
|
||||
fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe,
|
||||
"ui_print %s\n", line);
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
void RegisterInstallFunctions() {
|
||||
RegisterFunction("mount", MountFn);
|
||||
RegisterFunction("is_mounted", IsMountedFn);
|
||||
RegisterFunction("unmount", UnmountFn);
|
||||
RegisterFunction("format", FormatFn);
|
||||
RegisterFunction("show_progress", ShowProgressFn);
|
||||
RegisterFunction("set_progress", SetProgressFn);
|
||||
RegisterFunction("delete", DeleteFn);
|
||||
RegisterFunction("delete_recursive", DeleteFn);
|
||||
RegisterFunction("package_extract_dir", PackageExtractDirFn);
|
||||
RegisterFunction("package_extract_file", PackageExtractFileFn);
|
||||
RegisterFunction("symlink", SymlinkFn);
|
||||
RegisterFunction("set_perm", SetPermFn);
|
||||
RegisterFunction("set_perm_recursive", SetPermFn);
|
||||
|
||||
RegisterFunction("getprop", GetPropFn);
|
||||
RegisterFunction("file_getprop", FileGetPropFn);
|
||||
RegisterFunction("write_raw_image", WriteRawImageFn);
|
||||
RegisterFunction("write_firmware_image", WriteFirmwareImageFn);
|
||||
|
||||
RegisterFunction("apply_patch", ApplyPatchFn);
|
||||
RegisterFunction("apply_patch_check", ApplyPatchFn);
|
||||
RegisterFunction("apply_patch_space", ApplyPatchFn);
|
||||
|
||||
RegisterFunction("ui_print", UIPrintFn);
|
||||
}
|
22
updater/install.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 _UPDATER_INSTALL_H_
|
||||
#define _UPDATER_INSTALL_H_
|
||||
|
||||
void RegisterInstallFunctions();
|
||||
|
||||
#endif
|
128
updater/updater.c
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "edify/expr.h"
|
||||
#include "updater.h"
|
||||
#include "install.h"
|
||||
#include "minzip/Zip.h"
|
||||
|
||||
// Where in the package we expect to find the edify script to execute.
|
||||
// (Note it's "updateR-script", not the older "update-script".)
|
||||
#define SCRIPT_NAME "META-INF/com/google/android/updater-script"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "unexpected number of arguments (%d)\n", argc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* version = argv[1];
|
||||
if ((version[0] != '1' && version[0] != '2') || version[1] != '\0') {
|
||||
// We support version "1" or "2".
|
||||
fprintf(stderr, "wrong updater binary API; expected 1 or 2, got %s\n",
|
||||
argv[1]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Set up the pipe for sending commands back to the parent process.
|
||||
|
||||
int fd = atoi(argv[2]);
|
||||
FILE* cmd_pipe = fdopen(fd, "wb");
|
||||
setlinebuf(cmd_pipe);
|
||||
|
||||
// Extract the script from the package.
|
||||
|
||||
char* package_data = argv[3];
|
||||
ZipArchive za;
|
||||
int err;
|
||||
err = mzOpenZipArchive(package_data, &za);
|
||||
if (err != 0) {
|
||||
fprintf(stderr, "failed to open package %s: %s\n",
|
||||
package_data, strerror(err));
|
||||
return 3;
|
||||
}
|
||||
|
||||
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
|
||||
if (script_entry == NULL) {
|
||||
fprintf(stderr, "failed to find %s in %s\n", SCRIPT_NAME, package_data);
|
||||
return 4;
|
||||
}
|
||||
|
||||
char* script = malloc(script_entry->uncompLen+1);
|
||||
if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
|
||||
fprintf(stderr, "failed to read script from package\n");
|
||||
return 5;
|
||||
}
|
||||
script[script_entry->uncompLen] = '\0';
|
||||
|
||||
// Configure edify's functions.
|
||||
|
||||
RegisterBuiltins();
|
||||
RegisterInstallFunctions();
|
||||
FinishRegistration();
|
||||
|
||||
// Parse the script.
|
||||
|
||||
Expr* root;
|
||||
int error_count = 0;
|
||||
yy_scan_string(script);
|
||||
int error = yyparse(&root, &error_count);
|
||||
if (error != 0 || error_count > 0) {
|
||||
fprintf(stderr, "%d parse errors\n", error_count);
|
||||
return 6;
|
||||
}
|
||||
|
||||
// Evaluate the parsed script.
|
||||
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.cmd_pipe = cmd_pipe;
|
||||
updater_info.package_zip = &za;
|
||||
|
||||
State state;
|
||||
state.cookie = &updater_info;
|
||||
state.script = script;
|
||||
state.errmsg = NULL;
|
||||
|
||||
char* result = Evaluate(&state, root);
|
||||
if (result == NULL) {
|
||||
if (state.errmsg == NULL) {
|
||||
fprintf(stderr, "script aborted (no error message)\n");
|
||||
fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
|
||||
} else {
|
||||
fprintf(stderr, "script aborted: %s\n", state.errmsg);
|
||||
char* line = strtok(state.errmsg, "\n");
|
||||
while (line) {
|
||||
fprintf(cmd_pipe, "ui_print %s\n", line);
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
fprintf(cmd_pipe, "ui_print\n");
|
||||
}
|
||||
free(state.errmsg);
|
||||
return 7;
|
||||
} else {
|
||||
fprintf(stderr, "script result was [%s]\n", result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
mzCloseZipArchive(&za);
|
||||
free(script);
|
||||
|
||||
return 0;
|
||||
}
|
28
updater/updater.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 _UPDATER_UPDATER_H_
|
||||
#define _UPDATER_UPDATER_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include "minzip/Zip.h"
|
||||
|
||||
typedef struct {
|
||||
FILE* cmd_pipe;
|
||||
ZipArchive* package_zip;
|
||||
} UpdaterInfo;
|
||||
|
||||
#endif
|