edify extensions for OTA package installation, part 1

Adds the following edify functions:

  mount unmount format show_progress delete delete_recursive
  package_extract symlink set_perm set_perm_recursive

This set is enough to extract and install the system part of a (full)
OTA package.

Adds the updater binary that extracts an edify script from the OTA
package and then executes it.  Minor changes to the edify core (adds a
sleep() builtin for debugging, adds "." to the set of characters that
can appear in an unquoted string).
This commit is contained in:
Doug Zongker 2009-06-10 14:11:53 -07:00
parent 9b9c2114bd
commit 9931f7f3c1
13 changed files with 694 additions and 13 deletions

View file

@ -44,4 +44,6 @@ 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 :=

View file

@ -26,15 +26,14 @@ LOCAL_YACCFLAGS := -v
include $(BUILD_HOST_EXECUTABLE)
# #
# # Build the device-side library
# #
# include $(CLEAR_VARS)
#
# Build the device-side library
#
include $(CLEAR_VARS)
# LOCAL_SRC_FILES := $(edify_src_files)
# LOCAL_SRC_FILES += $(edify_test_files)
LOCAL_SRC_FILES := $(edify_src_files)
# LOCAL_CFLAGS := $(edify_cflags)
# LOCAL_MODULE := libedify
LOCAL_CFLAGS := $(edify_cflags)
LOCAL_MODULE := libedify
# include $(BUILD_STATIC_LIBRARY)
include $(BUILD_STATIC_LIBRARY)

View file

@ -10,7 +10,7 @@ the old one ("amend"). This is a brief overview of the new language.
understood, as are hexadecimal escapes like \x4a.
- String literals consisting of only letters, numbers, colons,
underscores, and slashes don't need to be in double quotes.
underscores, slashes, and periods don't need to be in double quotes.
- The following words are reserved:

View file

@ -19,6 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include "expr.h"
@ -92,6 +93,12 @@ char* IfElseFn(const char* name, void* cookie, int argc, Expr* argv[]) {
}
char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]) {
char* msg = NULL;
if (argc > 0) {
msg = Evaluate(cookie, argv[0]);
}
SetError(msg == NULL ? "called abort()" : msg);
free(msg);
return NULL;
}
@ -105,12 +112,23 @@ char* AssertFn(const char* name, void* cookie, int argc, Expr* argv[]) {
int b = BooleanString(v);
free(v);
if (!b) {
SetError("assert() failed");
return NULL;
}
}
return strdup("");
}
char* SleepFn(const char* name, void* cookie, int argc, Expr* argv[]) {
char* val = Evaluate(cookie, argv[0]);
if (val == NULL) {
return NULL;
}
int v = strtol(val, NULL, 10);
sleep(v);
return val;
}
char* PrintFn(const char* name, void* cookie, int argc, Expr* argv[]) {
int i;
for (i = 0; i < argc; ++i) {
@ -234,6 +252,32 @@ Expr* Build(Function fn, int count, ...) {
return e;
}
// -----------------------------------------------------------------
// error reporting
// -----------------------------------------------------------------
static char* error_message = NULL;
void SetError(const char* message) {
if (error_message) {
free(error_message);
}
error_message = strdup(message);
}
const char* GetError() {
return error_message;
}
void ClearError() {
free(error_message);
error_message = NULL;
}
// -----------------------------------------------------------------
// the function table
// -----------------------------------------------------------------
static int fn_entries = 0;
static int fn_size = 0;
NamedFunction* fn_table = NULL;
@ -276,4 +320,55 @@ void RegisterBuiltins() {
RegisterFunction("concat", ConcatFn);
RegisterFunction("is_substring", SubstringFn);
RegisterFunction("print", PrintFn);
RegisterFunction("sleep", SleepFn);
}
// -----------------------------------------------------------------
// 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(void* cookie, 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(cookie, 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(void* cookie, int argc, Expr* argv[]) {
char** args = (char**)malloc(argc * sizeof(char*));
int i = 0;
for (i = 0; i < argc; ++i) {
args[i] = Evaluate(cookie, argv[i]);
if (args[i] == NULL) {
int j;
for (j = 0; j < i; ++j) {
free(args[j]);
}
free(args);
return NULL;
}
}
return args;
}

View file

@ -57,6 +57,14 @@ char* IfElseFn(const char* name, void* cookie, int argc, Expr* argv[]);
char* AssertFn(const char* name, void* cookie, int argc, Expr* argv[]);
char* AbortFn(const char* name, void* cookie, 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;
@ -77,4 +85,19 @@ void FinishRegistration();
// 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(void* cookie, 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(void* cookie, int argc, Expr* argv[]);
#endif // _EXPRESSION_H

View file

@ -77,7 +77,7 @@ then { gColumn += yyleng; return THEN; }
else { gColumn += yyleng; return ELSE; }
endif { gColumn += yyleng; return ENDIF; }
[a-zA-Z0-9_:/]+ {
[a-zA-Z0-9_:/.]+ {
gColumn += yyleng;
yylval.str = strdup(yytext);
return STRING;

View file

@ -158,7 +158,14 @@ int main(int argc, char** argv) {
printf("parse returned %d\n", error);
if (error == 0) {
char* result = Evaluate(NULL, root);
printf("result is [%s]\n", result == NULL ? "(NULL)" : result);
if (result == NULL) {
char* errmsg = GetError();
printf("result was NULL, message is: %s\n",
(errmsg == NULL ? "(NULL)" : errmsg));
ClearError();
} else {
printf("result is [%s]\n", result);
}
}
return 0;
}

View file

@ -256,7 +256,7 @@ try_update_binary(const char *path, ZipArchive *zip) {
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE("Error in %s\n(Status %d)\n", path, status);
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
}

24
updater/Android.mk Normal file
View file

@ -0,0 +1,24 @@
# Copyright 2009 The Android Open Source Project
LOCAL_PATH := $(call my-dir)
updater_src_files := \
install.c \
updater.c
#
# Build the device-side library
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(updater_src_files)
LOCAL_STATIC_LIBRARIES := libedify libmtdutils libminzip libz
LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
LOCAL_MODULE := updater
LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_EXECUTABLE)

370
updater/install.c Normal file
View file

@ -0,0 +1,370 @@
/*
* 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 <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "edify/expr.h"
#include "minzip/DirUtil.h"
#include "mtdutils/mounts.h"
#include "mtdutils/mtdutils.h"
#include "updater.h"
char* ErrorAbort(void* cookie, char* format, ...) {
char* buffer = malloc(4096);
va_list v;
va_start(v, format);
vsnprintf(buffer, 4096, format, v);
va_end(v);
SetError(buffer);
return NULL;
}
// 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, void* cookie, int argc, Expr* argv[]) {
char* result = NULL;
if (argc != 3) {
return ErrorAbort(cookie, "%s() expects 3 args, got %d", name, argc);
}
char* type;
char* location;
char* mount_point;
if (ReadArgs(cookie, argv, 3, &type, &location, &mount_point) < 0) {
return NULL;
}
if (strlen(type) == 0) {
ErrorAbort(cookie, "type argument to %s() can't be empty", name);
goto done;
}
if (strlen(location) == 0) {
ErrorAbort(cookie, "location argument to %s() can't be empty", name);
goto done;
}
if (strlen(mount_point) == 0) {
ErrorAbort(cookie, "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;
}
char* UnmountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
char* result = NULL;
if (argc != 1) {
return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
}
char* mount_point;
if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
return NULL;
}
if (strlen(mount_point) == 0) {
ErrorAbort(cookie, "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, void* cookie, int argc, Expr* argv[]) {
char* result = NULL;
if (argc != 2) {
return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
}
char* type;
char* location;
if (ReadArgs(cookie, argv, 2, &type, &location) < 0) {
return NULL;
}
if (strlen(type) == 0) {
ErrorAbort(cookie, "type argument to %s() can't be empty", name);
goto done;
}
if (strlen(location) == 0) {
ErrorAbort(cookie, "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, void* cookie, int argc, Expr* argv[]) {
char** paths = malloc(argc * sizeof(char*));
int i;
for (i = 0; i < argc; ++i) {
paths[i] = Evaluate(cookie, 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, void* cookie, int argc, Expr* argv[]) {
if (argc != 2) {
return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
}
char* frac_str;
char* sec_str;
if (ReadArgs(cookie, 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*)cookie;
fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
free(frac_str);
free(sec_str);
return strdup("");
}
// package_extract package_path destination_path
char* PackageExtractFn(const char* name, void* cookie, int argc, Expr* argv[]) {
if (argc != 2) {
return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
}
char* zip_path;
char* dest_path;
if (ReadArgs(cookie, argv, 2, &zip_path, &dest_path) < 0) return NULL;
ZipArchive* za = ((UpdaterInfo*)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, &timestamp,
NULL, NULL);
free(zip_path);
free(dest_path);
return strdup(success ? "t" : "");
}
// symlink target src1 src2 ...
char* SymlinkFn(const char* name, void* cookie, int argc, Expr* argv[]) {
if (argc == 0) {
return ErrorAbort(cookie, "%s() expects 1+ args, got %d", name, argc);
}
char* target;
target = Evaluate(cookie, argv[0]);
if (target == NULL) return NULL;
char** srcs = ReadVarArgs(cookie, 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, void* cookie, 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(cookie, "%s() expects %d+ args, got %d", name, argc);
}
char** args = ReadVarArgs(cookie, 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(cookie, "%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(cookie, "%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(cookie, "%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(cookie, "%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(cookie, "%s: \"%s\" not a valid mode", name, args[2]);
goto done;
}
for (i = 4; 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;
}
void RegisterInstallFunctions() {
RegisterFunction("mount", MountFn);
RegisterFunction("unmount", UnmountFn);
RegisterFunction("format", FormatFn);
RegisterFunction("show_progress", ShowProgressFn);
RegisterFunction("delete", DeleteFn);
RegisterFunction("delete_recursive", DeleteFn);
RegisterFunction("package_extract", PackageExtractFn);
RegisterFunction("symlink", SymlinkFn);
RegisterFunction("set_perm", SetPermFn);
RegisterFunction("set_perm_recursive", SetPermFn);
}

22
updater/install.h Normal file
View 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

111
updater/updater.c Normal file
View file

@ -0,0 +1,111 @@
/*
* 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[1] != '\0') {
fprintf(stderr, "wrong updater binary API; expected 1, got %s\n",
version);
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;
yy_scan_string(script);
int error = yyparse(&root);
if (error != 0) {
fprintf(stderr, "%d parse errors\n", error);
return 6;
}
// Evaluate the parsed script.
UpdaterInfo updater_info;
updater_info.cmd_pipe = cmd_pipe;
updater_info.package_zip = &za;
char* result = Evaluate(&updater_info, root);
if (result == NULL) {
const char* errmsg = GetError();
fprintf(stderr, "script aborted with error: %s\n",
errmsg == NULL ? "(none)" : errmsg);
ClearError();
return 7;
} else {
fprintf(stderr, "script result was [%s]\n", result);
free(result);
}
mzCloseZipArchive(&za);
return 0;
}

28
updater/updater.h Normal file
View 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