1200 lines
32 KiB
C
1200 lines
32 KiB
C
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#undef NDEBUG
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <unistd.h>
|
|
|
|
#include "amend/commands.h"
|
|
#include "commands.h"
|
|
#include "common.h"
|
|
#include "cutils/misc.h"
|
|
#include "cutils/properties.h"
|
|
#include "firmware.h"
|
|
#include "minzip/DirUtil.h"
|
|
#include "minzip/Zip.h"
|
|
#include "roots.h"
|
|
|
|
#include "extendedcommands.h"
|
|
|
|
static int gDidShowProgress = 0;
|
|
|
|
#define UNUSED(p) ((void)(p))
|
|
|
|
#define CHECK_BOOL() \
|
|
do { \
|
|
assert(argv == NULL); \
|
|
if (argv != NULL) return -1; \
|
|
assert(argc == true || argc == false); \
|
|
if (argc != true && argc != false) return -1; \
|
|
} while (false)
|
|
|
|
#define CHECK_WORDS() \
|
|
do { \
|
|
assert(argc >= 0); \
|
|
if (argc < 0) return -1; \
|
|
assert(argc == 0 || argv != NULL); \
|
|
if (argc != 0 && argv == NULL) return -1; \
|
|
if (permissions != NULL) { \
|
|
int CW_I_; \
|
|
for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
|
|
assert(argv[CW_I_] != NULL); \
|
|
if (argv[CW_I_] == NULL) return -1; \
|
|
} \
|
|
} \
|
|
} while (false)
|
|
|
|
#define CHECK_FN() \
|
|
do { \
|
|
CHECK_WORDS(); \
|
|
if (permissions != NULL) { \
|
|
assert(result == NULL); \
|
|
if (result != NULL) return -1; \
|
|
} else { \
|
|
assert(result != NULL); \
|
|
if (result == NULL) return -1; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define NO_PERMS(perms) \
|
|
do { \
|
|
PermissionRequestList *NP_PRL_ = (perms); \
|
|
if (NP_PRL_ != NULL) { \
|
|
int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
|
|
"", false, PERM_NONE); \
|
|
if (NP_RET_ < 0) { \
|
|
/* Returns from the calling function. \
|
|
*/ \
|
|
return NP_RET_; \
|
|
} \
|
|
} \
|
|
} while (false)
|
|
|
|
/*
|
|
* Command definitions
|
|
*/
|
|
|
|
/* assert <boolexpr>
|
|
*/
|
|
static int
|
|
cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_BOOL();
|
|
NO_PERMS(permissions);
|
|
|
|
if (!script_assert_enabled) {
|
|
return 0;
|
|
}
|
|
|
|
/* If our argument is false, return non-zero (failure)
|
|
* If our argument is true, return zero (success)
|
|
*/
|
|
if (argc) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* format <root>
|
|
*/
|
|
static int
|
|
cmd_format(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc != 1) {
|
|
LOGE("Command %s requires exactly one argument\n", name);
|
|
return 1;
|
|
}
|
|
const char *root = argv[0];
|
|
ui_print("Formatting %s...\n", root);
|
|
|
|
int ret = format_root_device(root);
|
|
if (ret != 0) {
|
|
LOGE("Can't format %s\n", root);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* delete <file1> [<fileN> ...]
|
|
* delete_recursive <file-or-dir1> [<file-or-dirN> ...]
|
|
*
|
|
* Like "rm -f", will try to delete every named file/dir, even if
|
|
* earlier ones fail. Recursive deletes that fail halfway through
|
|
* give up early.
|
|
*/
|
|
static int
|
|
cmd_delete(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
int nerr = 0;
|
|
bool recurse;
|
|
|
|
if (argc < 1) {
|
|
LOGE("Command %s requires at least one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
recurse = (strcmp(name, "delete_recursive") == 0);
|
|
ui_print("Deleting files...\n");
|
|
//xxx permissions
|
|
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
const char *root_path = argv[i];
|
|
char pathbuf[PATH_MAX];
|
|
const char *path;
|
|
|
|
/* This guarantees that all paths use "SYSTEM:"-style roots;
|
|
* plain paths won't make it through translate_root_path().
|
|
*/
|
|
path = translate_root_path(root_path, pathbuf, sizeof(pathbuf));
|
|
if (path != NULL) {
|
|
int ret = ensure_root_path_mounted(root_path);
|
|
if (ret < 0) {
|
|
LOGW("Can't mount volume to delete \"%s\"\n", root_path);
|
|
nerr++;
|
|
continue;
|
|
}
|
|
if (recurse) {
|
|
ret = dirUnlinkHierarchy(path);
|
|
} else {
|
|
ret = unlink(path);
|
|
}
|
|
if (ret != 0 && errno != ENOENT) {
|
|
LOGW("Can't delete %s\n(%s)\n", path, strerror(errno));
|
|
nerr++;
|
|
}
|
|
} else {
|
|
nerr++;
|
|
}
|
|
}
|
|
//TODO: add a way to fail if a delete didn't work
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
int num_done;
|
|
int num_total;
|
|
} ExtractContext;
|
|
|
|
static void extract_count_cb(const char *fn, void *cookie)
|
|
{
|
|
++((ExtractContext*) cookie)->num_total;
|
|
}
|
|
|
|
static void extract_cb(const char *fn, void *cookie)
|
|
{
|
|
// minzip writes the filename to the log, so we don't need to
|
|
ExtractContext *ctx = (ExtractContext*) cookie;
|
|
ui_set_progress((float) ++ctx->num_done / ctx->num_total);
|
|
}
|
|
|
|
/* copy_dir <src-dir> <dst-dir> [<timestamp>]
|
|
*
|
|
* The contents of <src-dir> will become the contents of <dst-dir>.
|
|
* The original contents of <dst-dir> are preserved unless something
|
|
* in <src-dir> overwrote them.
|
|
*
|
|
* e.g., for "copy_dir PKG:system SYSTEM:", the file "PKG:system/a"
|
|
* would be copied to "SYSTEM:a".
|
|
*
|
|
* The specified timestamp (in decimal seconds since 1970) will be used,
|
|
* 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)
|
|
{
|
|
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
|
|
if (argc == 3) {
|
|
char *end;
|
|
time_t value = strtoul(argv[2], &end, 0);
|
|
if (value == 0 || end[0] != '\0') {
|
|
LOGE("Command %s: invalid timestamp \"%s\"\n", name, argv[2]);
|
|
return 1;
|
|
} else if (value < timestamp.modtime) {
|
|
LOGE("Command %s: timestamp \"%s\" too early\n", name, argv[2]);
|
|
return 1;
|
|
}
|
|
timestamp.modtime = timestamp.actime = value;
|
|
} else if (argc != 2) {
|
|
LOGE("Command %s requires exactly two arguments\n", name);
|
|
return 1;
|
|
}
|
|
|
|
// Use 40% of the progress bar (80% post-verification) by default
|
|
ui_print("Copying files...\n");
|
|
if (!gDidShowProgress) ui_show_progress(DEFAULT_FILES_PROGRESS_FRACTION, 0);
|
|
|
|
/* Mount the destination volume if it isn't already.
|
|
*/
|
|
const char *dst_root_path = argv[1];
|
|
int ret = ensure_root_path_mounted(dst_root_path);
|
|
if (ret < 0) {
|
|
LOGE("Can't mount %s\n", dst_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Get the real target path.
|
|
*/
|
|
char dstpathbuf[PATH_MAX];
|
|
const char *dst_path;
|
|
dst_path = translate_root_path(dst_root_path,
|
|
dstpathbuf, sizeof(dstpathbuf));
|
|
if (dst_path == NULL) {
|
|
LOGE("Command %s: bad destination path \"%s\"\n", name, dst_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Try to copy the directory. The source may be inside a package.
|
|
*/
|
|
const char *src_root_path = argv[0];
|
|
char srcpathbuf[PATH_MAX];
|
|
const char *src_path;
|
|
if (is_package_root_path(src_root_path)) {
|
|
const ZipArchive *package;
|
|
src_path = translate_package_root_path(src_root_path,
|
|
srcpathbuf, sizeof(srcpathbuf), &package);
|
|
if (src_path == NULL) {
|
|
LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Extract the files. Set MZ_EXTRACT_FILES_ONLY, because only files
|
|
* are validated by the signature. Do a dry run first to count how
|
|
* many there are (and find some errors early).
|
|
*/
|
|
ExtractContext ctx;
|
|
ctx.num_done = 0;
|
|
ctx.num_total = 0;
|
|
|
|
if (!mzExtractRecursive(package, src_path, dst_path,
|
|
MZ_EXTRACT_FILES_ONLY | MZ_EXTRACT_DRY_RUN,
|
|
×tamp, extract_count_cb, (void *) &ctx) ||
|
|
!mzExtractRecursive(package, src_path, dst_path,
|
|
MZ_EXTRACT_FILES_ONLY,
|
|
×tamp, extract_cb, (void *) &ctx)) {
|
|
LOGW("Command %s: couldn't extract \"%s\" to \"%s\"\n",
|
|
name, src_root_path, dst_root_path);
|
|
return 1;
|
|
}
|
|
} else {
|
|
LOGE("Command %s: non-package source path \"%s\" not yet supported\n",
|
|
name, src_root_path);
|
|
//xxx mount the src volume
|
|
//xxx
|
|
return 255;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* run_program <program-file> [<args> ...]
|
|
*
|
|
* 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)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc < 1) {
|
|
LOGE("Command %s requires at least one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
// Copy the program file to temporary storage.
|
|
if (!is_package_root_path(argv[0])) {
|
|
LOGE("Command %s: non-package program file \"%s\" not supported\n",
|
|
name, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
char path[PATH_MAX];
|
|
const ZipArchive *package;
|
|
if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) {
|
|
LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const ZipEntry *entry = mzFindZipEntry(package, path);
|
|
if (entry == NULL) {
|
|
LOGE("Can't find %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
static const char *binary = "/tmp/run_program_binary";
|
|
unlink(binary); // just to be sure
|
|
int fd = creat(binary, 0755);
|
|
if (fd < 0) {
|
|
LOGE("Can't make %s\n", binary);
|
|
return 1;
|
|
}
|
|
bool ok = mzExtractZipEntryToFile(package, entry, fd);
|
|
close(fd);
|
|
|
|
if (!ok) {
|
|
LOGE("Can't copy %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
// Create a copy of argv to NULL-terminate it, as execv requires
|
|
char **args = (char **) malloc(sizeof(char*) * (argc + 1));
|
|
memcpy(args, argv, sizeof(char*) * argc);
|
|
args[argc] = NULL;
|
|
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
execv(binary, args);
|
|
fprintf(stderr, "E:Can't run %s\n(%s)\n", binary, strerror(errno));
|
|
_exit(-1);
|
|
}
|
|
|
|
int status;
|
|
waitpid(pid, &status, 0);
|
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 0 || !script_assert_enabled) {
|
|
return 0;
|
|
} else {
|
|
LOGE("Error in %s\n(Status %d)\n", path, status);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* set_perm <uid> <gid> <mode> <path> [... <pathN>]
|
|
* set_perm_recursive <uid> <gid> <dir-mode> <file-mode> <path> [... <pathN>]
|
|
*
|
|
* Like "chmod", "chown" and "chgrp" all in one, set ownership and permissions
|
|
* of single files or entire directory trees. Any error causes failure.
|
|
* 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)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
bool recurse = !strcmp(name, "set_perm_recursive");
|
|
|
|
int min_args = 4 + (recurse ? 1 : 0);
|
|
if (argc < min_args) {
|
|
LOGE("Command %s requires at least %d args\n", name, min_args);
|
|
return 1;
|
|
}
|
|
|
|
// All the arguments except the path(s) are numeric.
|
|
int i, n[min_args - 1];
|
|
for (i = 0; i < min_args - 1; ++i) {
|
|
char *end;
|
|
n[i] = strtoul(argv[i], &end, 0);
|
|
if (end[0] != '\0' || argv[i][0] == '\0') {
|
|
LOGE("Command %s: invalid argument \"%s\"\n", name, argv[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for (i = min_args - 1; i < min_args; ++i) {
|
|
char path[PATH_MAX];
|
|
if (translate_root_path(argv[i], path, sizeof(path)) == NULL) {
|
|
LOGE("Command %s: bad path \"%s\"\n", name, argv[i]);
|
|
return 1;
|
|
}
|
|
|
|
if (ensure_root_path_mounted(argv[i])) {
|
|
LOGE("Can't mount %s\n", argv[i]);
|
|
return 1;
|
|
}
|
|
|
|
if (recurse
|
|
? dirSetHierarchyPermissions(path, n[0], n[1], n[2], n[3])
|
|
: (chown(path, n[0], n[1]) || chmod(path, n[2]))) {
|
|
LOGE("Can't chown/mod %s\n(%s)\n", path, strerror(errno));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* show_progress <fraction> <duration>
|
|
*
|
|
* Use <fraction> of the on-screen progress meter for the next operation,
|
|
* automatically advancing the meter over <duration> seconds (or more rapidly
|
|
* 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)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc != 2) {
|
|
LOGE("Command %s requires exactly two arguments\n", name);
|
|
return 1;
|
|
}
|
|
|
|
char *end;
|
|
double fraction = strtod(argv[0], &end);
|
|
if (end[0] != '\0' || argv[0][0] == '\0' || fraction < 0 || fraction > 1) {
|
|
LOGE("Command %s: invalid fraction \"%s\"\n", name, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
int duration = strtoul(argv[1], &end, 0);
|
|
if (end[0] != '\0' || argv[0][0] == '\0') {
|
|
LOGE("Command %s: invalid duration \"%s\"\n", name, argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
// Half of the progress bar is taken by verification,
|
|
// so everything that happens during installation is scaled.
|
|
ui_show_progress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), duration);
|
|
gDidShowProgress = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* symlink <link-target> <link-path>
|
|
*
|
|
* Create a symlink, like "ln -s". The link path must not exist already.
|
|
* Note that <link-path> is in root:path format, but <link-target> is
|
|
* for the target filesystem (and may be relative).
|
|
*/
|
|
static int
|
|
cmd_symlink(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc != 2) {
|
|
LOGE("Command %s requires exactly two arguments\n", name);
|
|
return 1;
|
|
}
|
|
|
|
char path[PATH_MAX];
|
|
if (translate_root_path(argv[1], path, sizeof(path)) == NULL) {
|
|
LOGE("Command %s: bad path \"%s\"\n", name, argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
if (ensure_root_path_mounted(argv[1])) {
|
|
LOGE("Can't mount %s\n", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
if (symlink(argv[0], path)) {
|
|
LOGE("Can't symlink %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct FirmwareContext {
|
|
size_t total_bytes, done_bytes;
|
|
char *data;
|
|
};
|
|
|
|
static bool firmware_fn(const unsigned char *data, int data_len, void *cookie)
|
|
{
|
|
struct FirmwareContext *context = (struct FirmwareContext*) cookie;
|
|
if (context->done_bytes + data_len > context->total_bytes) {
|
|
LOGE("Data overrun in firmware\n");
|
|
return false; // Should not happen, but let's be safe.
|
|
}
|
|
|
|
memcpy(context->data + context->done_bytes, data, data_len);
|
|
context->done_bytes += data_len;
|
|
ui_set_progress(context->done_bytes * 1.0 / context->total_bytes);
|
|
return true;
|
|
}
|
|
|
|
/* write_radio_image <src-image>
|
|
* write_hboot_image <src-image>
|
|
* Doesn't actually take effect until the rest of installation finishes.
|
|
*/
|
|
static int
|
|
cmd_write_firmware_image(const char *name, void *cookie,
|
|
int argc, const char *argv[], PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc != 1) {
|
|
LOGE("Command %s requires exactly one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
const char *type;
|
|
if (!strcmp(name, "write_radio_image")) {
|
|
type = "radio";
|
|
} else if (!strcmp(name, "write_hboot_image")) {
|
|
type = "hboot";
|
|
} else {
|
|
LOGE("Unknown firmware update command %s\n", name);
|
|
return 1;
|
|
}
|
|
|
|
if (!is_package_root_path(argv[0])) {
|
|
LOGE("Command %s: non-package image file \"%s\" not supported\n",
|
|
name, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
ui_print("Extracting %s image...\n", type);
|
|
char path[PATH_MAX];
|
|
const ZipArchive *package;
|
|
if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) {
|
|
LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const ZipEntry *entry = mzFindZipEntry(package, path);
|
|
if (entry == NULL) {
|
|
LOGE("Can't find %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
// Load the update image into RAM.
|
|
struct FirmwareContext context;
|
|
context.total_bytes = mzGetZipEntryUncompLen(entry);
|
|
context.done_bytes = 0;
|
|
context.data = malloc(context.total_bytes);
|
|
if (context.data == NULL) {
|
|
LOGE("Can't allocate %d bytes for %s\n", context.total_bytes, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (!mzProcessZipEntryContents(package, entry, firmware_fn, &context) ||
|
|
context.done_bytes != context.total_bytes) {
|
|
LOGE("Can't read %s\n", argv[0]);
|
|
free(context.data);
|
|
return 1;
|
|
}
|
|
|
|
if (remember_firmware_update(type, context.data, context.total_bytes)) {
|
|
LOGE("Can't store %s image\n", type);
|
|
free(context.data);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool write_raw_image_process_fn(
|
|
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;
|
|
LOGE("%s\n", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
/* write_raw_image <src-image> <dest-root>
|
|
*/
|
|
static int
|
|
cmd_write_raw_image(const char *name, void *cookie,
|
|
int argc, const char *argv[], PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
//xxx permissions
|
|
|
|
if (argc != 2) {
|
|
LOGE("Command %s requires exactly two arguments\n", name);
|
|
return 1;
|
|
}
|
|
|
|
// Use 10% of the progress bar (20% post-verification) by default
|
|
const char *src_root_path = argv[0];
|
|
const char *dst_root_path = argv[1];
|
|
ui_print("Writing %s...\n", dst_root_path);
|
|
if (!gDidShowProgress) ui_show_progress(DEFAULT_IMAGE_PROGRESS_FRACTION, 0);
|
|
|
|
/* Find the source image, which is probably in a package.
|
|
*/
|
|
if (!is_package_root_path(src_root_path)) {
|
|
LOGE("Command %s: non-package source path \"%s\" not yet supported\n",
|
|
name, src_root_path);
|
|
return 255;
|
|
}
|
|
|
|
/* Get the package.
|
|
*/
|
|
char srcpathbuf[PATH_MAX];
|
|
const char *src_path;
|
|
const ZipArchive *package;
|
|
src_path = translate_package_root_path(src_root_path,
|
|
srcpathbuf, sizeof(srcpathbuf), &package);
|
|
if (src_path == NULL) {
|
|
LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Get the entry.
|
|
*/
|
|
const ZipEntry *entry = mzFindZipEntry(package, src_path);
|
|
if (entry == NULL) {
|
|
LOGE("Missing file %s\n", src_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Unmount the destination root if it isn't already.
|
|
*/
|
|
int ret = ensure_root_path_unmounted(dst_root_path);
|
|
if (ret < 0) {
|
|
LOGE("Can't unmount %s\n", dst_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Open the partition for writing.
|
|
*/
|
|
const MtdPartition *partition = get_root_mtd_partition(dst_root_path);
|
|
if (partition == NULL) {
|
|
LOGE("Can't find %s\n", dst_root_path);
|
|
return 1;
|
|
}
|
|
MtdWriteContext *context = mtd_write_partition(partition);
|
|
if (context == NULL) {
|
|
LOGE("Can't open %s\n", dst_root_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Extract and write the image.
|
|
*/
|
|
bool ok = mzProcessZipEntryContents(package, entry,
|
|
write_raw_image_process_fn, context);
|
|
if (!ok) {
|
|
LOGE("Error writing %s\n", dst_root_path);
|
|
mtd_write_close(context);
|
|
return 1;
|
|
}
|
|
|
|
if (mtd_erase_blocks(context, -1) == (off_t) -1) {
|
|
LOGE("Error finishing %s\n", dst_root_path);
|
|
mtd_write_close(context);
|
|
return -1;
|
|
}
|
|
|
|
if (mtd_write_close(context)) {
|
|
LOGE("Error closing %s\n", dst_root_path);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* mark <resource> dirty|clean
|
|
*/
|
|
static int
|
|
cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
//xxx when marking, save the top-level hash at the mark point
|
|
// so we can retry on failure. Otherwise the hashes won't match,
|
|
// or someone could intentionally dirty the FS to force a downgrade
|
|
//xxx
|
|
return -1;
|
|
}
|
|
|
|
/* done
|
|
*/
|
|
static int
|
|
cmd_done(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
//xxx
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
cmd_backup_rom(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
char* backup_name = NULL;
|
|
switch(argc)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
backup_name = argv[0];
|
|
break;
|
|
default:
|
|
LOGE("Command %s requires zero or one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
return do_nandroid_backup(backup_name);
|
|
}
|
|
|
|
static int
|
|
cmd_restore_rom(const char *name, void *cookie, int argc, const char *argv[],
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_WORDS();
|
|
|
|
if (argc != 1) {
|
|
LOGE("Command %s requires exactly one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
return do_nandroid_restore(argv[0]);
|
|
}
|
|
|
|
|
|
/*
|
|
* Function definitions
|
|
*/
|
|
|
|
/* compatible_with(<version>)
|
|
*
|
|
* Returns "true" if this version of the script parser and command
|
|
* set supports the named version.
|
|
*/
|
|
static int
|
|
fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc != 1) {
|
|
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
|
name, argc);
|
|
return 1;
|
|
}
|
|
|
|
if (!strcmp(argv[0], "0.1") || !strcmp(argv[0], "0.2")) {
|
|
*result = strdup("true");
|
|
} else {
|
|
*result = strdup("");
|
|
}
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* update_forced()
|
|
*
|
|
* Returns "true" if some system setting has determined that
|
|
* the update should happen no matter what.
|
|
*/
|
|
static int
|
|
fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc != 0) {
|
|
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
|
name, argc);
|
|
return 1;
|
|
}
|
|
|
|
//xxx check some global or property
|
|
bool force = true;
|
|
if (force) {
|
|
*result = strdup("true");
|
|
} else {
|
|
*result = strdup("");
|
|
}
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* get_mark(<resource>)
|
|
*
|
|
* Returns the current mark associated with the provided resource.
|
|
*/
|
|
static int
|
|
fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc != 1) {
|
|
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
|
name, argc);
|
|
return 1;
|
|
}
|
|
|
|
//xxx look up the value
|
|
*result = strdup("");
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* hash_dir(<path-to-directory>)
|
|
*/
|
|
static int
|
|
fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
int ret = -1;
|
|
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
|
|
const char *dir;
|
|
if (argc != 1) {
|
|
fprintf(stderr, "%s: wrong number of arguments (%d)\n",
|
|
name, argc);
|
|
return 1;
|
|
} else {
|
|
dir = argv[0];
|
|
}
|
|
|
|
if (permissions != NULL) {
|
|
if (dir == NULL) {
|
|
/* The argument is the result of another function.
|
|
* Assume the worst case, where the function returns
|
|
* the root.
|
|
*/
|
|
dir = "/";
|
|
}
|
|
ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
|
|
} else {
|
|
//xxx build and return the string
|
|
*result = strdup("hashvalue");
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* matches(<str>, <str1> [, <strN>...])
|
|
* If <str> matches (strcmp) any of <str1>...<strN>, returns <str>,
|
|
* otherwise returns "".
|
|
*
|
|
* E.g., assert matches(hash_dir("/path"), "hash1", "hash2")
|
|
*/
|
|
static int
|
|
fn_matches(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc < 2) {
|
|
fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
|
|
name, argc);
|
|
return 1;
|
|
}
|
|
|
|
int i;
|
|
for (i = 1; i < argc; i++) {
|
|
if (strcmp(argv[0], argv[i]) == 0) {
|
|
*result = strdup(argv[0]);
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
*result = strdup("");
|
|
if (resultLen != NULL) {
|
|
*resultLen = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* concat(<str>, <str1> [, <strN>...])
|
|
* Returns the concatenation of all strings.
|
|
*/
|
|
static int
|
|
fn_concat(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(name);
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
size_t totalLen = 0;
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
totalLen += strlen(argv[i]);
|
|
}
|
|
|
|
char *s = (char *)malloc(totalLen + 1);
|
|
if (s == NULL) {
|
|
return -1;
|
|
}
|
|
s[totalLen] = '\0';
|
|
for (i = 0; i < argc; i++) {
|
|
//TODO: keep track of the end to avoid walking the string each time
|
|
strcat(s, argv[i]);
|
|
}
|
|
*result = s;
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(s);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* getprop(<property>)
|
|
* Returns the named Android system property value, or "" if not set.
|
|
*/
|
|
static int
|
|
fn_getprop(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc != 1) {
|
|
LOGE("Command %s requires exactly one argument\n", name);
|
|
return 1;
|
|
}
|
|
|
|
char value[PROPERTY_VALUE_MAX];
|
|
property_get(argv[0], value, "");
|
|
|
|
*result = strdup(value);
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* file_contains(<filename>, <substring>)
|
|
* Returns "true" if the file exists and contains the specified substring.
|
|
*/
|
|
static int
|
|
fn_file_contains(const char *name, void *cookie, int argc, const char *argv[],
|
|
char **result, size_t *resultLen,
|
|
PermissionRequestList *permissions)
|
|
{
|
|
UNUSED(cookie);
|
|
CHECK_FN();
|
|
NO_PERMS(permissions);
|
|
|
|
if (argc != 2) {
|
|
LOGE("Command %s requires exactly two arguments\n", name);
|
|
return 1;
|
|
}
|
|
|
|
char pathbuf[PATH_MAX];
|
|
const char *root_path = argv[0];
|
|
const char *path = translate_root_path(root_path, pathbuf, sizeof(pathbuf));
|
|
if (path == NULL) {
|
|
LOGE("Command %s: bad path \"%s\"\n", name, root_path);
|
|
return 1;
|
|
}
|
|
|
|
if (ensure_root_path_mounted(root_path)) {
|
|
LOGE("Can't mount %s\n", root_path);
|
|
return 1;
|
|
}
|
|
|
|
const char *needle = argv[1];
|
|
char *haystack = (char*) load_file(path, NULL);
|
|
if (haystack == NULL) {
|
|
LOGI("%s: Can't read \"%s\" (%s)\n", name, path, strerror(errno));
|
|
*result = ""; /* File not found is not an error. */
|
|
} else if (strstr(haystack, needle) == NULL) {
|
|
LOGI("%s: Can't find \"%s\" in \"%s\"\n", name, needle, path);
|
|
*result = strdup("");
|
|
free(haystack);
|
|
} else {
|
|
*result = strdup("true");
|
|
free(haystack);
|
|
}
|
|
|
|
if (resultLen != NULL) {
|
|
*resultLen = strlen(*result);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
register_update_commands(RecoveryCommandContext *ctx)
|
|
{
|
|
int ret;
|
|
|
|
ret = commandInit();
|
|
if (ret < 0) return ret;
|
|
|
|
/*
|
|
* Commands
|
|
*/
|
|
|
|
ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("delete", CMD_ARGS_WORDS, cmd_delete, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("delete_recursive", CMD_ARGS_WORDS, cmd_delete,
|
|
(void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("copy_dir", CMD_ARGS_WORDS,
|
|
cmd_copy_dir, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("run_program", CMD_ARGS_WORDS,
|
|
cmd_run_program, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("set_perm", CMD_ARGS_WORDS,
|
|
cmd_set_perm, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("set_perm_recursive", CMD_ARGS_WORDS,
|
|
cmd_set_perm, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("show_progress", CMD_ARGS_WORDS,
|
|
cmd_show_progress, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("symlink", CMD_ARGS_WORDS, cmd_symlink, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("write_radio_image", CMD_ARGS_WORDS,
|
|
cmd_write_firmware_image, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("write_hboot_image", CMD_ARGS_WORDS,
|
|
cmd_write_firmware_image, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("write_raw_image", CMD_ARGS_WORDS,
|
|
cmd_write_raw_image, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("backup_rom", CMD_ARGS_WORDS, cmd_backup_rom, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerCommand("restore_rom", CMD_ARGS_WORDS, cmd_restore_rom, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
/*
|
|
* Functions
|
|
*/
|
|
|
|
ret = registerFunction("compatible_with", fn_compatible_with, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("update_forced", fn_update_forced, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("get_mark", fn_get_mark, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("hash_dir", fn_hash_dir, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("matches", fn_matches, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("concat", fn_concat, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("getprop", fn_getprop, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
ret = registerFunction("file_contains", fn_file_contains, (void *)ctx);
|
|
if (ret < 0) return ret;
|
|
|
|
return 0;
|
|
}
|