diff --git a/Android.mk b/Android.mk index 8c1de736..bfb1bedc 100644 --- a/Android.mk +++ b/Android.mk @@ -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 := diff --git a/edify/Android.mk b/edify/Android.mk index 803fba20..fac0ba71 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -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) diff --git a/edify/README b/edify/README index 5ccb582e..810455cc 100644 --- a/edify/README +++ b/edify/README @@ -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: diff --git a/edify/expr.c b/edify/expr.c index b3b89271..129fbd96 100644 --- a/edify/expr.c +++ b/edify/expr.c @@ -19,6 +19,7 @@ #include #include #include +#include #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; } diff --git a/edify/expr.h b/edify/expr.h index ac5df186..cfbef903 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -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 diff --git a/edify/lexer.l b/edify/lexer.l index 4faef5da..cb5eb318 100644 --- a/edify/lexer.l +++ b/edify/lexer.l @@ -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; diff --git a/edify/main.c b/edify/main.c index 4d65da2d..c9596837 100644 --- a/edify/main.c +++ b/edify/main.c @@ -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; } diff --git a/install.c b/install.c index eff9312b..0b5c04da 100644 --- a/install.c +++ b/install.c @@ -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; } diff --git a/updater/Android.mk b/updater/Android.mk new file mode 100644 index 00000000..2716d486 --- /dev/null +++ b/updater/Android.mk @@ -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) diff --git a/updater/install.c b/updater/install.c new file mode 100644 index 00000000..2336f614 --- /dev/null +++ b/updater/install.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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="" to mount a yaffs2 filesystem +// type="vfat" location="/dev/block/" 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, ×tamp, + 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); +} diff --git a/updater/install.h b/updater/install.h new file mode 100644 index 00000000..94f344f8 --- /dev/null +++ b/updater/install.h @@ -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 diff --git a/updater/updater.c b/updater/updater.c new file mode 100644 index 00000000..aa03803a --- /dev/null +++ b/updater/updater.c @@ -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 +#include +#include + +#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; +} diff --git a/updater/updater.h b/updater/updater.h new file mode 100644 index 00000000..22fbfd28 --- /dev/null +++ b/updater/updater.h @@ -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 +#include "minzip/Zip.h" + +typedef struct { + FILE* cmd_pipe; + ZipArchive* package_zip; +} UpdaterInfo; + +#endif