diff options
Diffstat (limited to 'cmds')
41 files changed, 9508 insertions, 22 deletions
diff --git a/cmds/atrace/Android.mk b/cmds/atrace/Android.mk new file mode 100644 index 0000000..028ca8f --- /dev/null +++ b/cmds/atrace/Android.mk @@ -0,0 +1,20 @@ +# Copyright 2012 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= atrace.cpp + +LOCAL_C_INCLUDES += external/zlib + +LOCAL_MODULE:= atrace + +LOCAL_MODULE_TAGS:= optional + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libcutils \ + libutils \ + libz \ + +include $(BUILD_EXECUTABLE) diff --git a/cmds/atrace/MODULE_LICENSE_APACHE2 b/cmds/atrace/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cmds/atrace/MODULE_LICENSE_APACHE2 diff --git a/cmds/atrace/NOTICE b/cmds/atrace/NOTICE new file mode 100644 index 0000000..c77f135 --- /dev/null +++ b/cmds/atrace/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2012, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp new file mode 100644 index 0000000..76ba81f --- /dev/null +++ b/cmds/atrace/atrace.cpp @@ -0,0 +1,891 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/sendfile.h> +#include <time.h> +#include <zlib.h> + +#include <binder/IBinder.h> +#include <binder/IServiceManager.h> +#include <binder/Parcel.h> + +#include <cutils/properties.h> + +#include <utils/String8.h> +#include <utils/Trace.h> + +using namespace android; + +#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) + +enum { MAX_SYS_FILES = 8 }; + +const char* k_traceTagsProperty = "debug.atrace.tags.enableflags"; +const char* k_traceAppCmdlineProperty = "debug.atrace.app_cmdlines"; + +typedef enum { OPT, REQ } requiredness ; + +struct TracingCategory { + // The name identifying the category. + const char* name; + + // A longer description of the category. + const char* longname; + + // The userland tracing tags that the category enables. + uint64_t tags; + + // The fname==NULL terminated list of /sys/ files that the category + // enables. + struct { + // Whether the file must be writable in order to enable the tracing + // category. + requiredness required; + + // The path to the enable file. + const char* path; + } sysfiles[MAX_SYS_FILES]; +}; + +/* Tracing categories */ +static const TracingCategory k_categories[] = { + { "gfx", "Graphics", ATRACE_TAG_GRAPHICS, { } }, + { "input", "Input", ATRACE_TAG_INPUT, { } }, + { "view", "View System", ATRACE_TAG_VIEW, { } }, + { "webview", "WebView", ATRACE_TAG_WEBVIEW, { } }, + { "wm", "Window Manager", ATRACE_TAG_WINDOW_MANAGER, { } }, + { "am", "Activity Manager", ATRACE_TAG_ACTIVITY_MANAGER, { } }, + { "audio", "Audio", ATRACE_TAG_AUDIO, { } }, + { "video", "Video", ATRACE_TAG_VIDEO, { } }, + { "camera", "Camera", ATRACE_TAG_CAMERA, { } }, + { "hal", "Hardware Modules", ATRACE_TAG_HAL, { } }, + { "sched", "CPU Scheduling", 0, { + { REQ, "/sys/kernel/debug/tracing/events/sched/sched_switch/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/sched/sched_wakeup/enable" }, + } }, + { "freq", "CPU Frequency", 0, { + { REQ, "/sys/kernel/debug/tracing/events/power/cpu_frequency/enable" }, + { OPT, "/sys/kernel/debug/tracing/events/power/clock_set_rate/enable" }, + } }, + { "membus", "Memory Bus Utilization", 0, { + { REQ, "/sys/kernel/debug/tracing/events/memory_bus/enable" }, + } }, + { "idle", "CPU Idle", 0, { + { REQ, "/sys/kernel/debug/tracing/events/power/cpu_idle/enable" }, + } }, + { "disk", "Disk I/O", 0, { + { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_complete/enable" }, + } }, + { "load", "CPU Load", 0, { + { REQ, "/sys/kernel/debug/tracing/events/cpufreq_interactive/enable" }, + } }, + { "sync", "Synchronization", 0, { + { REQ, "/sys/kernel/debug/tracing/events/sync/enable" }, + } }, + { "workq", "Kernel Workqueues", 0, { + { REQ, "/sys/kernel/debug/tracing/events/workqueue/enable" }, + } }, +}; + +/* Command line options */ +static int g_traceDurationSeconds = 5; +static bool g_traceOverwrite = false; +static int g_traceBufferSizeKB = 2048; +static bool g_compress = false; +static bool g_nohup = false; +static int g_initialSleepSecs = 0; +static const char* g_kernelTraceFuncs = NULL; +static const char* g_debugAppCmdLine = ""; + +/* Global state */ +static bool g_traceAborted = false; +static bool g_categoryEnables[NELEM(k_categories)] = {}; + +/* Sys file paths */ +static const char* k_traceClockPath = + "/sys/kernel/debug/tracing/trace_clock"; + +static const char* k_traceBufferSizePath = + "/sys/kernel/debug/tracing/buffer_size_kb"; + +static const char* k_tracingOverwriteEnablePath = + "/sys/kernel/debug/tracing/options/overwrite"; + +static const char* k_currentTracerPath = + "/sys/kernel/debug/tracing/current_tracer"; + +static const char* k_printTgidPath = + "/sys/kernel/debug/tracing/options/print-tgid"; + +static const char* k_funcgraphAbsTimePath = + "/sys/kernel/debug/tracing/options/funcgraph-abstime"; + +static const char* k_funcgraphCpuPath = + "/sys/kernel/debug/tracing/options/funcgraph-cpu"; + +static const char* k_funcgraphProcPath = + "/sys/kernel/debug/tracing/options/funcgraph-proc"; + +static const char* k_funcgraphFlatPath = + "/sys/kernel/debug/tracing/options/funcgraph-flat"; + +static const char* k_funcgraphDurationPath = + "/sys/kernel/debug/tracing/options/funcgraph-duration"; + +static const char* k_ftraceFilterPath = + "/sys/kernel/debug/tracing/set_ftrace_filter"; + +static const char* k_tracingOnPath = + "/sys/kernel/debug/tracing/tracing_on"; + +static const char* k_tracePath = + "/sys/kernel/debug/tracing/trace"; + +// Check whether a file exists. +static bool fileExists(const char* filename) { + return access(filename, F_OK) != -1; +} + +// Check whether a file is writable. +static bool fileIsWritable(const char* filename) { + return access(filename, W_OK) != -1; +} + +// Truncate a file. +static bool truncateFile(const char* path) +{ + // This uses creat rather than truncate because some of the debug kernel + // device nodes (e.g. k_ftraceFilterPath) currently aren't changed by + // calls to truncate, but they are cleared by calls to creat. + int traceFD = creat(path, 0); + if (traceFD == -1) { + fprintf(stderr, "error truncating %s: %s (%d)\n", path, + strerror(errno), errno); + return false; + } + + close(traceFD); + + return true; +} + +static bool _writeStr(const char* filename, const char* str, int flags) +{ + int fd = open(filename, flags); + if (fd == -1) { + fprintf(stderr, "error opening %s: %s (%d)\n", filename, + strerror(errno), errno); + return false; + } + + bool ok = true; + ssize_t len = strlen(str); + if (write(fd, str, len) != len) { + fprintf(stderr, "error writing to %s: %s (%d)\n", filename, + strerror(errno), errno); + ok = false; + } + + close(fd); + + return ok; +} + +// Write a string to a file, returning true if the write was successful. +static bool writeStr(const char* filename, const char* str) +{ + return _writeStr(filename, str, O_WRONLY); +} + +// Append a string to a file, returning true if the write was successful. +static bool appendStr(const char* filename, const char* str) +{ + return _writeStr(filename, str, O_APPEND|O_WRONLY); +} + +// Enable or disable a kernel option by writing a "1" or a "0" into a /sys +// file. +static bool setKernelOptionEnable(const char* filename, bool enable) +{ + return writeStr(filename, enable ? "1" : "0"); +} + +// Check whether the category is supported on the device with the current +// rootness. A category is supported only if all its required /sys/ files are +// writable and if enabling the category will enable one or more tracing tags +// or /sys/ files. +static bool isCategorySupported(const TracingCategory& category) +{ + bool ok = category.tags != 0; + for (int i = 0; i < MAX_SYS_FILES; i++) { + const char* path = category.sysfiles[i].path; + bool req = category.sysfiles[i].required == REQ; + if (path != NULL) { + if (req) { + if (!fileIsWritable(path)) { + return false; + } else { + ok = true; + } + } else { + ok |= fileIsWritable(path); + } + } + } + return ok; +} + +// Check whether the category would be supported on the device if the user +// were root. This function assumes that root is able to write to any file +// that exists. It performs the same logic as isCategorySupported, but it +// uses file existance rather than writability in the /sys/ file checks. +static bool isCategorySupportedForRoot(const TracingCategory& category) +{ + bool ok = category.tags != 0; + for (int i = 0; i < MAX_SYS_FILES; i++) { + const char* path = category.sysfiles[i].path; + bool req = category.sysfiles[i].required == REQ; + if (path != NULL) { + if (req) { + if (!fileExists(path)) { + return false; + } else { + ok = true; + } + } else { + ok |= fileExists(path); + } + } + } + return ok; +} + +// Enable or disable overwriting of the kernel trace buffers. Disabling this +// will cause tracing to stop once the trace buffers have filled up. +static bool setTraceOverwriteEnable(bool enable) +{ + return setKernelOptionEnable(k_tracingOverwriteEnablePath, enable); +} + +// Enable or disable kernel tracing. +static bool setTracingEnabled(bool enable) +{ + return setKernelOptionEnable(k_tracingOnPath, enable); +} + +// Clear the contents of the kernel trace. +static bool clearTrace() +{ + return truncateFile(k_tracePath); +} + +// Set the size of the kernel's trace buffer in kilobytes. +static bool setTraceBufferSizeKB(int size) +{ + char str[32] = "1"; + int len; + if (size < 1) { + size = 1; + } + snprintf(str, 32, "%d", size); + return writeStr(k_traceBufferSizePath, str); +} + +// Enable or disable the kernel's use of the global clock. Disabling the global +// clock will result in the kernel using a per-CPU local clock. +static bool setGlobalClockEnable(bool enable) +{ + return writeStr(k_traceClockPath, enable ? "global" : "local"); +} + +static bool setPrintTgidEnableIfPresent(bool enable) +{ + if (fileExists(k_printTgidPath)) { + return setKernelOptionEnable(k_printTgidPath, enable); + } + return true; +} + +// Poke all the binder-enabled processes in the system to get them to re-read +// their system properties. +static bool pokeBinderServices() +{ + sp<IServiceManager> sm = defaultServiceManager(); + Vector<String16> services = sm->listServices(); + for (size_t i = 0; i < services.size(); i++) { + sp<IBinder> obj = sm->checkService(services[i]); + if (obj != NULL) { + Parcel data; + if (obj->transact(IBinder::SYSPROPS_TRANSACTION, data, + NULL, 0) != OK) { + if (false) { + // XXX: For some reason this fails on tablets trying to + // poke the "phone" service. It's not clear whether some + // are expected to fail. + String8 svc(services[i]); + fprintf(stderr, "error poking binder service %s\n", + svc.string()); + return false; + } + } + } + } + return true; +} + +// Set the trace tags that userland tracing uses, and poke the running +// processes to pick up the new value. +static bool setTagsProperty(uint64_t tags) +{ + char buf[64]; + snprintf(buf, 64, "%#llx", tags); + if (property_set(k_traceTagsProperty, buf) < 0) { + fprintf(stderr, "error setting trace tags system property\n"); + return false; + } + return true; +} + +// Set the system property that indicates which apps should perform +// application-level tracing. +static bool setAppCmdlineProperty(const char* cmdline) +{ + if (property_set(k_traceAppCmdlineProperty, cmdline) < 0) { + fprintf(stderr, "error setting trace app system property\n"); + return false; + } + return true; +} + +// Disable all /sys/ enable files. +static bool disableKernelTraceEvents() { + bool ok = true; + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory &c = k_categories[i]; + for (int j = 0; j < MAX_SYS_FILES; j++) { + const char* path = c.sysfiles[j].path; + if (path != NULL && fileIsWritable(path)) { + ok &= setKernelOptionEnable(path, false); + } + } + } + return ok; +} + +// Verify that the comma separated list of functions are being traced by the +// kernel. +static bool verifyKernelTraceFuncs(const char* funcs) +{ + int fd = open(k_ftraceFilterPath, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "error opening %s: %s (%d)\n", k_ftraceFilterPath, + strerror(errno), errno); + return false; + } + + char buf[4097]; + ssize_t n = read(fd, buf, 4096); + close(fd); + if (n == -1) { + fprintf(stderr, "error reading %s: %s (%d)\n", k_ftraceFilterPath, + strerror(errno), errno); + return false; + } + + buf[n] = '\0'; + String8 funcList = String8::format("\n%s", buf); + + // Make sure that every function listed in funcs is in the list we just + // read from the kernel. + bool ok = true; + char* myFuncs = strdup(funcs); + char* func = strtok(myFuncs, ","); + while (func) { + String8 fancyFunc = String8::format("\n%s\n", func); + bool found = funcList.find(fancyFunc.string(), 0) >= 0; + if (!found || func[0] == '\0') { + fprintf(stderr, "error: \"%s\" is not a valid kernel function " + "to trace.\n", func); + ok = false; + } + func = strtok(NULL, ","); + } + free(myFuncs); + + return ok; +} + +// Set the comma separated list of functions that the kernel is to trace. +static bool setKernelTraceFuncs(const char* funcs) +{ + bool ok = true; + + if (funcs == NULL || funcs[0] == '\0') { + // Disable kernel function tracing. + if (fileIsWritable(k_currentTracerPath)) { + ok &= writeStr(k_currentTracerPath, "nop"); + } + if (fileIsWritable(k_ftraceFilterPath)) { + ok &= truncateFile(k_ftraceFilterPath); + } + } else { + // Enable kernel function tracing. + ok &= writeStr(k_currentTracerPath, "function_graph"); + ok &= setKernelOptionEnable(k_funcgraphAbsTimePath, true); + ok &= setKernelOptionEnable(k_funcgraphCpuPath, true); + ok &= setKernelOptionEnable(k_funcgraphProcPath, true); + ok &= setKernelOptionEnable(k_funcgraphFlatPath, true); + + // Set the requested filter functions. + ok &= truncateFile(k_ftraceFilterPath); + char* myFuncs = strdup(funcs); + char* func = strtok(myFuncs, ","); + while (func) { + ok &= appendStr(k_ftraceFilterPath, func); + func = strtok(NULL, ","); + } + free(myFuncs); + + // Verify that the set functions are being traced. + if (ok) { + ok &= verifyKernelTraceFuncs(funcs); + } + } + + return ok; +} + +// Set all the kernel tracing settings to the desired state for this trace +// capture. +static bool setUpTrace() +{ + bool ok = true; + + // Set up the tracing options. + ok &= setTraceOverwriteEnable(g_traceOverwrite); + ok &= setTraceBufferSizeKB(g_traceBufferSizeKB); + ok &= setGlobalClockEnable(true); + ok &= setPrintTgidEnableIfPresent(true); + ok &= setKernelTraceFuncs(g_kernelTraceFuncs); + + // Set up the tags property. + uint64_t tags = 0; + for (int i = 0; i < NELEM(k_categories); i++) { + if (g_categoryEnables[i]) { + const TracingCategory &c = k_categories[i]; + tags |= c.tags; + } + } + ok &= setTagsProperty(tags); + ok &= setAppCmdlineProperty(g_debugAppCmdLine); + ok &= pokeBinderServices(); + + // Disable all the sysfs enables. This is done as a separate loop from + // the enables to allow the same enable to exist in multiple categories. + ok &= disableKernelTraceEvents(); + + // Enable all the sysfs enables that are in an enabled category. + for (int i = 0; i < NELEM(k_categories); i++) { + if (g_categoryEnables[i]) { + const TracingCategory &c = k_categories[i]; + for (int j = 0; j < MAX_SYS_FILES; j++) { + const char* path = c.sysfiles[j].path; + bool required = c.sysfiles[j].required == REQ; + if (path != NULL) { + if (fileIsWritable(path)) { + ok &= setKernelOptionEnable(path, true); + } else if (required) { + fprintf(stderr, "error writing file %s\n", path); + ok = false; + } + } + } + } + } + + return ok; +} + +// Reset all the kernel tracing settings to their default state. +static void cleanUpTrace() +{ + // Disable all tracing that we're able to. + disableKernelTraceEvents(); + + // Reset the system properties. + setTagsProperty(0); + setAppCmdlineProperty(""); + pokeBinderServices(); + + // Set the options back to their defaults. + setTraceOverwriteEnable(true); + setTraceBufferSizeKB(1); + setGlobalClockEnable(false); + setPrintTgidEnableIfPresent(false); + setKernelTraceFuncs(NULL); +} + + +// Enable tracing in the kernel. +static bool startTrace() +{ + return setTracingEnabled(true); +} + +// Disable tracing in the kernel. +static void stopTrace() +{ + setTracingEnabled(false); +} + +// Read the current kernel trace and write it to stdout. +static void dumpTrace() +{ + int traceFD = open(k_tracePath, O_RDWR); + if (traceFD == -1) { + fprintf(stderr, "error opening %s: %s (%d)\n", k_tracePath, + strerror(errno), errno); + return; + } + + if (g_compress) { + z_stream zs; + uint8_t *in, *out; + int result, flush; + + bzero(&zs, sizeof(zs)); + result = deflateInit(&zs, Z_DEFAULT_COMPRESSION); + if (result != Z_OK) { + fprintf(stderr, "error initializing zlib: %d\n", result); + close(traceFD); + return; + } + + const size_t bufSize = 64*1024; + in = (uint8_t*)malloc(bufSize); + out = (uint8_t*)malloc(bufSize); + flush = Z_NO_FLUSH; + + zs.next_out = out; + zs.avail_out = bufSize; + + do { + + if (zs.avail_in == 0) { + // More input is needed. + result = read(traceFD, in, bufSize); + if (result < 0) { + fprintf(stderr, "error reading trace: %s (%d)\n", + strerror(errno), errno); + result = Z_STREAM_END; + break; + } else if (result == 0) { + flush = Z_FINISH; + } else { + zs.next_in = in; + zs.avail_in = result; + } + } + + if (zs.avail_out == 0) { + // Need to write the output. + result = write(STDOUT_FILENO, out, bufSize); + if ((size_t)result < bufSize) { + fprintf(stderr, "error writing deflated trace: %s (%d)\n", + strerror(errno), errno); + result = Z_STREAM_END; // skip deflate error message + zs.avail_out = bufSize; // skip the final write + break; + } + zs.next_out = out; + zs.avail_out = bufSize; + } + + } while ((result = deflate(&zs, flush)) == Z_OK); + + if (result != Z_STREAM_END) { + fprintf(stderr, "error deflating trace: %s\n", zs.msg); + } + + if (zs.avail_out < bufSize) { + size_t bytes = bufSize - zs.avail_out; + result = write(STDOUT_FILENO, out, bytes); + if ((size_t)result < bytes) { + fprintf(stderr, "error writing deflated trace: %s (%d)\n", + strerror(errno), errno); + } + } + + result = deflateEnd(&zs); + if (result != Z_OK) { + fprintf(stderr, "error cleaning up zlib: %d\n", result); + } + + free(in); + free(out); + } else { + ssize_t sent = 0; + while ((sent = sendfile(STDOUT_FILENO, traceFD, NULL, 64*1024*1024)) > 0); + if (sent == -1) { + fprintf(stderr, "error dumping trace: %s (%d)\n", strerror(errno), + errno); + } + } + + close(traceFD); +} + +static void handleSignal(int signo) +{ + if (!g_nohup) { + g_traceAborted = true; + } +} + +static void registerSigHandler() +{ + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handleSignal; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); +} + +static bool setCategoryEnable(const char* name, bool enable) +{ + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory& c = k_categories[i]; + if (strcmp(name, c.name) == 0) { + if (isCategorySupported(c)) { + g_categoryEnables[i] = enable; + return true; + } else { + if (isCategorySupportedForRoot(c)) { + fprintf(stderr, "error: category \"%s\" requires root " + "privileges.\n", name); + } else { + fprintf(stderr, "error: category \"%s\" is not supported " + "on this device.\n", name); + } + return false; + } + } + } + fprintf(stderr, "error: unknown tracing category \"%s\"\n", name); + return false; +} + +static void listSupportedCategories() +{ + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory& c = k_categories[i]; + if (isCategorySupported(c)) { + printf(" %10s - %s\n", c.name, c.longname); + } + } +} + +// Print the command usage help to stderr. +static void showHelp(const char *cmd) +{ + fprintf(stderr, "usage: %s [options] [categories...]\n", cmd); + fprintf(stderr, "options include:\n" + " -a appname enable app-level tracing for a comma " + "separated list of cmdlines\n" + " -b N use a trace buffer size of N KB\n" + " -c trace into a circular buffer\n" + " -k fname,... trace the listed kernel functions\n" + " -n ignore signals\n" + " -s N sleep for N seconds before tracing [default 0]\n" + " -t N trace for N seconds [defualt 5]\n" + " -z compress the trace dump\n" + " --async_start start circular trace and return immediatly\n" + " --async_dump dump the current contents of circular trace buffer\n" + " --async_stop stop tracing and dump the current contents of circular\n" + " trace buffer\n" + " --list_categories\n" + " list the available tracing categories\n" + ); +} + +int main(int argc, char **argv) +{ + bool async = false; + bool traceStart = true; + bool traceStop = true; + bool traceDump = true; + + if (argc == 2 && 0 == strcmp(argv[1], "--help")) { + showHelp(argv[0]); + exit(0); + } + + for (;;) { + int ret; + int option_index = 0; + static struct option long_options[] = { + {"async_start", no_argument, 0, 0 }, + {"async_stop", no_argument, 0, 0 }, + {"async_dump", no_argument, 0, 0 }, + {"list_categories", no_argument, 0, 0 }, + { 0, 0, 0, 0 } + }; + + ret = getopt_long(argc, argv, "a:b:ck:ns:t:z", + long_options, &option_index); + + if (ret < 0) { + for (int i = optind; i < argc; i++) { + if (!setCategoryEnable(argv[i], true)) { + fprintf(stderr, "error enabling tracing category \"%s\"\n", argv[i]); + exit(1); + } + } + break; + } + + switch(ret) { + case 'a': + g_debugAppCmdLine = optarg; + break; + + case 'b': + g_traceBufferSizeKB = atoi(optarg); + break; + + case 'c': + g_traceOverwrite = true; + break; + + case 'k': + g_kernelTraceFuncs = optarg; + break; + + case 'n': + g_nohup = true; + break; + + case 's': + g_initialSleepSecs = atoi(optarg); + break; + + case 't': + g_traceDurationSeconds = atoi(optarg); + break; + + case 'z': + g_compress = true; + break; + + case 0: + if (!strcmp(long_options[option_index].name, "async_start")) { + async = true; + traceStop = false; + traceDump = false; + g_traceOverwrite = true; + } else if (!strcmp(long_options[option_index].name, "async_stop")) { + async = true; + traceStop = false; + } else if (!strcmp(long_options[option_index].name, "async_dump")) { + async = true; + traceStart = false; + traceStop = false; + } else if (!strcmp(long_options[option_index].name, "list_categories")) { + listSupportedCategories(); + exit(0); + } + break; + + default: + fprintf(stderr, "\n"); + showHelp(argv[0]); + exit(-1); + break; + } + } + + registerSigHandler(); + + if (g_initialSleepSecs > 0) { + sleep(g_initialSleepSecs); + } + + bool ok = true; + ok &= setUpTrace(); + ok &= startTrace(); + + if (ok && traceStart) { + printf("capturing trace..."); + fflush(stdout); + + // We clear the trace after starting it because tracing gets enabled for + // each CPU individually in the kernel. Having the beginning of the trace + // contain entries from only one CPU can cause "begin" entries without a + // matching "end" entry to show up if a task gets migrated from one CPU to + // another. + ok = clearTrace(); + + if (ok && !async) { + // Sleep to allow the trace to be captured. + struct timespec timeLeft; + timeLeft.tv_sec = g_traceDurationSeconds; + timeLeft.tv_nsec = 0; + do { + if (g_traceAborted) { + break; + } + } while (nanosleep(&timeLeft, &timeLeft) == -1 && errno == EINTR); + } + } + + // Stop the trace and restore the default settings. + if (traceStop) + stopTrace(); + + if (ok && traceDump) { + if (!g_traceAborted) { + printf(" done\nTRACE:\n"); + fflush(stdout); + dumpTrace(); + } else { + printf("\ntrace aborted.\n"); + fflush(stdout); + } + clearTrace(); + } else if (!ok) { + fprintf(stderr, "unable to start tracing\n"); + } + + // Reset the trace buffer size to 1. + if (traceStop) + cleanUpTrace(); + + return g_traceAborted ? 1 : 0; +} diff --git a/cmds/bugreport/Android.mk b/cmds/bugreport/Android.mk new file mode 100644 index 0000000..f476f5e --- /dev/null +++ b/cmds/bugreport/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= bugreport.c + +LOCAL_MODULE:= bugreport + +LOCAL_SHARED_LIBRARIES := libcutils + +include $(BUILD_EXECUTABLE) diff --git a/cmds/bugreport/bugreport.c b/cmds/bugreport/bugreport.c new file mode 100644 index 0000000..4a0b511 --- /dev/null +++ b/cmds/bugreport/bugreport.c @@ -0,0 +1,56 @@ +/* + * 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 <unistd.h> + +#include <cutils/properties.h> +#include <cutils/sockets.h> + +int main(int argc, char *argv[]) { + char buffer[65536]; + int i, s; + + /* start the dumpstate service */ + property_set("ctl.start", "dumpstate"); + + /* socket will not be available until service starts */ + for (i = 0; i < 10; i++) { + s = socket_local_client("dumpstate", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + if (s >= 0) + break; + /* try again in 1 second */ + sleep(1); + } + + if (s < 0) { + fprintf(stderr, "Failed to connect to dumpstate service\n"); + exit(1); + } + + while (1) { + int length = read(s, buffer, sizeof(buffer)); + if (length <= 0) + break; + fwrite(buffer, 1, length, stdout); + } + + close(s); + return 0; +} diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index 55a36c2..9a332a9 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -101,8 +101,14 @@ static void dumpstate() { run_command("LIST OF OPEN FILES", 10, SU_PATH, "root", "lsof", NULL); + if (screenshot_path[0]) { + ALOGI("taking screenshot\n"); + run_command(NULL, 10, "/system/bin/screencap", "-p", screenshot_path, NULL); + ALOGI("wrote screenshot: %s\n", screenshot_path); + } + for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES"); - for_each_pid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS"); + for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS"); // dump_file("EVENT LOG TAGS", "/etc/event-log-tags"); run_command("SYSTEM LOG", 20, "logcat", "-v", "threadtime", "-d", "*:v", NULL); @@ -159,12 +165,6 @@ static void dumpstate() { dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console"); dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads"); - if (screenshot_path[0]) { - ALOGI("taking screenshot\n"); - run_command(NULL, 5, SU_PATH, "root", "screenshot", screenshot_path, NULL); - ALOGI("wrote screenshot: %s\n", screenshot_path); - } - run_command("SYSTEM SETTINGS", 20, SU_PATH, "root", "sqlite3", "/data/data/com.android.providers.settings/databases/settings.db", "pragma user_version; select * from system; select * from secure; select * from global;", NULL); @@ -196,13 +196,13 @@ static void dumpstate() { property_get("dhcp.wlan0.gateway", network, ""); if (network[0]) - run_command("PING GATEWAY", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL); + run_command("PING GATEWAY", 10, "ping", "-c", "3", "-i", ".5", network, NULL); property_get("dhcp.wlan0.dns1", network, ""); if (network[0]) - run_command("PING DNS1", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL); + run_command("PING DNS1", 10, "ping", "-c", "3", "-i", ".5", network, NULL); property_get("dhcp.wlan0.dns2", network, ""); if (network[0]) - run_command("PING DNS2", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL); + run_command("PING DNS2", 10, "ping", "-c", "3", "-i", ".5", network, NULL); #ifdef FWDUMP_bcmdhd run_command("DUMP WIFI STATUS", 20, SU_PATH, "root", "dhdutil", "-i", "wlan0", "dump", NULL); @@ -216,7 +216,7 @@ static void dumpstate() { run_command("VOLD DUMP", 10, "vdc", "dump", NULL); run_command("SECURE CONTAINERS", 10, "vdc", "asec", "list", NULL); - run_command("FILESYSTEMS & FREE SPACE", 10, SU_PATH, "root", "df", NULL); + run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL); run_command("PACKAGE SETTINGS", 20, SU_PATH, "root", "cat", "/data/system/packages.xml", NULL); dump_file("PACKAGE UID ERRORS", "/data/system/uiderrors.txt"); @@ -311,6 +311,7 @@ static void usage() { " -b: play sound file instead of vibrate, at beginning of job\n" " -e: play sound file instead of vibrate, at end of job\n" " -q: disable vibrate\n" + " -B: send broadcast when finished (requires -o and -p)\n" ); } @@ -323,6 +324,7 @@ int main(int argc, char *argv[]) { char* end_sound = 0; int use_socket = 0; int do_fb = 0; + int do_broadcast = 0; if (getuid() != 0) { // Old versions of the adb client would call the @@ -348,7 +350,7 @@ int main(int argc, char *argv[]) { dump_traces_path = dump_traces(); int c; - while ((c = getopt(argc, argv, "b:de:ho:svqzp")) != -1) { + while ((c = getopt(argc, argv, "b:de:ho:svqzpB")) != -1) { switch (c) { case 'b': begin_sound = optarg; break; case 'd': do_add_date = 1; break; @@ -359,6 +361,7 @@ int main(int argc, char *argv[]) { case 'q': do_vibrate = 0; break; case 'z': do_compress = 6; break; case 'p': do_fb = 1; break; + case 'B': do_broadcast = 1; break; case '?': printf("\n"); case 'h': usage(); @@ -474,6 +477,14 @@ int main(int argc, char *argv[]) { fprintf(stderr, "rename(%s, %s): %s\n", tmp_path, path, strerror(errno)); } + if (do_broadcast && use_outfile && do_fb) { + run_command(NULL, 5, "/system/bin/am", "broadcast", "--user", "0", + "-a", "android.intent.action.BUGREPORT_FINISHED", + "--es", "android.intent.extra.BUGREPORT", path, + "--es", "android.intent.extra.SCREENSHOT", screenshot_path, + "--receiver-permission", "android.permission.DUMP", NULL); + } + ALOGI("done\n"); return 0; diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 45247cd..67bbd7e 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -19,10 +19,14 @@ #include <time.h> #include <unistd.h> +#include <stdbool.h> #include <stdio.h> #define SU_PATH "/system/xbin/su" +typedef void (for_each_pid_func)(int, const char *); +typedef void (for_each_tid_func)(int, int, const char *); + /* prints the contents of a file */ int dump_file(const char *title, const char* path); @@ -42,10 +46,13 @@ pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level); const char *dump_traces(); /* for each process in the system, run the specified function */ -void for_each_pid(void (*func)(int, const char *), const char *header); +void for_each_pid(for_each_pid_func func, const char *header); + +/* for each thread in the system, run the specified function */ +void for_each_tid(for_each_tid_func func, const char *header); /* Displays a blocked processes in-kernel wait channel */ -void show_wchan(int pid, const char *name); +void show_wchan(int pid, int tid, const char *name); /* Runs "showmap" for a process */ void do_showmap(int pid, const char *name); diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c index 5b8ed49..fe716ac 100644 --- a/cmds/dumpstate/utils.c +++ b/cmds/dumpstate/utils.c @@ -51,7 +51,7 @@ static const char* native_processes_to_dump[] = { NULL, }; -void for_each_pid(void (*func)(int, const char *), const char *header) { +static void __for_each_pid(void (*helper)(int, const char *, void *), const char *header, void *arg) { DIR *d; struct dirent *de; @@ -76,23 +76,85 @@ void for_each_pid(void (*func)(int, const char *), const char *header) { if ((fd = open(cmdpath, O_RDONLY)) < 0) { strcpy(cmdline, "N/A"); } else { - read(fd, cmdline, sizeof(cmdline)); + read(fd, cmdline, sizeof(cmdline) - 1); close(fd); } - func(pid, cmdline); + helper(pid, cmdline, arg); } closedir(d); } -void show_wchan(int pid, const char *name) { +static void for_each_pid_helper(int pid, const char *cmdline, void *arg) { + for_each_pid_func *func = arg; + func(pid, cmdline); +} + +void for_each_pid(for_each_pid_func func, const char *header) { + __for_each_pid(for_each_pid_helper, header, func); +} + +static void for_each_tid_helper(int pid, const char *cmdline, void *arg) { + DIR *d; + struct dirent *de; + char taskpath[255]; + for_each_tid_func *func = arg; + + sprintf(taskpath, "/proc/%d/task", pid); + + if (!(d = opendir(taskpath))) { + printf("Failed to open %s (%s)\n", taskpath, strerror(errno)); + return; + } + + func(pid, pid, cmdline); + + while ((de = readdir(d))) { + int tid; + int fd; + char commpath[255]; + char comm[255]; + + if (!(tid = atoi(de->d_name))) { + continue; + } + + if (tid == pid) + continue; + + sprintf(commpath,"/proc/%d/comm", tid); + memset(comm, 0, sizeof(comm)); + if ((fd = open(commpath, O_RDONLY)) < 0) { + strcpy(comm, "N/A"); + } else { + char *c; + read(fd, comm, sizeof(comm) - 1); + close(fd); + + c = strrchr(comm, '\n'); + if (c) { + *c = '\0'; + } + } + func(pid, tid, comm); + } + + closedir(d); +} + +void for_each_tid(for_each_tid_func func, const char *header) { + __for_each_pid(for_each_tid_helper, header, func); +} + +void show_wchan(int pid, int tid, const char *name) { char path[255]; char buffer[255]; int fd; + char name_buffer[255]; memset(buffer, 0, sizeof(buffer)); - sprintf(path, "/proc/%d/wchan", pid); + sprintf(path, "/proc/%d/wchan", tid); if ((fd = open(path, O_RDONLY)) < 0) { printf("Failed to open '%s' (%s)\n", path, strerror(errno)); return; @@ -103,7 +165,10 @@ void show_wchan(int pid, const char *name) { goto out_close; } - printf("%-7d %-32s %s\n", pid, name, buffer); + snprintf(name_buffer, sizeof(name_buffer), "%*s%s", + pid == tid ? 0 : 3, "", name); + + printf("%-7d %-32s %s\n", tid, name_buffer, buffer); out_close: close(fd); @@ -316,7 +381,7 @@ pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level) { chp = strchr(chp, '/'); if (chp) { *chp = 0; - mkdir(path, 0775); /* drwxrwxr-x */ + mkdir(path, 0770); /* drwxrwx--- */ *chp++ = '/'; } } diff --git a/cmds/flatland/Android.mk b/cmds/flatland/Android.mk new file mode 100644 index 0000000..5e57f02 --- /dev/null +++ b/cmds/flatland/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Composers.cpp \ + GLHelper.cpp \ + Renderers.cpp \ + Main.cpp \ + +LOCAL_MODULE:= flatland + +LOCAL_MODULE_TAGS := tests + +LOCAL_SHARED_LIBRARIES := \ + libEGL \ + libGLESv2 \ + libcutils \ + libgui \ + libui \ + libutils \ + +include $(BUILD_EXECUTABLE) diff --git a/cmds/flatland/Composers.cpp b/cmds/flatland/Composers.cpp new file mode 100644 index 0000000..8365a31 --- /dev/null +++ b/cmds/flatland/Composers.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012 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 "Flatland.h" +#include "GLHelper.h" + +namespace android { + +class Blitter { +public: + + bool setUp(GLHelper* helper) { + bool result; + + result = helper->getShaderProgram("Blit", &mBlitPgm); + if (!result) { + return false; + } + + mPosAttribLoc = glGetAttribLocation(mBlitPgm, "position"); + mUVAttribLoc = glGetAttribLocation(mBlitPgm, "uv"); + mUVToTexUniformLoc = glGetUniformLocation(mBlitPgm, "uvToTex"); + mObjToNdcUniformLoc = glGetUniformLocation(mBlitPgm, "objToNdc"); + mBlitSrcSamplerLoc = glGetUniformLocation(mBlitPgm, "blitSrc"); + mModColorUniformLoc = glGetUniformLocation(mBlitPgm, "modColor"); + + return true; + } + + bool blit(GLuint texName, const float* texMatrix, + int32_t x, int32_t y, uint32_t w, uint32_t h) { + float modColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + return modBlit(texName, texMatrix, modColor, x, y, w, h); + } + + bool modBlit(GLuint texName, const float* texMatrix, float* modColor, + int32_t x, int32_t y, uint32_t w, uint32_t h) { + glUseProgram(mBlitPgm); + + GLint vp[4]; + glGetIntegerv(GL_VIEWPORT, vp); + float screenToNdc[16] = { + 2.0f/float(vp[2]), 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f/float(vp[3]), 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + }; + const float pos[] = { + float(x), float(y), + float(x+w), float(y), + float(x), float(y+h), + float(x+w), float(y+h), + }; + const float uv[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + + glVertexAttribPointer(mPosAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, pos); + glVertexAttribPointer(mUVAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, uv); + glEnableVertexAttribArray(mPosAttribLoc); + glEnableVertexAttribArray(mUVAttribLoc); + + glUniformMatrix4fv(mObjToNdcUniformLoc, 1, GL_FALSE, screenToNdc); + glUniformMatrix4fv(mUVToTexUniformLoc, 1, GL_FALSE, texMatrix); + glUniform4fv(mModColorUniformLoc, 1, modColor); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texName); + glUniform1i(mBlitSrcSamplerLoc, 0); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(mPosAttribLoc); + glDisableVertexAttribArray(mUVAttribLoc); + + if (glGetError() != GL_NO_ERROR) { + fprintf(stderr, "GL error!\n"); + } + + return true; + } + +private: + GLuint mBlitPgm; + GLint mPosAttribLoc; + GLint mUVAttribLoc; + GLint mUVToTexUniformLoc; + GLint mObjToNdcUniformLoc; + GLint mBlitSrcSamplerLoc; + GLint mModColorUniformLoc; +}; + +class ComposerBase : public Composer { +public: + virtual ~ComposerBase() {} + + virtual bool setUp(const LayerDesc& desc, + GLHelper* helper) { + mLayerDesc = desc; + return setUp(helper); + } + + virtual void tearDown() { + } + + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) { + return true; + } + +protected: + virtual bool setUp(GLHelper* helper) { + return true; + } + + LayerDesc mLayerDesc; +}; + +Composer* nocomp() { + class NoComp : public ComposerBase { + }; + return new NoComp(); +} + +Composer* opaque() { + class OpaqueComp : public ComposerBase { + virtual bool setUp(GLHelper* helper) { + return mBlitter.setUp(helper); + } + + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) { + float texMatrix[16]; + glc->getTransformMatrix(texMatrix); + + int32_t x = mLayerDesc.x; + int32_t y = mLayerDesc.y; + int32_t w = mLayerDesc.width; + int32_t h = mLayerDesc.height; + + return mBlitter.blit(texName, texMatrix, x, y, w, h); + } + + Blitter mBlitter; + }; + return new OpaqueComp(); +} + +Composer* opaqueShrink() { + class OpaqueComp : public ComposerBase { + virtual bool setUp(GLHelper* helper) { + mParity = false; + return mBlitter.setUp(helper); + } + + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) { + float texMatrix[16]; + glc->getTransformMatrix(texMatrix); + + int32_t x = mLayerDesc.x; + int32_t y = mLayerDesc.y; + int32_t w = mLayerDesc.width; + int32_t h = mLayerDesc.height; + + mParity = !mParity; + if (mParity) { + x += w / 128; + y += h / 128; + w -= w / 64; + h -= h / 64; + } + + return mBlitter.blit(texName, texMatrix, x, y, w, h); + } + + Blitter mBlitter; + bool mParity; + }; + return new OpaqueComp(); +} + +Composer* blend() { + class BlendComp : public ComposerBase { + virtual bool setUp(GLHelper* helper) { + return mBlitter.setUp(helper); + } + + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) { + bool result; + + float texMatrix[16]; + glc->getTransformMatrix(texMatrix); + + float modColor[4] = { .75f, .75f, .75f, .75f }; + + int32_t x = mLayerDesc.x; + int32_t y = mLayerDesc.y; + int32_t w = mLayerDesc.width; + int32_t h = mLayerDesc.height; + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + result = mBlitter.modBlit(texName, texMatrix, modColor, + x, y, w, h); + if (!result) { + return false; + } + + glDisable(GL_BLEND); + + return true; + } + + Blitter mBlitter; + }; + return new BlendComp(); +} + +Composer* blendShrink() { + class BlendShrinkComp : public ComposerBase { + virtual bool setUp(GLHelper* helper) { + mParity = false; + return mBlitter.setUp(helper); + } + + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) { + bool result; + + float texMatrix[16]; + glc->getTransformMatrix(texMatrix); + + float modColor[4] = { .75f, .75f, .75f, .75f }; + + int32_t x = mLayerDesc.x; + int32_t y = mLayerDesc.y; + int32_t w = mLayerDesc.width; + int32_t h = mLayerDesc.height; + + mParity = !mParity; + if (mParity) { + x += w / 128; + y += h / 128; + w -= w / 64; + h -= h / 64; + } + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + result = mBlitter.modBlit(texName, texMatrix, modColor, + x, y, w, h); + if (!result) { + return false; + } + + glDisable(GL_BLEND); + + return true; + } + + Blitter mBlitter; + bool mParity; + }; + return new BlendShrinkComp(); +} + +} // namespace android diff --git a/cmds/flatland/Flatland.h b/cmds/flatland/Flatland.h new file mode 100644 index 0000000..fd26ad3 --- /dev/null +++ b/cmds/flatland/Flatland.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 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 <stdint.h> + +#include <EGL/egl.h> +#include <GLES2/gl2.h> + +#include <gui/GLConsumer.h> + +namespace android { + +#define NELEMS(x) ((int) (sizeof(x) / sizeof((x)[0]))) + +enum { MAX_NUM_LAYERS = 16 }; +enum { MAX_TEST_RUNS = 16 }; + +class Composer; +class Renderer; +class GLHelper; + +struct LayerDesc { + uint32_t flags; + Renderer* (*rendererFactory)(); + Composer* (*composerFactory)(); + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; +}; + +void resetColorGenerator(); + +class Composer { +public: + virtual ~Composer() {} + virtual bool setUp(const LayerDesc& desc, GLHelper* helper) = 0; + virtual void tearDown() = 0; + virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) = 0; +}; + +Composer* nocomp(); +Composer* opaque(); +Composer* opaqueShrink(); +Composer* blend(); +Composer* blendShrink(); + +class Renderer { +public: + virtual ~Renderer() {} + virtual bool setUp(GLHelper* helper) = 0; + virtual void tearDown() = 0; + virtual bool render(EGLSurface surface) = 0; +}; + +Renderer* staticGradient(); + +} // namespace android diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp new file mode 100644 index 0000000..4f7697f --- /dev/null +++ b/cmds/flatland/GLHelper.cpp @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2012 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 <ui/DisplayInfo.h> +#include <gui/SurfaceComposerClient.h> + +#include "GLHelper.h" + + namespace android { + +GLHelper::GLHelper() : + mGraphicBufferAlloc(new GraphicBufferAlloc()), + mDisplay(EGL_NO_DISPLAY), + mContext(EGL_NO_CONTEXT), + mDummySurface(EGL_NO_SURFACE), + mConfig(0), + mShaderPrograms(NULL), + mDitherTexture(0) { +} + +GLHelper::~GLHelper() { +} + +bool GLHelper::setUp(const ShaderDesc* shaderDescs, size_t numShaders) { + bool result; + + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay == EGL_NO_DISPLAY) { + fprintf(stderr, "eglGetDisplay error: %#x\n", eglGetError()); + return false; + } + + EGLint majorVersion; + EGLint minorVersion; + result = eglInitialize(mDisplay, &majorVersion, &minorVersion); + if (result != EGL_TRUE) { + fprintf(stderr, "eglInitialize error: %#x\n", eglGetError()); + return false; + } + + EGLint numConfigs = 0; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + result = eglChooseConfig(mDisplay, configAttribs, &mConfig, 1, + &numConfigs); + if (result != EGL_TRUE) { + fprintf(stderr, "eglChooseConfig error: %#x\n", eglGetError()); + return false; + } + + EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + mContext = eglCreateContext(mDisplay, mConfig, EGL_NO_CONTEXT, + contextAttribs); + if (mContext == EGL_NO_CONTEXT) { + fprintf(stderr, "eglCreateContext error: %#x\n", eglGetError()); + return false; + } + + bool resultb = createNamedSurfaceTexture(0, 1, 1, &mDummyGLConsumer, + &mDummySurface); + if (!resultb) { + return false; + } + + resultb = makeCurrent(mDummySurface); + if (!resultb) { + return false; + } + + resultb = setUpShaders(shaderDescs, numShaders); + if (!resultb) { + return false; + } + + return true; +} + +void GLHelper::tearDown() { + if (mShaderPrograms != NULL) { + delete[] mShaderPrograms; + mShaderPrograms = NULL; + } + + if (mSurfaceComposerClient != NULL) { + mSurfaceComposerClient->dispose(); + mSurfaceComposerClient.clear(); + } + + if (mDisplay != EGL_NO_DISPLAY) { + eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + + if (mContext != EGL_NO_CONTEXT) { + eglDestroyContext(mDisplay, mContext); + } + + if (mDummySurface != EGL_NO_SURFACE) { + eglDestroySurface(mDisplay, mDummySurface); + } + + mDisplay = EGL_NO_DISPLAY; + mContext = EGL_NO_CONTEXT; + mDummySurface = EGL_NO_SURFACE; + mDummyGLConsumer.clear(); + mConfig = 0; +} + +bool GLHelper::makeCurrent(EGLSurface surface) { + EGLint result; + + result = eglMakeCurrent(mDisplay, surface, surface, mContext); + if (result != EGL_TRUE) { + fprintf(stderr, "eglMakeCurrent error: %#x\n", eglGetError()); + return false; + } + + EGLint w, h; + eglQuerySurface(mDisplay, surface, EGL_WIDTH, &w); + eglQuerySurface(mDisplay, surface, EGL_HEIGHT, &h); + glViewport(0, 0, w, h); + + return true; +} + +bool GLHelper::createSurfaceTexture(uint32_t w, uint32_t h, + sp<GLConsumer>* glConsumer, EGLSurface* surface, + GLuint* name) { + if (!makeCurrent(mDummySurface)) { + return false; + } + + *name = 0; + glGenTextures(1, name); + if (*name == 0) { + fprintf(stderr, "glGenTextures error: %#x\n", glGetError()); + return false; + } + + return createNamedSurfaceTexture(*name, w, h, glConsumer, surface); +} + +void GLHelper::destroySurface(EGLSurface* surface) { + if (eglGetCurrentSurface(EGL_READ) == *surface || + eglGetCurrentSurface(EGL_DRAW) == *surface) { + eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + eglDestroySurface(mDisplay, *surface); + *surface = EGL_NO_SURFACE; +} + +bool GLHelper::swapBuffers(EGLSurface surface) { + EGLint result; + result = eglSwapBuffers(mDisplay, surface); + if (result != EGL_TRUE) { + fprintf(stderr, "eglSwapBuffers error: %#x\n", eglGetError()); + return false; + } + return true; +} + +bool GLHelper::getShaderProgram(const char* name, GLuint* outPgm) { + for (size_t i = 0; i < mNumShaders; i++) { + if (strcmp(mShaderDescs[i].name, name) == 0) { + *outPgm = mShaderPrograms[i]; + return true; + } + } + + fprintf(stderr, "unknown shader name: \"%s\"\n", name); + + return false; +} + +bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h, + sp<GLConsumer>* glConsumer, EGLSurface* surface) { + sp<BufferQueue> bq = new BufferQueue(true, mGraphicBufferAlloc); + sp<GLConsumer> glc = new GLConsumer(name, true, + GL_TEXTURE_EXTERNAL_OES, false, bq); + glc->setDefaultBufferSize(w, h); + glc->setDefaultMaxBufferCount(3); + glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER); + + sp<ANativeWindow> anw = new Surface(bq); + EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL); + if (s == EGL_NO_SURFACE) { + fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError()); + return false; + } + + *glConsumer = glc; + *surface = s; + return true; +} + +bool GLHelper::computeWindowScale(uint32_t w, uint32_t h, float* scale) { + sp<IBinder> dpy = mSurfaceComposerClient->getBuiltInDisplay(0); + if (dpy == NULL) { + fprintf(stderr, "SurfaceComposer::getBuiltInDisplay failed.\n"); + return false; + } + + DisplayInfo info; + status_t err = mSurfaceComposerClient->getDisplayInfo(dpy, &info); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::getDisplayInfo failed: %#x\n", err); + return false; + } + + float scaleX = float(info.w) / float(w); + float scaleY = float(info.h) / float(h); + *scale = scaleX < scaleY ? scaleX : scaleY; + + return true; +} + +bool GLHelper::createWindowSurface(uint32_t w, uint32_t h, + sp<SurfaceControl>* surfaceControl, EGLSurface* surface) { + bool result; + status_t err; + + if (mSurfaceComposerClient == NULL) { + mSurfaceComposerClient = new SurfaceComposerClient; + } + err = mSurfaceComposerClient->initCheck(); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err); + return false; + } + + sp<SurfaceControl> sc = mSurfaceComposerClient->createSurface( + String8("Benchmark"), w, h, PIXEL_FORMAT_RGBA_8888, 0); + if (sc == NULL || !sc->isValid()) { + fprintf(stderr, "Failed to create SurfaceControl.\n"); + return false; + } + + float scale; + result = computeWindowScale(w, h, &scale); + if (!result) { + return false; + } + + SurfaceComposerClient::openGlobalTransaction(); + err = sc->setLayer(0x7FFFFFFF); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::setLayer error: %#x\n", err); + return false; + } + err = sc->setMatrix(scale, 0.0f, 0.0f, scale); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::setMatrix error: %#x\n", err); + return false; + } + + err = sc->show(); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::show error: %#x\n", err); + return false; + } + SurfaceComposerClient::closeGlobalTransaction(); + + sp<ANativeWindow> anw = sc->getSurface(); + EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL); + if (s == EGL_NO_SURFACE) { + fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError()); + return false; + } + + *surfaceControl = sc; + *surface = s; + return true; +} + +static bool compileShader(GLenum shaderType, const char* src, + GLuint* outShader) { + GLuint shader = glCreateShader(shaderType); + if (shader == 0) { + fprintf(stderr, "glCreateShader error: %#x\n", glGetError()); + return false; + } + + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen) { + char* buf = new char[infoLen]; + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + fprintf(stderr, "Shader compile log:\n%s\n", buf); + delete[] buf; + } + } + glDeleteShader(shader); + return false; + } + *outShader = shader; + return true; +} + +static void printShaderSource(const char* const* src) { + for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) { + fprintf(stderr, "%3d: %s\n", i+1, src[i]); + } +} + +static const char* makeShaderString(const char* const* src) { + size_t len = 0; + for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) { + // The +1 is for the '\n' that will be added. + len += strlen(src[i]) + 1; + } + + char* result = new char[len+1]; + char* end = result; + for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) { + strcpy(end, src[i]); + end += strlen(src[i]); + *end = '\n'; + end++; + } + *end = '\0'; + + return result; +} + +static bool compileShaderLines(GLenum shaderType, const char* const* lines, + GLuint* outShader) { + const char* src = makeShaderString(lines); + bool result = compileShader(shaderType, src, outShader); + if (!result) { + fprintf(stderr, "Shader source:\n"); + printShaderSource(lines); + return false; + } + delete[] src; + + return true; +} + +static bool linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) { + GLuint program = glCreateProgram(); + if (program == 0) { + fprintf(stderr, "glCreateProgram error: %#x\n", glGetError()); + return false; + } + + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char* buf = new char[bufLength]; + if (buf) { + glGetProgramInfoLog(program, bufLength, NULL, buf); + fprintf(stderr, "Program link log:\n%s\n", buf); + delete[] buf; + } + } + glDeleteProgram(program); + program = 0; + } + + *outPgm = program; + return program != 0; +} + +bool GLHelper::setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders) { + mShaderPrograms = new GLuint[numShaders]; + bool result = true; + + for (size_t i = 0; i < numShaders && result; i++) { + GLuint vs, fs; + + result = compileShaderLines(GL_VERTEX_SHADER, + shaderDescs[i].vertexShader, &vs); + if (!result) { + return false; + } + + result = compileShaderLines(GL_FRAGMENT_SHADER, + shaderDescs[i].fragmentShader, &fs); + if (!result) { + glDeleteShader(vs); + return false; + } + + result = linkShaderProgram(vs, fs, &mShaderPrograms[i]); + glDeleteShader(vs); + glDeleteShader(fs); + } + + mNumShaders = numShaders; + mShaderDescs = shaderDescs; + + return result; +} + +bool GLHelper::getDitherTexture(GLuint* outTexName) { + if (mDitherTexture == 0) { + const uint8_t pattern[] = { + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 + }; + + glGenTextures(1, &mDitherTexture); + glBindTexture(GL_TEXTURE_2D, mDitherTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, + DITHER_KERNEL_SIZE, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); + } + + *outTexName = mDitherTexture; + + return true; +} + +} diff --git a/cmds/flatland/GLHelper.h b/cmds/flatland/GLHelper.h new file mode 100644 index 0000000..7a9e9e3 --- /dev/null +++ b/cmds/flatland/GLHelper.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 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 <gui/GraphicBufferAlloc.h> +#include <gui/GLConsumer.h> +#include <gui/Surface.h> +#include <gui/SurfaceControl.h> + +#include <EGL/egl.h> +#include <GLES2/gl2.h> + +namespace android { + +class SurfaceComposerClient; +class SurfaceControl; + +enum { MAX_SHADER_LINES = 128 }; + +struct ShaderDesc { + const char* name; + const char* vertexShader[MAX_SHADER_LINES]; + const char* fragmentShader[MAX_SHADER_LINES]; +}; + +class GLHelper { + +public: + + enum { DITHER_KERNEL_SIZE = 4 }; + + GLHelper(); + + ~GLHelper(); + + bool setUp(const ShaderDesc* shaderDescs, size_t numShaders); + + void tearDown(); + + bool makeCurrent(EGLSurface surface); + + bool createSurfaceTexture(uint32_t w, uint32_t h, + sp<GLConsumer>* surfaceTexture, EGLSurface* surface, + GLuint* name); + + bool createWindowSurface(uint32_t w, uint32_t h, + sp<SurfaceControl>* surfaceControl, EGLSurface* surface); + + void destroySurface(EGLSurface* surface); + + bool swapBuffers(EGLSurface surface); + + bool getShaderProgram(const char* name, GLuint* outPgm); + + bool getDitherTexture(GLuint* outTexName); + +private: + + bool createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h, + sp<GLConsumer>* surfaceTexture, EGLSurface* surface); + + bool computeWindowScale(uint32_t w, uint32_t h, float* scale); + + bool setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders); + + sp<GraphicBufferAlloc> mGraphicBufferAlloc; + + EGLDisplay mDisplay; + EGLContext mContext; + EGLSurface mDummySurface; + sp<GLConsumer> mDummyGLConsumer; + EGLConfig mConfig; + + sp<SurfaceComposerClient> mSurfaceComposerClient; + + GLuint* mShaderPrograms; + const ShaderDesc* mShaderDescs; + size_t mNumShaders; + + GLuint mDitherTexture; +}; + +} // namespace android diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp new file mode 100644 index 0000000..99715d3 --- /dev/null +++ b/cmds/flatland/Main.cpp @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2012 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. + */ + +#define ATRACE_TAG ATRACE_TAG_ALWAYS + +#include <gui/GraphicBufferAlloc.h> +#include <gui/Surface.h> +#include <gui/SurfaceControl.h> +#include <gui/GLConsumer.h> +#include <gui/Surface.h> +#include <ui/Fence.h> +#include <utils/Trace.h> + +#include <EGL/egl.h> +#include <GLES2/gl2.h> + +#include <math.h> +#include <getopt.h> + +#include "Flatland.h" +#include "GLHelper.h" + +using namespace ::android; + +static uint32_t g_SleepBetweenSamplesMs = 0; +static bool g_PresentToWindow = false; +static size_t g_BenchmarkNameLen = 0; + +struct BenchmarkDesc { + // The name of the test. + const char* name; + + // The dimensions of the space in which window layers are specified. + uint32_t width; + uint32_t height; + + // The screen heights at which to run the test. + uint32_t runHeights[MAX_TEST_RUNS]; + + // The list of window layers. + LayerDesc layers[MAX_NUM_LAYERS]; +}; + +static const BenchmarkDesc benchmarks[] = { + { "16:10 Single Static Window", + 2560, 1600, { 800, 1600, 2400 }, + { + { // Window + 0, staticGradient, opaque, + 0, 50, 2560, 1454, + }, + { // Status bar + 0, staticGradient, opaque, + 0, 0, 2560, 50, + }, + { // Navigation bar + 0, staticGradient, opaque, + 0, 1504, 2560, 96, + }, + }, + }, + + { "16:10 App -> Home Transition", + 2560, 1600, { 800, 1600, 2400 }, + { + { // Wallpaper + 0, staticGradient, opaque, + 0, 50, 2560, 1454, + }, + { // Launcher + 0, staticGradient, blend, + 0, 50, 2560, 1454, + }, + { // Outgoing activity + 0, staticGradient, blendShrink, + 20, 70, 2520, 1414, + }, + { // Status bar + 0, staticGradient, opaque, + 0, 0, 2560, 50, + }, + { // Navigation bar + 0, staticGradient, opaque, + 0, 1504, 2560, 96, + }, + }, + }, + + { "16:10 SurfaceView -> Home Transition", + 2560, 1600, { 800, 1600, 2400 }, + { + { // Wallpaper + 0, staticGradient, opaque, + 0, 50, 2560, 1454, + }, + { // Launcher + 0, staticGradient, blend, + 0, 50, 2560, 1454, + }, + { // Outgoing SurfaceView + 0, staticGradient, blendShrink, + 20, 70, 2520, 1414, + }, + { // Outgoing activity + 0, staticGradient, blendShrink, + 20, 70, 2520, 1414, + }, + { // Status bar + 0, staticGradient, opaque, + 0, 0, 2560, 50, + }, + { // Navigation bar + 0, staticGradient, opaque, + 0, 1504, 2560, 96, + }, + }, + }, +}; + +static const ShaderDesc shaders[] = { + { + name: "Blit", + vertexShader: { + "precision mediump float;", + "", + "attribute vec4 position;", + "attribute vec4 uv;", + "", + "varying vec4 texCoords;", + "", + "uniform mat4 objToNdc;", + "uniform mat4 uvToTex;", + "", + "void main() {", + " gl_Position = objToNdc * position;", + " texCoords = uvToTex * uv;", + "}", + }, + fragmentShader: { + "#extension GL_OES_EGL_image_external : require", + "precision mediump float;", + "", + "varying vec4 texCoords;", + "", + "uniform samplerExternalOES blitSrc;", + "uniform vec4 modColor;", + "", + "void main() {", + " gl_FragColor = texture2D(blitSrc, texCoords.xy);", + " gl_FragColor *= modColor;", + "}", + }, + }, + + { + name: "Gradient", + vertexShader: { + "precision mediump float;", + "", + "attribute vec4 position;", + "attribute vec4 uv;", + "", + "varying float interp;", + "", + "uniform mat4 objToNdc;", + "uniform mat4 uvToInterp;", + "", + "void main() {", + " gl_Position = objToNdc * position;", + " interp = (uvToInterp * uv).x;", + "}", + }, + fragmentShader: { + "precision mediump float;", + "", + "varying float interp;", + "", + "uniform vec4 color0;", + "uniform vec4 color1;", + "", + "uniform sampler2D ditherKernel;", + "uniform float invDitherKernelSize;", + "uniform float invDitherKernelSizeSq;", + "", + "void main() {", + " float dither = texture2D(ditherKernel,", + " gl_FragCoord.xy * invDitherKernelSize).a;", + " dither *= invDitherKernelSizeSq;", + " vec4 color = mix(color0, color1, clamp(interp, 0.0, 1.0));", + " gl_FragColor = color + vec4(dither, dither, dither, 0.0);", + "}", + }, + }, +}; + +class Layer { + +public: + + Layer() : + mFirstFrame(true), + mGLHelper(NULL), + mSurface(EGL_NO_SURFACE) { + } + + bool setUp(const LayerDesc& desc, GLHelper* helper) { + bool result; + + mDesc = desc; + mGLHelper = helper; + + result = mGLHelper->createSurfaceTexture(mDesc.width, mDesc.height, + &mGLConsumer, &mSurface, &mTexName); + if (!result) { + return false; + } + + mRenderer = desc.rendererFactory(); + result = mRenderer->setUp(helper); + if (!result) { + return false; + } + + mComposer = desc.composerFactory(); + result = mComposer->setUp(desc, helper); + if (!result) { + return false; + } + + return true; + } + + void tearDown() { + if (mComposer != NULL) { + mComposer->tearDown(); + delete mComposer; + mComposer = NULL; + } + + if (mRenderer != NULL) { + mRenderer->tearDown(); + delete mRenderer; + mRenderer = NULL; + } + + if (mSurface != EGL_NO_SURFACE) { + mGLHelper->destroySurface(&mSurface); + mGLConsumer->abandon(); + } + mGLHelper = NULL; + mGLConsumer.clear(); + } + + bool render() { + return mRenderer->render(mSurface); + } + + bool prepareComposition() { + status_t err; + + err = mGLConsumer->updateTexImage(); + if (err < 0) { + fprintf(stderr, "GLConsumer::updateTexImage error: %d\n", err); + return false; + } + + return true; + } + + bool compose() { + return mComposer->compose(mTexName, mGLConsumer); + } + +private: + bool mFirstFrame; + + LayerDesc mDesc; + + GLHelper* mGLHelper; + + GLuint mTexName; + sp<GLConsumer> mGLConsumer; + EGLSurface mSurface; + + Renderer* mRenderer; + Composer* mComposer; +}; + +class BenchmarkRunner { + +public: + + BenchmarkRunner(const BenchmarkDesc& desc, size_t instance) : + mDesc(desc), + mInstance(instance), + mNumLayers(countLayers(desc)), + mGLHelper(NULL), + mSurface(EGL_NO_SURFACE), + mWindowSurface(EGL_NO_SURFACE) { + } + + bool setUp() { + ATRACE_CALL(); + + bool result; + EGLint resulte; + + float scaleFactor = float(mDesc.runHeights[mInstance]) / + float(mDesc.height); + uint32_t w = uint32_t(scaleFactor * float(mDesc.width)); + uint32_t h = mDesc.runHeights[mInstance]; + + mGLHelper = new GLHelper(); + result = mGLHelper->setUp(shaders, NELEMS(shaders)); + if (!result) { + return false; + } + + GLuint texName; + result = mGLHelper->createSurfaceTexture(w, h, &mGLConsumer, &mSurface, + &texName); + if (!result) { + return false; + } + + for (size_t i = 0; i < mNumLayers; i++) { + // Scale the layer to match the current screen size. + LayerDesc ld = mDesc.layers[i]; + ld.x = int32_t(scaleFactor * float(ld.x)); + ld.y = int32_t(scaleFactor * float(ld.y)); + ld.width = uint32_t(scaleFactor * float(ld.width)); + ld.height = uint32_t(scaleFactor * float(ld.height)); + + // Set up the layer. + result = mLayers[i].setUp(ld, mGLHelper); + if (!result) { + return false; + } + } + + if (g_PresentToWindow) { + result = mGLHelper->createWindowSurface(w, h, &mSurfaceControl, + &mWindowSurface); + if (!result) { + return false; + } + + result = doFrame(mWindowSurface); + if (!result) { + return false; + } + } + + return true; + } + + void tearDown() { + ATRACE_CALL(); + + for (size_t i = 0; i < mNumLayers; i++) { + mLayers[i].tearDown(); + } + + if (mGLHelper != NULL) { + if (mWindowSurface != EGL_NO_SURFACE) { + mGLHelper->destroySurface(&mWindowSurface); + } + mGLHelper->destroySurface(&mSurface); + mGLConsumer->abandon(); + mGLConsumer.clear(); + mSurfaceControl.clear(); + mGLHelper->tearDown(); + delete mGLHelper; + mGLHelper = NULL; + } + } + + nsecs_t run(uint32_t warmUpFrames, uint32_t totalFrames) { + ATRACE_CALL(); + + bool result; + status_t err; + + resetColorGenerator(); + + // Do the warm-up frames. + for (uint32_t i = 0; i < warmUpFrames; i++) { + result = doFrame(mSurface); + if (!result) { + return -1; + } + } + + // Grab the fence for the start timestamp. + sp<Fence> startFence = mGLConsumer->getCurrentFence(); + + // the timed frames. + for (uint32_t i = warmUpFrames; i < totalFrames; i++) { + result = doFrame(mSurface); + if (!result) { + return -1; + } + } + + // Grab the fence for the end timestamp. + sp<Fence> endFence = mGLConsumer->getCurrentFence(); + + // Keep doing frames until the end fence has signaled. + while (endFence->wait(0) == -ETIME) { + result = doFrame(mSurface); + if (!result) { + return -1; + } + } + + // Compute the time delta. + nsecs_t startTime = startFence->getSignalTime(); + nsecs_t endTime = endFence->getSignalTime(); + + return endTime - startTime; + } + +private: + + bool doFrame(EGLSurface surface) { + bool result; + status_t err; + + for (size_t i = 0; i < mNumLayers; i++) { + result = mLayers[i].render(); + if (!result) { + return false; + } + } + + for (size_t i = 0; i < mNumLayers; i++) { + result = mLayers[i].prepareComposition(); + if (!result) { + return false; + } + } + + result = mGLHelper->makeCurrent(surface); + if (!result) { + return false; + } + + glClearColor(1.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + for (size_t i = 0; i < mNumLayers; i++) { + result = mLayers[i].compose(); + if (!result) { + return false; + } + } + + result = mGLHelper->swapBuffers(surface); + if (!result) { + return false; + } + + err = mGLConsumer->updateTexImage(); + if (err < 0) { + fprintf(stderr, "GLConsumer::updateTexImage error: %d\n", err); + return false; + } + + return true; + } + + static size_t countLayers(const BenchmarkDesc& desc) { + size_t i; + for (i = 0; i < MAX_NUM_LAYERS; i++) { + if (desc.layers[i].rendererFactory == NULL) { + break; + } + } + return i; + } + + const BenchmarkDesc& mDesc; + const size_t mInstance; + const size_t mNumLayers; + + GLHelper* mGLHelper; + + // The surface into which layers are composited + sp<GLConsumer> mGLConsumer; + EGLSurface mSurface; + + // Used for displaying the surface to a window. + EGLSurface mWindowSurface; + sp<SurfaceControl> mSurfaceControl; + + Layer mLayers[MAX_NUM_LAYERS]; +}; + +static int cmpDouble(const double* lhs, const double* rhs) { + if (*lhs < *rhs) { + return -1; + } else if (*rhs < *lhs) { + return 1; + } + return 0; +} + +// Run a single benchmark and print the result. +static bool runTest(const BenchmarkDesc b, size_t run) { + bool success = true; + double prevResult = 0.0, result = 0.0; + Vector<double> samples; + + uint32_t runHeight = b.runHeights[run]; + uint32_t runWidth = b.width * runHeight / b.height; + printf(" %-*s | %4d x %4d | ", g_BenchmarkNameLen, b.name, + runWidth, runHeight); + fflush(stdout); + + BenchmarkRunner r(b, run); + if (!r.setUp()) { + fprintf(stderr, "error initializing runner.\n"); + return false; + } + + // The slowest 1/outlierFraction sample results are ignored as potential + // outliers. + const uint32_t outlierFraction = 16; + const double threshold = .0025; + + uint32_t warmUpFrames = 1; + uint32_t totalFrames = 5; + + // Find the number of frames needed to run for over 100ms. + double runTime = 0.0; + while (true) { + runTime = double(r.run(warmUpFrames, totalFrames)); + if (runTime < 50e6) { + warmUpFrames *= 2; + totalFrames *= 2; + } else { + break; + } + } + + + if (totalFrames - warmUpFrames > 16) { + // The test runs too fast to get a stable result. Skip it. + printf(" fast"); + goto done; + } else if (totalFrames == 5 && runTime > 200e6) { + // The test runs too slow to be very useful. Skip it. + printf(" slow"); + goto done; + } + + do { + size_t newSamples = samples.size(); + if (newSamples == 0) { + newSamples = 4*outlierFraction; + } + + if (newSamples > 512) { + printf("varies"); + goto done; + } + + for (size_t i = 0; i < newSamples; i++) { + double sample = double(r.run(warmUpFrames, totalFrames)); + + if (g_SleepBetweenSamplesMs > 0) { + usleep(g_SleepBetweenSamplesMs * 1000); + } + + if (sample < 0.0) { + success = false; + goto done; + } + + samples.add(sample); + } + + samples.sort(cmpDouble); + + prevResult = result; + size_t elem = (samples.size() * (outlierFraction-1) / outlierFraction); + result = (samples[elem-1] + samples[elem]) * 0.5; + } while (fabs(result - prevResult) > threshold * result); + + printf("%6.3f", result / double(totalFrames - warmUpFrames) / 1e6); + +done: + + printf("\n"); + fflush(stdout); + r.tearDown(); + + return success; +} + +static void printResultsTableHeader() { + const char* scenario = "Scenario"; + size_t len = strlen(scenario); + size_t leftPad = (g_BenchmarkNameLen - len) / 2; + size_t rightPad = g_BenchmarkNameLen - len - leftPad; + printf(" %*s%s%*s | Resolution | Time (ms)\n", leftPad, "", + "Scenario", rightPad, ""); +} + +// Run ALL the benchmarks! +static bool runTests() { + printResultsTableHeader(); + + for (size_t i = 0; i < NELEMS(benchmarks); i++) { + const BenchmarkDesc& b = benchmarks[i]; + for (size_t j = 0; j < MAX_TEST_RUNS && b.runHeights[j]; j++) { + if (!runTest(b, j)) { + return false; + } + } + } + return true; +} + +// Return the length longest benchmark name. +static size_t maxBenchmarkNameLen() { + size_t maxLen = 0; + for (size_t i = 0; i < NELEMS(benchmarks); i++) { + const BenchmarkDesc& b = benchmarks[i]; + size_t len = strlen(b.name); + if (len > maxLen) { + maxLen = len; + } + } + return maxLen; +} + +// Print the command usage help to stderr. +static void showHelp(const char *cmd) { + fprintf(stderr, "usage: %s [options]\n", cmd); + fprintf(stderr, "options include:\n" + " -s N sleep for N ms between samples\n" + " -d display the test frame to a window\n" + " --help print this helpful message and exit\n" + ); +} + +int main(int argc, char** argv) { + if (argc == 2 && 0 == strcmp(argv[1], "--help")) { + showHelp(argv[0]); + exit(0); + } + + for (;;) { + int ret; + int option_index = 0; + static struct option long_options[] = { + {"help", no_argument, 0, 0 }, + { 0, 0, 0, 0 } + }; + + ret = getopt_long(argc, argv, "ds:", + long_options, &option_index); + + if (ret < 0) { + break; + } + + switch(ret) { + case 'd': + g_PresentToWindow = true; + break; + + case 's': + g_SleepBetweenSamplesMs = atoi(optarg); + break; + + case 0: + if (strcmp(long_options[option_index].name, "help")) { + showHelp(argv[0]); + exit(0); + } + break; + + default: + showHelp(argv[0]); + exit(2); + } + } + + g_BenchmarkNameLen = maxBenchmarkNameLen(); + + printf(" cmdline:"); + for (int i = 0; i < argc; i++) { + printf(" %s", argv[i]); + } + printf("\n"); + + if (!runTests()) { + fprintf(stderr, "exiting due to error.\n"); + return 1; + } +} diff --git a/cmds/flatland/README.txt b/cmds/flatland/README.txt new file mode 100644 index 0000000..ed47b3c --- /dev/null +++ b/cmds/flatland/README.txt @@ -0,0 +1,74 @@ +Flatland is a benchmark for measuring GPU performance in various 2D UI +rendering and window compositing scenarios. It is designed to be used early +in the device development process to evaluate GPU hardware (e.g. for SoC +selection). It uses OpenGL ES 2.0, gralloc, and the Android explicit +synchronization framework, so it can only be run on devices with drivers +supporting those HALs. + + +Preparing a Device + +Because it's measuring hardware performance, flatland should be run in as +consistent and static an environment as possible. The display should be +turned off and background services should be stopped before running the +benchmark. Running 'adb shell stop' after turning off the display is probably +sufficient for this, but if there are device- specific background services +that consume much CPU cycles, memory bandwidth, or might otherwise interfere +with GPU rendering, those should be stopped as well (and ideally they'd be +fixed or eliminated for production devices). + +Additionally, all relevant hardware clocks should be locked at a particular +frequency when running flatland. At a minimum this includes the CPU, GPU, and +memory bus clocks. Running flatland with dynamic clocking essentially +measures the behavior of the dynamic clocking algorithm under a fairly +unrealistic workload, and will likely result in unstable and useless results. + +If running the benchmark with the clocks locked causes thermal issues, the -s +command line option can be used to insert a sleep (specified in milliseconds) +in between each benchmark sample run. Regardless of the scenario being +measured, each sample measurement runs for between 50 and 200 ms, so a sleep +time between 10 and 50 ms should address most thermal problems. + + +Interpreting the Output + +The output of flatland should look something like this: + + cmdline: flatland + Scenario | Resolution | Time (ms) + 16:10 Single Static Window | 1280 x 800 | fast + 16:10 Single Static Window | 2560 x 1600 | 5.368 + 16:10 Single Static Window | 3840 x 2400 | 11.979 + 16:10 App -> Home Transition | 1280 x 800 | 4.069 + 16:10 App -> Home Transition | 2560 x 1600 | 15.911 + 16:10 App -> Home Transition | 3840 x 2400 | 38.795 + 16:10 SurfaceView -> Home Transition | 1280 x 800 | 5.387 + 16:10 SurfaceView -> Home Transition | 2560 x 1600 | 21.147 + 16:10 SurfaceView -> Home Transition | 3840 x 2400 | slow + +The first column is simply a description of the scenario that's being +simulated. The second column indicates the resolution at which the scenario +was measured. The third column is the measured benchmark result. It +indicates the expected time in milliseconds that a single frame of the +scenario takes to complete. + +The third column may also contain one of three other values: + + fast - This indicates that frames of the scenario completed too fast to be + reliably benchmarked. This corresponds to a frame time less than 3 ms. + Rather than spending time trying (and likely failing) to get a stable + result, the scenario was skipped. + + slow - This indicates that frames of the scenario took too long to + complete. This corresponds to a frame time over 50 ms. Rather than + simulating a scenario that is obviously impractical on this device, the + scenario was skipped. + + varies - This indicates that the scenario was measured, but it did not + yield a stable result. Occasionally this happens with an otherwise stable + scenario. In this case, simply rerunning flatland should yield a valid + result. If a scenario repeatedly results in a 'varies' output, that + probably indicates that something is wrong with the environment in which + flatland is being run. Check that the hardware clock frequencies are + locked and that no heavy-weight services / daemons are running in the + background. diff --git a/cmds/flatland/Renderers.cpp b/cmds/flatland/Renderers.cpp new file mode 100644 index 0000000..f1e5488 --- /dev/null +++ b/cmds/flatland/Renderers.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 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 "Flatland.h" +#include "GLHelper.h" + +namespace android { + +static float colors[][4] = { + { .85f, .14f, .44f, 1.0f }, + { .91f, .72f, .10f, 1.0f }, + { .04f, .66f, .42f, 1.0f }, + { .84f, .39f, .68f, 1.0f }, + { .38f, .53f, .78f, 1.0f }, +}; + +static size_t g_colorIndex; + +const float* genColor() { + float* color = colors[g_colorIndex]; + g_colorIndex = (g_colorIndex + 1) % NELEMS(colors); + return color; +} + +void resetColorGenerator() { + g_colorIndex = 0; +} + +class GradientRenderer { + +public: + + bool setUp(GLHelper* helper) { + bool result; + + result = helper->getShaderProgram("Gradient", &mGradPgm); + if (!result) { + return false; + } + + result = helper->getDitherTexture(&mDitherTexName); + if (!result) { + return false; + } + + mPosAttribLoc = glGetAttribLocation(mGradPgm, "position"); + mUVAttribLoc = glGetAttribLocation(mGradPgm, "uv"); + mUVToInterpUniformLoc = glGetUniformLocation(mGradPgm, "uvToInterp"); + mObjToNdcUniformLoc = glGetUniformLocation(mGradPgm, "objToNdc"); + mDitherKernelSamplerLoc = glGetUniformLocation(mGradPgm, "ditherKernel"); + mInvDitherKernelSizeUniformLoc = glGetUniformLocation(mGradPgm, + "invDitherKernelSize"); + mInvDitherKernelSizeSqUniformLoc = glGetUniformLocation(mGradPgm, + "invDitherKernelSizeSq"); + mColor0UniformLoc = glGetUniformLocation(mGradPgm, "color0"); + mColor1UniformLoc = glGetUniformLocation(mGradPgm, "color1"); + + return true; + } + + void tearDown() { + } + + bool drawGradient() { + float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + const float pos[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + }; + const float uv[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + const float* color0 = genColor(); + const float* color1 = genColor(); + + glUseProgram(mGradPgm); + + glVertexAttribPointer(mPosAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, pos); + glVertexAttribPointer(mUVAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, uv); + glEnableVertexAttribArray(mPosAttribLoc); + glEnableVertexAttribArray(mUVAttribLoc); + + float invDitherKernelSize = 1.0f / float(GLHelper::DITHER_KERNEL_SIZE); + float invDitherKernelSizeSq = invDitherKernelSize * invDitherKernelSize; + + glUniformMatrix4fv(mObjToNdcUniformLoc, 1, GL_FALSE, identity); + glUniformMatrix4fv(mUVToInterpUniformLoc, 1, GL_FALSE, identity); + glUniform1f(mInvDitherKernelSizeUniformLoc, invDitherKernelSize); + glUniform1f(mInvDitherKernelSizeSqUniformLoc, invDitherKernelSizeSq); + glUniform4fv(mColor0UniformLoc, 1, color0); + glUniform4fv(mColor1UniformLoc, 1, color1); + + if (glGetError() != GL_NO_ERROR) { + fprintf(stderr, "GL error! 0\n"); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mDitherTexName); + + if (glGetError() != GL_NO_ERROR) { + fprintf(stderr, "GL error! 1\n"); + } + + glUniform1i(mDitherKernelSamplerLoc, 0); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(mPosAttribLoc); + glDisableVertexAttribArray(mUVAttribLoc); + + if (glGetError() != GL_NO_ERROR) { + fprintf(stderr, "GL error! 2\n"); + } + + return true; + } + + GLuint mGradPgm; + GLuint mDitherTexName; + GLuint mPosAttribLoc; + GLuint mUVAttribLoc; + GLuint mObjToNdcUniformLoc; + GLuint mUVToInterpUniformLoc; + GLuint mDitherKernelSamplerLoc; + GLuint mInvDitherKernelSizeUniformLoc; + GLuint mInvDitherKernelSizeSqUniformLoc; + GLuint mColor0UniformLoc; + GLuint mColor1UniformLoc; +}; + +Renderer* staticGradient() { + class NoRenderer : public Renderer { + virtual bool setUp(GLHelper* helper) { + mIsFirstFrame = true; + mGLHelper = helper; + return mGradientRenderer.setUp(helper); + } + + virtual void tearDown() { + mGradientRenderer.tearDown(); + } + + virtual bool render(EGLSurface surface) { + if (mIsFirstFrame) { + bool result; + mIsFirstFrame = false; + + result = mGLHelper->makeCurrent(surface); + if (!result) { + return false; + } + + result = mGradientRenderer.drawGradient(); + if (!result) { + return false; + } + + result = mGLHelper->swapBuffers(surface); + if (!result) { + return false; + } + } + return true; + } + + bool mIsFirstFrame; + GLHelper* mGLHelper; + GradientRenderer mGradientRenderer; + }; + return new NoRenderer; +} + + +} // namespace android diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk new file mode 100644 index 0000000..1dd4ee5 --- /dev/null +++ b/cmds/installd/Android.mk @@ -0,0 +1,42 @@ +LOCAL_PATH := $(call my-dir) + +common_src_files := \ + commands.c utils.c + +# +# Static library used in testing and executable +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(common_src_files) + +LOCAL_MODULE := libinstalld + +LOCAL_MODULE_TAGS := eng tests + +include $(BUILD_STATIC_LIBRARY) + +# +# Executable +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + installd.c \ + $(common_src_files) + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libselinux + +LOCAL_STATIC_LIBRARIES := \ + libdiskusage + +LOCAL_MODULE := installd + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c new file mode 100644 index 0000000..e544be7 --- /dev/null +++ b/cmds/installd/commands.c @@ -0,0 +1,1075 @@ +/* +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <sys/capability.h> +#include "installd.h" +#include <diskusage/dirsize.h> +#include <selinux/android.h> + +/* Directory records that are used in execution of commands. */ +dir_rec_t android_data_dir; +dir_rec_t android_asec_dir; +dir_rec_t android_app_dir; +dir_rec_t android_app_private_dir; +dir_rec_t android_app_lib_dir; +dir_rec_t android_media_dir; +dir_rec_array_t android_system_dirs; + +int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo) +{ + char pkgdir[PKG_PATH_MAX]; + char libsymlink[PKG_PATH_MAX]; + char applibdir[PKG_PATH_MAX]; + struct stat libStat; + + if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) { + ALOGE("invalid uid/gid: %d %d\n", uid, gid); + return -1; + } + + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) { + ALOGE("cannot create package path\n"); + return -1; + } + + if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, 0)) { + ALOGE("cannot create package lib symlink origin path\n"); + return -1; + } + + if (create_pkg_path_in_dir(applibdir, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) { + ALOGE("cannot create package lib symlink dest path\n"); + return -1; + } + + if (mkdir(pkgdir, 0751) < 0) { + ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); + return -1; + } + if (chmod(pkgdir, 0751) < 0) { + ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -1; + } + + if (lstat(libsymlink, &libStat) < 0) { + if (errno != ENOENT) { + ALOGE("couldn't stat lib dir: %s\n", strerror(errno)); + return -1; + } + } else { + if (S_ISDIR(libStat.st_mode)) { + if (delete_dir_contents(libsymlink, 1, 0) < 0) { + ALOGE("couldn't delete lib directory during install for: %s", libsymlink); + return -1; + } + } else if (S_ISLNK(libStat.st_mode)) { + if (unlink(libsymlink) < 0) { + ALOGE("couldn't unlink lib directory during install for: %s", libsymlink); + return -1; + } + } + } + + if (symlink(applibdir, libsymlink) < 0) { + ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, applibdir, + strerror(errno)); + unlink(pkgdir); + return -1; + } + + if (selinux_android_setfilecon2(pkgdir, pkgname, seinfo, uid) < 0) { + ALOGE("cannot setfilecon dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(libsymlink); + unlink(pkgdir); + return -errno; + } + + if (chown(pkgdir, uid, gid) < 0) { + ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(libsymlink); + unlink(pkgdir); + return -1; + } + + return 0; +} + +int uninstall(const char *pkgname, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) + return -1; + + /* delete contents AND directory, no exceptions */ + return delete_dir_contents(pkgdir, 1, NULL); +} + +int renamepkg(const char *oldpkgname, const char *newpkgname) +{ + char oldpkgdir[PKG_PATH_MAX]; + char newpkgdir[PKG_PATH_MAX]; + + if (create_pkg_path(oldpkgdir, oldpkgname, PKG_DIR_POSTFIX, 0)) + return -1; + if (create_pkg_path(newpkgdir, newpkgname, PKG_DIR_POSTFIX, 0)) + return -1; + + if (rename(oldpkgdir, newpkgdir) < 0) { + ALOGE("cannot rename dir '%s' to '%s': %s\n", oldpkgdir, newpkgdir, strerror(errno)); + return -errno; + } + return 0; +} + +int fix_uid(const char *pkgname, uid_t uid, gid_t gid) +{ + char pkgdir[PKG_PATH_MAX]; + struct stat s; + int rc = 0; + + if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) { + ALOGE("invalid uid/gid: %d %d\n", uid, gid); + return -1; + } + + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) { + ALOGE("cannot create package path\n"); + return -1; + } + + if (stat(pkgdir, &s) < 0) return -1; + + if (s.st_uid != 0 || s.st_gid != 0) { + ALOGE("fixing uid of non-root pkg: %s %lu %lu\n", pkgdir, s.st_uid, s.st_gid); + return -1; + } + + if (chmod(pkgdir, 0751) < 0) { + ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + if (chown(pkgdir, uid, gid) < 0) { + ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + + return 0; +} + +int delete_user_data(const char *pkgname, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) + return -1; + + /* delete contents, excluding "lib", but not the directory itself */ + return delete_dir_contents(pkgdir, 0, "lib"); +} + +int make_user_data(const char *pkgname, uid_t uid, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + char applibdir[PKG_PATH_MAX]; + char libsymlink[PKG_PATH_MAX]; + struct stat libStat; + + // Create the data dir for the package + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) { + return -1; + } + if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, persona)) { + ALOGE("cannot create package lib symlink origin path\n"); + return -1; + } + if (create_pkg_path_in_dir(applibdir, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) { + ALOGE("cannot create package lib symlink dest path\n"); + return -1; + } + + if (mkdir(pkgdir, 0751) < 0) { + ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); + return -errno; + } + if (chmod(pkgdir, 0751) < 0) { + ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + + if (lstat(libsymlink, &libStat) < 0) { + if (errno != ENOENT) { + ALOGE("couldn't stat lib dir for non-primary: %s\n", strerror(errno)); + unlink(pkgdir); + return -1; + } + } else { + if (S_ISDIR(libStat.st_mode)) { + if (delete_dir_contents(libsymlink, 1, 0) < 0) { + ALOGE("couldn't delete lib directory during install for non-primary: %s", + libsymlink); + unlink(pkgdir); + return -1; + } + } else if (S_ISLNK(libStat.st_mode)) { + if (unlink(libsymlink) < 0) { + ALOGE("couldn't unlink lib directory during install for non-primary: %s", + libsymlink); + unlink(pkgdir); + return -1; + } + } + } + + if (symlink(applibdir, libsymlink) < 0) { + ALOGE("couldn't symlink directory for non-primary '%s' -> '%s': %s\n", libsymlink, + applibdir, strerror(errno)); + unlink(pkgdir); + return -1; + } + + if (selinux_android_setfilecon(pkgdir, pkgname, uid) < 0) { + ALOGE("cannot setfilecon dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(libsymlink); + unlink(pkgdir); + return -errno; + } + + if (chown(pkgdir, uid, uid) < 0) { + ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(libsymlink); + unlink(pkgdir); + return -errno; + } + + return 0; +} + +int delete_persona(uid_t persona) +{ + char data_path[PKG_PATH_MAX]; + if (create_persona_path(data_path, persona)) { + return -1; + } + if (delete_dir_contents(data_path, 1, NULL)) { + return -1; + } + + char media_path[PATH_MAX]; + if (create_persona_media_path(media_path, (userid_t) persona) == -1) { + return -1; + } + if (delete_dir_contents(media_path, 1, NULL) == -1) { + return -1; + } + + return 0; +} + +int delete_cache(const char *pkgname, uid_t persona) +{ + char cachedir[PKG_PATH_MAX]; + + if (create_pkg_path(cachedir, pkgname, CACHE_DIR_POSTFIX, persona)) + return -1; + + /* delete contents, not the directory, no exceptions */ + return delete_dir_contents(cachedir, 0, 0); +} + +/* Try to ensure free_size bytes of storage are available. + * Returns 0 on success. + * This is rather simple-minded because doing a full LRU would + * be potentially memory-intensive, and without atime it would + * also require that apps constantly modify file metadata even + * when just reading from the cache, which is pretty awful. + */ +int free_cache(int64_t free_size) +{ + cache_t* cache; + int64_t avail; + DIR *d; + struct dirent *de; + char tmpdir[PATH_MAX]; + char *dirpos; + + avail = data_disk_free(); + if (avail < 0) return -1; + + ALOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail); + if (avail >= free_size) return 0; + + cache = start_cache_collection(); + + // Collect cache files for primary user. + if (create_persona_path(tmpdir, 0) == 0) { + //ALOGI("adding cache files from %s\n", tmpdir); + add_cache_files(cache, tmpdir, "cache"); + } + + // Search for other users and add any cache files from them. + snprintf(tmpdir, sizeof(tmpdir), "%s%s", android_data_dir.path, + SECONDARY_USER_PREFIX); + dirpos = tmpdir + strlen(tmpdir); + d = opendir(tmpdir); + if (d != NULL) { + while ((de = readdir(d))) { + if (de->d_type == DT_DIR) { + const char *name = de->d_name; + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + if ((strlen(name)+(dirpos-tmpdir)) < (sizeof(tmpdir)-1)) { + strcpy(dirpos, name); + //ALOGI("adding cache files from %s\n", tmpdir); + add_cache_files(cache, tmpdir, "cache"); + } else { + ALOGW("Path exceeds limit: %s%s", tmpdir, name); + } + } + } + closedir(d); + } + + // Collect cache files on external storage for all users (if it is mounted as part + // of the internal storage). + strcpy(tmpdir, android_media_dir.path); + dirpos = tmpdir + strlen(tmpdir); + d = opendir(tmpdir); + if (d != NULL) { + while ((de = readdir(d))) { + if (de->d_type == DT_DIR) { + const char *name = de->d_name; + /* skip any dir that doesn't start with a number, so not a user */ + if (name[0] < '0' || name[0] > '9') { + continue; + } + if ((strlen(name)+(dirpos-tmpdir)) < (sizeof(tmpdir)-1)) { + strcpy(dirpos, name); + if (lookup_media_dir(tmpdir, "Android") == 0 + && lookup_media_dir(tmpdir, "data") == 0) { + //ALOGI("adding cache files from %s\n", tmpdir); + add_cache_files(cache, tmpdir, "cache"); + } + } else { + ALOGW("Path exceeds limit: %s%s", tmpdir, name); + } + } + } + closedir(d); + } + + clear_cache_files(cache, free_size); + finish_cache_collection(cache); + + return data_disk_free() >= free_size ? 0 : -1; +} + +int move_dex(const char *src, const char *dst) +{ + char src_dex[PKG_PATH_MAX]; + char dst_dex[PKG_PATH_MAX]; + + if (validate_apk_path(src)) return -1; + if (validate_apk_path(dst)) return -1; + + if (create_cache_path(src_dex, src)) return -1; + if (create_cache_path(dst_dex, dst)) return -1; + + ALOGV("move %s -> %s\n", src_dex, dst_dex); + if (rename(src_dex, dst_dex) < 0) { + ALOGE("Couldn't move %s: %s\n", src_dex, strerror(errno)); + return -1; + } else { + return 0; + } +} + +int rm_dex(const char *path) +{ + char dex_path[PKG_PATH_MAX]; + + if (validate_apk_path(path)) return -1; + if (create_cache_path(dex_path, path)) return -1; + + ALOGV("unlink %s\n", dex_path); + if (unlink(dex_path) < 0) { + ALOGE("Couldn't unlink %s: %s\n", dex_path, strerror(errno)); + return -1; + } else { + return 0; + } +} + +int get_size(const char *pkgname, int persona, const char *apkpath, + const char *fwdlock_apkpath, const char *asecpath, + int64_t *_codesize, int64_t *_datasize, int64_t *_cachesize, + int64_t* _asecsize) +{ + DIR *d; + int dfd; + struct dirent *de; + struct stat s; + char path[PKG_PATH_MAX]; + + int64_t codesize = 0; + int64_t datasize = 0; + int64_t cachesize = 0; + int64_t asecsize = 0; + + /* count the source apk as code -- but only if it's not + * on the /system partition and its not on the sdcard. + */ + if (validate_system_app_path(apkpath) && + strncmp(apkpath, android_asec_dir.path, android_asec_dir.len) != 0) { + if (stat(apkpath, &s) == 0) { + codesize += stat_size(&s); + } + } + /* count the forward locked apk as code if it is given + */ + if (fwdlock_apkpath != NULL && fwdlock_apkpath[0] != '!') { + if (stat(fwdlock_apkpath, &s) == 0) { + codesize += stat_size(&s); + } + } + /* count the cached dexfile as code */ + if (!create_cache_path(path, apkpath)) { + if (stat(path, &s) == 0) { + codesize += stat_size(&s); + } + } + + /* add in size of any libraries */ + if (!create_pkg_path_in_dir(path, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) { + d = opendir(path); + if (d != NULL) { + dfd = dirfd(d); + codesize += calculate_dir_size(dfd); + closedir(d); + } + } + + /* compute asec size if it is given + */ + if (asecpath != NULL && asecpath[0] != '!') { + if (stat(asecpath, &s) == 0) { + asecsize += stat_size(&s); + } + } + + if (create_pkg_path(path, pkgname, PKG_DIR_POSTFIX, persona)) { + goto done; + } + + d = opendir(path); + if (d == NULL) { + goto done; + } + dfd = dirfd(d); + + /* most stuff in the pkgdir is data, except for the "cache" + * directory and below, which is cache, and the "lib" directory + * and below, which is code... + */ + while ((de = readdir(d))) { + const char *name = de->d_name; + + if (de->d_type == DT_DIR) { + int subfd; + int64_t statsize = 0; + int64_t dirsize = 0; + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) { + statsize = stat_size(&s); + } + subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY); + if (subfd >= 0) { + dirsize = calculate_dir_size(subfd); + } + if(!strcmp(name,"lib")) { + codesize += dirsize + statsize; + } else if(!strcmp(name,"cache")) { + cachesize += dirsize + statsize; + } else { + datasize += dirsize + statsize; + } + } else if (de->d_type == DT_LNK && !strcmp(name,"lib")) { + // This is the symbolic link to the application's library + // code. We'll count this as code instead of data, since + // it is not something that the app creates. + if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) { + codesize += stat_size(&s); + } + } else { + if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) { + datasize += stat_size(&s); + } + } + } + closedir(d); +done: + *_codesize = codesize; + *_datasize = datasize; + *_cachesize = cachesize; + *_asecsize = asecsize; + return 0; +} + + +/* a simpler version of dexOptGenerateCacheFileName() */ +int create_cache_path(char path[PKG_PATH_MAX], const char *src) +{ + char *tmp; + int srclen; + int dstlen; + + srclen = strlen(src); + + /* demand that we are an absolute path */ + if ((src == 0) || (src[0] != '/') || strstr(src,"..")) { + return -1; + } + + if (srclen > PKG_PATH_MAX) { // XXX: PKG_NAME_MAX? + return -1; + } + + dstlen = srclen + strlen(DALVIK_CACHE_PREFIX) + + strlen(DALVIK_CACHE_POSTFIX) + 1; + + if (dstlen > PKG_PATH_MAX) { + return -1; + } + + sprintf(path,"%s%s%s", + DALVIK_CACHE_PREFIX, + src + 1, /* skip the leading / */ + DALVIK_CACHE_POSTFIX); + + for(tmp = path + strlen(DALVIK_CACHE_PREFIX); *tmp; tmp++) { + if (*tmp == '/') { + *tmp = '@'; + } + } + + return 0; +} + +static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name, + const char* dexopt_flags) +{ + static const char* DEX_OPT_BIN = "/system/bin/dexopt"; + static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig + char zip_num[MAX_INT_LEN]; + char odex_num[MAX_INT_LEN]; + + sprintf(zip_num, "%d", zip_fd); + sprintf(odex_num, "%d", odex_fd); + + execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name, + dexopt_flags, (char*) NULL); + ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno)); +} + +static int wait_dexopt(pid_t pid, const char* apk_path) +{ + int status; + pid_t got_pid; + + /* + * Wait for the optimization process to finish. + */ + while (1) { + got_pid = waitpid(pid, &status, 0); + if (got_pid == -1 && errno == EINTR) { + printf("waitpid interrupted, retrying\n"); + } else { + break; + } + } + if (got_pid != pid) { + ALOGW("waitpid failed: wanted %d, got %d: %s\n", + (int) pid, (int) got_pid, strerror(errno)); + return 1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + ALOGV("DexInv: --- END '%s' (success) ---\n", apk_path); + return 0; + } else { + ALOGW("DexInv: --- END '%s' --- status=0x%04x, process failed\n", + apk_path, status); + return status; /* always nonzero */ + } +} + +int dexopt(const char *apk_path, uid_t uid, int is_public) +{ + struct utimbuf ut; + struct stat apk_stat, dex_stat; + char dex_path[PKG_PATH_MAX]; + char dexopt_flags[PROPERTY_VALUE_MAX]; + char *end; + int res, zip_fd=-1, odex_fd=-1; + + /* Before anything else: is there a .odex file? If so, we have + * pre-optimized the apk and there is nothing to do here. + */ + if (strlen(apk_path) >= (PKG_PATH_MAX - 8)) { + return -1; + } + + /* platform-specific flags affecting optimization and verification */ + property_get("dalvik.vm.dexopt-flags", dexopt_flags, ""); + + strcpy(dex_path, apk_path); + end = strrchr(dex_path, '.'); + if (end != NULL) { + strcpy(end, ".odex"); + if (stat(dex_path, &dex_stat) == 0) { + return 0; + } + } + + if (create_cache_path(dex_path, apk_path)) { + return -1; + } + + memset(&apk_stat, 0, sizeof(apk_stat)); + stat(apk_path, &apk_stat); + + zip_fd = open(apk_path, O_RDONLY, 0); + if (zip_fd < 0) { + ALOGE("dexopt cannot open '%s' for input\n", apk_path); + return -1; + } + + unlink(dex_path); + odex_fd = open(dex_path, O_RDWR | O_CREAT | O_EXCL, 0644); + if (odex_fd < 0) { + ALOGE("dexopt cannot open '%s' for output\n", dex_path); + goto fail; + } + if (fchmod(odex_fd, + S_IRUSR|S_IWUSR|S_IRGRP | + (is_public ? S_IROTH : 0)) < 0) { + ALOGE("dexopt cannot chmod '%s'\n", dex_path); + goto fail; + } + if (fchown(odex_fd, AID_SYSTEM, uid) < 0) { + ALOGE("dexopt cannot chown '%s'\n", dex_path); + goto fail; + } + + ALOGV("DexInv: --- BEGIN '%s' ---\n", apk_path); + + pid_t pid; + pid = fork(); + if (pid == 0) { + /* child -- drop privileges before continuing */ + if (setgid(uid) != 0) { + ALOGE("setgid(%d) failed during dexopt\n", uid); + exit(64); + } + if (setuid(uid) != 0) { + ALOGE("setuid(%d) during dexopt\n", uid); + exit(65); + } + // drop capabilities + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata[2]; + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + capheader.version = _LINUX_CAPABILITY_VERSION_3; + if (capset(&capheader, &capdata[0]) < 0) { + ALOGE("capset failed: %s\n", strerror(errno)); + exit(66); + } + if (flock(odex_fd, LOCK_EX | LOCK_NB) != 0) { + ALOGE("flock(%s) failed: %s\n", dex_path, strerror(errno)); + exit(67); + } + + run_dexopt(zip_fd, odex_fd, apk_path, dexopt_flags); + exit(68); /* only get here on exec failure */ + } else { + res = wait_dexopt(pid, apk_path); + if (res != 0) { + ALOGE("dexopt failed on '%s' res = %d\n", dex_path, res); + goto fail; + } + } + + ut.actime = apk_stat.st_atime; + ut.modtime = apk_stat.st_mtime; + utime(dex_path, &ut); + + close(odex_fd); + close(zip_fd); + return 0; + +fail: + if (odex_fd >= 0) { + close(odex_fd); + unlink(dex_path); + } + if (zip_fd >= 0) { + close(zip_fd); + } + return -1; +} + +void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid, + struct stat* statbuf) +{ + while (path[basepos] != 0) { + if (path[basepos] == '/') { + path[basepos] = 0; + if (lstat(path, statbuf) < 0) { + ALOGV("Making directory: %s\n", path); + if (mkdir(path, mode) == 0) { + chown(path, uid, gid); + } else { + ALOGW("Unable to make directory %s: %s\n", path, strerror(errno)); + } + } + path[basepos] = '/'; + basepos++; + } + basepos++; + } +} + +int movefileordir(char* srcpath, char* dstpath, int dstbasepos, + int dstuid, int dstgid, struct stat* statbuf) +{ + DIR *d; + struct dirent *de; + int res; + + int srcend = strlen(srcpath); + int dstend = strlen(dstpath); + + if (lstat(srcpath, statbuf) < 0) { + ALOGW("Unable to stat %s: %s\n", srcpath, strerror(errno)); + return 1; + } + + if ((statbuf->st_mode&S_IFDIR) == 0) { + mkinnerdirs(dstpath, dstbasepos, S_IRWXU|S_IRWXG|S_IXOTH, + dstuid, dstgid, statbuf); + ALOGV("Renaming %s to %s (uid %d)\n", srcpath, dstpath, dstuid); + if (rename(srcpath, dstpath) >= 0) { + if (chown(dstpath, dstuid, dstgid) < 0) { + ALOGE("cannot chown %s: %s\n", dstpath, strerror(errno)); + unlink(dstpath); + return 1; + } + } else { + ALOGW("Unable to rename %s to %s: %s\n", + srcpath, dstpath, strerror(errno)); + return 1; + } + return 0; + } + + d = opendir(srcpath); + if (d == NULL) { + ALOGW("Unable to opendir %s: %s\n", srcpath, strerror(errno)); + return 1; + } + + res = 0; + + while ((de = readdir(d))) { + const char *name = de->d_name; + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + + if ((srcend+strlen(name)) >= (PKG_PATH_MAX-2)) { + ALOGW("Source path too long; skipping: %s/%s\n", srcpath, name); + continue; + } + + if ((dstend+strlen(name)) >= (PKG_PATH_MAX-2)) { + ALOGW("Destination path too long; skipping: %s/%s\n", dstpath, name); + continue; + } + + srcpath[srcend] = dstpath[dstend] = '/'; + strcpy(srcpath+srcend+1, name); + strcpy(dstpath+dstend+1, name); + + if (movefileordir(srcpath, dstpath, dstbasepos, dstuid, dstgid, statbuf) != 0) { + res = 1; + } + + // Note: we will be leaving empty directories behind in srcpath, + // but that is okay, the package manager will be erasing all of the + // data associated with .apks that disappear. + + srcpath[srcend] = dstpath[dstend] = 0; + } + + closedir(d); + return res; +} + +int movefiles() +{ + DIR *d; + int dfd, subfd; + struct dirent *de; + struct stat s; + char buf[PKG_PATH_MAX+1]; + int bufp, bufe, bufi, readlen; + + char srcpkg[PKG_NAME_MAX]; + char dstpkg[PKG_NAME_MAX]; + char srcpath[PKG_PATH_MAX]; + char dstpath[PKG_PATH_MAX]; + int dstuid=-1, dstgid=-1; + int hasspace; + + d = opendir(UPDATE_COMMANDS_DIR_PREFIX); + if (d == NULL) { + goto done; + } + dfd = dirfd(d); + + /* Iterate through all files in the directory, executing the + * file movements requested there-in. + */ + while ((de = readdir(d))) { + const char *name = de->d_name; + + if (de->d_type == DT_DIR) { + continue; + } else { + subfd = openat(dfd, name, O_RDONLY); + if (subfd < 0) { + ALOGW("Unable to open update commands at %s%s\n", + UPDATE_COMMANDS_DIR_PREFIX, name); + continue; + } + + bufp = 0; + bufe = 0; + buf[PKG_PATH_MAX] = 0; + srcpkg[0] = dstpkg[0] = 0; + while (1) { + bufi = bufp; + while (bufi < bufe && buf[bufi] != '\n') { + bufi++; + } + if (bufi < bufe) { + buf[bufi] = 0; + ALOGV("Processing line: %s\n", buf+bufp); + hasspace = 0; + while (bufp < bufi && isspace(buf[bufp])) { + hasspace = 1; + bufp++; + } + if (buf[bufp] == '#' || bufp == bufi) { + // skip comments and empty lines. + } else if (hasspace) { + if (dstpkg[0] == 0) { + ALOGW("Path before package line in %s%s: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp); + } else if (srcpkg[0] == 0) { + // Skip -- source package no longer exists. + } else { + ALOGV("Move file: %s (from %s to %s)\n", buf+bufp, srcpkg, dstpkg); + if (!create_move_path(srcpath, srcpkg, buf+bufp, 0) && + !create_move_path(dstpath, dstpkg, buf+bufp, 0)) { + movefileordir(srcpath, dstpath, + strlen(dstpath)-strlen(buf+bufp), + dstuid, dstgid, &s); + } + } + } else { + char* div = strchr(buf+bufp, ':'); + if (div == NULL) { + ALOGW("Bad package spec in %s%s; no ':' sep: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp); + } else { + *div = 0; + div++; + if (strlen(buf+bufp) < PKG_NAME_MAX) { + strcpy(dstpkg, buf+bufp); + } else { + srcpkg[0] = dstpkg[0] = 0; + ALOGW("Package name too long in %s%s: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp); + } + if (strlen(div) < PKG_NAME_MAX) { + strcpy(srcpkg, div); + } else { + srcpkg[0] = dstpkg[0] = 0; + ALOGW("Package name too long in %s%s: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, div); + } + if (srcpkg[0] != 0) { + if (!create_pkg_path(srcpath, srcpkg, PKG_DIR_POSTFIX, 0)) { + if (lstat(srcpath, &s) < 0) { + // Package no longer exists -- skip. + srcpkg[0] = 0; + } + } else { + srcpkg[0] = 0; + ALOGW("Can't create path %s in %s%s\n", + div, UPDATE_COMMANDS_DIR_PREFIX, name); + } + if (srcpkg[0] != 0) { + if (!create_pkg_path(dstpath, dstpkg, PKG_DIR_POSTFIX, 0)) { + if (lstat(dstpath, &s) == 0) { + dstuid = s.st_uid; + dstgid = s.st_gid; + } else { + // Destination package doesn't + // exist... due to original-package, + // this is normal, so don't be + // noisy about it. + srcpkg[0] = 0; + } + } else { + srcpkg[0] = 0; + ALOGW("Can't create path %s in %s%s\n", + div, UPDATE_COMMANDS_DIR_PREFIX, name); + } + } + ALOGV("Transfering from %s to %s: uid=%d\n", + srcpkg, dstpkg, dstuid); + } + } + } + bufp = bufi+1; + } else { + if (bufp == 0) { + if (bufp < bufe) { + ALOGW("Line too long in %s%s, skipping: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, buf); + } + } else if (bufp < bufe) { + memcpy(buf, buf+bufp, bufe-bufp); + bufe -= bufp; + bufp = 0; + } + readlen = read(subfd, buf+bufe, PKG_PATH_MAX-bufe); + if (readlen < 0) { + ALOGW("Failure reading update commands in %s%s: %s\n", + UPDATE_COMMANDS_DIR_PREFIX, name, strerror(errno)); + break; + } else if (readlen == 0) { + break; + } + bufe += readlen; + buf[bufe] = 0; + ALOGV("Read buf: %s\n", buf); + } + } + close(subfd); + } + } + closedir(d); +done: + return 0; +} + +int linklib(const char* pkgname, const char* asecLibDir, int userId) +{ + char pkgdir[PKG_PATH_MAX]; + char libsymlink[PKG_PATH_MAX]; + struct stat s, libStat; + int rc = 0; + + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, userId)) { + ALOGE("cannot create package path\n"); + return -1; + } + if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, userId)) { + ALOGE("cannot create package lib symlink origin path\n"); + return -1; + } + + if (stat(pkgdir, &s) < 0) return -1; + + if (chown(pkgdir, AID_INSTALL, AID_INSTALL) < 0) { + ALOGE("failed to chown '%s': %s\n", pkgdir, strerror(errno)); + return -1; + } + + if (chmod(pkgdir, 0700) < 0) { + ALOGE("linklib() 1: failed to chmod '%s': %s\n", pkgdir, strerror(errno)); + rc = -1; + goto out; + } + + if (lstat(libsymlink, &libStat) < 0) { + if (errno != ENOENT) { + ALOGE("couldn't stat lib dir: %s\n", strerror(errno)); + rc = -1; + goto out; + } + } else { + if (S_ISDIR(libStat.st_mode)) { + if (delete_dir_contents(libsymlink, 1, 0) < 0) { + rc = -1; + goto out; + } + } else if (S_ISLNK(libStat.st_mode)) { + if (unlink(libsymlink) < 0) { + ALOGE("couldn't unlink lib dir: %s\n", strerror(errno)); + rc = -1; + goto out; + } + } + } + + if (symlink(asecLibDir, libsymlink) < 0) { + ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, asecLibDir, + strerror(errno)); + rc = -errno; + goto out; + } + +out: + if (chmod(pkgdir, s.st_mode) < 0) { + ALOGE("linklib() 2: failed to chmod '%s': %s\n", pkgdir, strerror(errno)); + rc = -errno; + } + + if (chown(pkgdir, s.st_uid, s.st_gid) < 0) { + ALOGE("failed to chown '%s' : %s\n", pkgdir, strerror(errno)); + return -errno; + } + + return rc; +} diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c new file mode 100644 index 0000000..230899b --- /dev/null +++ b/cmds/installd/installd.c @@ -0,0 +1,586 @@ +/* +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <sys/capability.h> +#include <linux/prctl.h> + +#include "installd.h" + + +#define BUFFER_MAX 1024 /* input buffer for commands */ +#define TOKEN_MAX 8 /* max number of arguments in buffer */ +#define REPLY_MAX 256 /* largest reply allowed */ + +static int do_ping(char **arg, char reply[REPLY_MAX]) +{ + return 0; +} + +static int do_install(char **arg, char reply[REPLY_MAX]) +{ + return install(arg[0], atoi(arg[1]), atoi(arg[2]), arg[3]); /* pkgname, uid, gid, seinfo */ +} + +static int do_dexopt(char **arg, char reply[REPLY_MAX]) +{ + /* apk_path, uid, is_public */ + return dexopt(arg[0], atoi(arg[1]), atoi(arg[2])); +} + +static int do_move_dex(char **arg, char reply[REPLY_MAX]) +{ + return move_dex(arg[0], arg[1]); /* src, dst */ +} + +static int do_rm_dex(char **arg, char reply[REPLY_MAX]) +{ + return rm_dex(arg[0]); /* pkgname */ +} + +static int do_remove(char **arg, char reply[REPLY_MAX]) +{ + return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_rename(char **arg, char reply[REPLY_MAX]) +{ + return renamepkg(arg[0], arg[1]); /* oldpkgname, newpkgname */ +} + +static int do_fixuid(char **arg, char reply[REPLY_MAX]) +{ + return fix_uid(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, gid */ +} + +static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_size */ +{ + return free_cache((int64_t)atoll(arg[0])); /* free_size */ +} + +static int do_rm_cache(char **arg, char reply[REPLY_MAX]) +{ + return delete_cache(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_get_size(char **arg, char reply[REPLY_MAX]) +{ + int64_t codesize = 0; + int64_t datasize = 0; + int64_t cachesize = 0; + int64_t asecsize = 0; + int res = 0; + + /* pkgdir, persona, apkpath */ + res = get_size(arg[0], atoi(arg[1]), arg[2], arg[3], arg[4], + &codesize, &datasize, &cachesize, &asecsize); + + /* + * Each int64_t can take up 22 characters printed out. Make sure it + * doesn't go over REPLY_MAX in the future. + */ + snprintf(reply, REPLY_MAX, "%" PRId64 " %" PRId64 " %" PRId64 " %" PRId64, + codesize, datasize, cachesize, asecsize); + return res; +} + +static int do_rm_user_data(char **arg, char reply[REPLY_MAX]) +{ + return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_mk_user_data(char **arg, char reply[REPLY_MAX]) +{ + return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */ +} + +static int do_rm_user(char **arg, char reply[REPLY_MAX]) +{ + return delete_persona(atoi(arg[0])); /* userid */ +} + +static int do_movefiles(char **arg, char reply[REPLY_MAX]) +{ + return movefiles(); +} + +static int do_linklib(char **arg, char reply[REPLY_MAX]) +{ + return linklib(arg[0], arg[1], atoi(arg[2])); +} + +struct cmdinfo { + const char *name; + unsigned numargs; + int (*func)(char **arg, char reply[REPLY_MAX]); +}; + +struct cmdinfo cmds[] = { + { "ping", 0, do_ping }, + { "install", 4, do_install }, + { "dexopt", 3, do_dexopt }, + { "movedex", 2, do_move_dex }, + { "rmdex", 1, do_rm_dex }, + { "remove", 2, do_remove }, + { "rename", 2, do_rename }, + { "fixuid", 3, do_fixuid }, + { "freecache", 1, do_free_cache }, + { "rmcache", 2, do_rm_cache }, + { "getsize", 5, do_get_size }, + { "rmuserdata", 2, do_rm_user_data }, + { "movefiles", 0, do_movefiles }, + { "linklib", 3, do_linklib }, + { "mkuserdata", 3, do_mk_user_data }, + { "rmuser", 1, do_rm_user }, +}; + +static int readx(int s, void *_buf, int count) +{ + char *buf = _buf; + int n = 0, r; + if (count < 0) return -1; + while (n < count) { + r = read(s, buf + n, count - n); + if (r < 0) { + if (errno == EINTR) continue; + ALOGE("read error: %s\n", strerror(errno)); + return -1; + } + if (r == 0) { + ALOGE("eof\n"); + return -1; /* EOF */ + } + n += r; + } + return 0; +} + +static int writex(int s, const void *_buf, int count) +{ + const char *buf = _buf; + int n = 0, r; + if (count < 0) return -1; + while (n < count) { + r = write(s, buf + n, count - n); + if (r < 0) { + if (errno == EINTR) continue; + ALOGE("write error: %s\n", strerror(errno)); + return -1; + } + n += r; + } + return 0; +} + + +/* Tokenize the command buffer, locate a matching command, + * ensure that the required number of arguments are provided, + * call the function(), return the result. + */ +static int execute(int s, char cmd[BUFFER_MAX]) +{ + char reply[REPLY_MAX]; + char *arg[TOKEN_MAX+1]; + unsigned i; + unsigned n = 0; + unsigned short count; + int ret = -1; + +// ALOGI("execute('%s')\n", cmd); + + /* default reply is "" */ + reply[0] = 0; + + /* n is number of args (not counting arg[0]) */ + arg[0] = cmd; + while (*cmd) { + if (isspace(*cmd)) { + *cmd++ = 0; + n++; + arg[n] = cmd; + if (n == TOKEN_MAX) { + ALOGE("too many arguments\n"); + goto done; + } + } + cmd++; + } + + for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) { + if (!strcmp(cmds[i].name,arg[0])) { + if (n != cmds[i].numargs) { + ALOGE("%s requires %d arguments (%d given)\n", + cmds[i].name, cmds[i].numargs, n); + } else { + ret = cmds[i].func(arg + 1, reply); + } + goto done; + } + } + ALOGE("unsupported command '%s'\n", arg[0]); + +done: + if (reply[0]) { + n = snprintf(cmd, BUFFER_MAX, "%d %s", ret, reply); + } else { + n = snprintf(cmd, BUFFER_MAX, "%d", ret); + } + if (n > BUFFER_MAX) n = BUFFER_MAX; + count = n; + +// ALOGI("reply: '%s'\n", cmd); + if (writex(s, &count, sizeof(count))) return -1; + if (writex(s, cmd, count)) return -1; + return 0; +} + +/** + * Initialize all the global variables that are used elsewhere. Returns 0 upon + * success and -1 on error. + */ +void free_globals() { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + if (android_system_dirs.dirs[i].path != NULL) { + free(android_system_dirs.dirs[i].path); + } + } + + free(android_system_dirs.dirs); +} + +int initialize_globals() { + // Get the android data directory. + if (get_path_from_env(&android_data_dir, "ANDROID_DATA") < 0) { + return -1; + } + + // Get the android app directory. + if (copy_and_append(&android_app_dir, &android_data_dir, APP_SUBDIR) < 0) { + return -1; + } + + // Get the android protected app directory. + if (copy_and_append(&android_app_private_dir, &android_data_dir, PRIVATE_APP_SUBDIR) < 0) { + return -1; + } + + // Get the android app native library directory. + if (copy_and_append(&android_app_lib_dir, &android_data_dir, APP_LIB_SUBDIR) < 0) { + return -1; + } + + // Get the sd-card ASEC mount point. + if (get_path_from_env(&android_asec_dir, "ASEC_MOUNTPOINT") < 0) { + return -1; + } + + // Get the android media directory. + if (copy_and_append(&android_media_dir, &android_data_dir, MEDIA_SUBDIR) < 0) { + return -1; + } + + // Take note of the system and vendor directories. + android_system_dirs.count = 2; + + android_system_dirs.dirs = calloc(android_system_dirs.count, sizeof(dir_rec_t)); + if (android_system_dirs.dirs == NULL) { + ALOGE("Couldn't allocate array for dirs; aborting\n"); + return -1; + } + + // system + if (get_path_from_env(&android_system_dirs.dirs[0], "ANDROID_ROOT") < 0) { + free_globals(); + return -1; + } + + // append "app/" to dirs[0] + char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR); + android_system_dirs.dirs[0].path = system_app_path; + android_system_dirs.dirs[0].len = strlen(system_app_path); + + // vendor + // TODO replace this with an environment variable (doesn't exist yet) + android_system_dirs.dirs[1].path = "/vendor/app/"; + android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path); + + return 0; +} + +int initialize_directories() { + int res = -1; + + // Read current filesystem layout version to handle upgrade paths + char version_path[PATH_MAX]; + snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path); + + int oldVersion; + if (fs_read_atomic_int(version_path, &oldVersion) == -1) { + oldVersion = 0; + } + int version = oldVersion; + + // /data/user + char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX); + // /data/data + char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX); + // /data/user/0 + char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, "0"); + if (!user_data_dir || !legacy_data_dir || !primary_data_dir) { + goto fail; + } + + // Make the /data/user directory if necessary + if (access(user_data_dir, R_OK) < 0) { + if (mkdir(user_data_dir, 0711) < 0) { + goto fail; + } + if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) { + goto fail; + } + if (chmod(user_data_dir, 0711) < 0) { + goto fail; + } + } + // Make the /data/user/0 symlink to /data/data if necessary + if (access(primary_data_dir, R_OK) < 0) { + if (symlink(legacy_data_dir, primary_data_dir)) { + goto fail; + } + } + + if (version == 0) { + // Introducing multi-user, so migrate /data/media contents into /data/media/0 + ALOGD("Upgrading /data/media for multi-user"); + + // Ensure /data/media + if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + goto fail; + } + + // /data/media.tmp + char media_tmp_dir[PATH_MAX]; + snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path); + + // Only copy when upgrade not already in progress + if (access(media_tmp_dir, F_OK) == -1) { + if (rename(android_media_dir.path, media_tmp_dir) == -1) { + ALOGE("Failed to move legacy media path: %s", strerror(errno)); + goto fail; + } + } + + // Create /data/media again + if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + goto fail; + } + + // /data/media/0 + char owner_media_dir[PATH_MAX]; + snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path); + + // Move any owner data into place + if (access(media_tmp_dir, F_OK) == 0) { + if (rename(media_tmp_dir, owner_media_dir) == -1) { + ALOGE("Failed to move owner media path: %s", strerror(errno)); + goto fail; + } + } + + // Ensure media directories for any existing users + DIR *dir; + struct dirent *dirent; + char user_media_dir[PATH_MAX]; + + dir = opendir(user_data_dir); + if (dir != NULL) { + while ((dirent = readdir(dir))) { + if (dirent->d_type == DT_DIR) { + const char *name = dirent->d_name; + + // skip "." and ".." + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + + // /data/media/<user_id> + snprintf(user_media_dir, PATH_MAX, "%s%s", android_media_dir.path, name); + if (fs_prepare_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + goto fail; + } + } + } + closedir(dir); + } + + version = 1; + } + + // /data/media/obb + char media_obb_dir[PATH_MAX]; + snprintf(media_obb_dir, PATH_MAX, "%sobb", android_media_dir.path); + + if (version == 1) { + // Introducing /data/media/obb for sharing OBB across users; migrate + // any existing OBB files from owner. + ALOGD("Upgrading to shared /data/media/obb"); + + // /data/media/0/Android/obb + char owner_obb_path[PATH_MAX]; + snprintf(owner_obb_path, PATH_MAX, "%s0/Android/obb", android_media_dir.path); + + // Only move if target doesn't already exist + if (access(media_obb_dir, F_OK) != 0 && access(owner_obb_path, F_OK) == 0) { + if (rename(owner_obb_path, media_obb_dir) == -1) { + ALOGE("Failed to move OBB from owner: %s", strerror(errno)); + goto fail; + } + } + + version = 2; + } + + if (ensure_media_user_dirs(0) == -1) { + ALOGE("Failed to setup media for user 0"); + goto fail; + } + if (fs_prepare_dir(media_obb_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + goto fail; + } + + // Persist layout version if changed + if (version != oldVersion) { + if (fs_write_atomic_int(version_path, version) == -1) { + ALOGE("Failed to save version to %s: %s", version_path, strerror(errno)); + goto fail; + } + } + + // Success! + res = 0; + +fail: + free(user_data_dir); + free(legacy_data_dir); + free(primary_data_dir); + return res; +} + +static void drop_privileges() { + if (prctl(PR_SET_KEEPCAPS, 1) < 0) { + ALOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno)); + exit(1); + } + + if (setgid(AID_INSTALL) < 0) { + ALOGE("setgid() can't drop privileges; exiting.\n"); + exit(1); + } + + if (setuid(AID_INSTALL) < 0) { + ALOGE("setuid() can't drop privileges; exiting.\n"); + exit(1); + } + + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata[2]; + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + capheader.version = _LINUX_CAPABILITY_VERSION_3; + capheader.pid = 0; + + capdata[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE); + capdata[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); + capdata[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); + capdata[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); + + capdata[0].effective = capdata[0].permitted; + capdata[1].effective = capdata[1].permitted; + capdata[0].inheritable = 0; + capdata[1].inheritable = 0; + + if (capset(&capheader, &capdata[0]) < 0) { + ALOGE("capset failed: %s\n", strerror(errno)); + exit(1); + } +} + +int main(const int argc, const char *argv[]) { + char buf[BUFFER_MAX]; + struct sockaddr addr; + socklen_t alen; + int lsocket, s, count; + + ALOGI("installd firing up\n"); + + if (initialize_globals() < 0) { + ALOGE("Could not initialize globals; exiting.\n"); + exit(1); + } + + if (initialize_directories() < 0) { + ALOGE("Could not create directories; exiting.\n"); + exit(1); + } + + drop_privileges(); + + lsocket = android_get_control_socket(SOCKET_PATH); + if (lsocket < 0) { + ALOGE("Failed to get socket from environment: %s\n", strerror(errno)); + exit(1); + } + if (listen(lsocket, 5)) { + ALOGE("Listen on socket failed: %s\n", strerror(errno)); + exit(1); + } + fcntl(lsocket, F_SETFD, FD_CLOEXEC); + + for (;;) { + alen = sizeof(addr); + s = accept(lsocket, &addr, &alen); + if (s < 0) { + ALOGE("Accept failed: %s\n", strerror(errno)); + continue; + } + fcntl(s, F_SETFD, FD_CLOEXEC); + + ALOGI("new connection\n"); + for (;;) { + unsigned short count; + if (readx(s, &count, sizeof(count))) { + ALOGE("failed to read size\n"); + break; + } + if ((count < 1) || (count >= BUFFER_MAX)) { + ALOGE("invalid size %d\n", count); + break; + } + if (readx(s, buf, count)) { + ALOGE("failed to read command\n"); + break; + } + buf[count] = 0; + if (execute(s, buf)) break; + } + ALOGI("closing connection\n"); + close(s); + } + + return 0; +} diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h new file mode 100644 index 0000000..033d5a3 --- /dev/null +++ b/cmds/installd/installd.h @@ -0,0 +1,212 @@ +/* +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "installd" + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <utime.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <cutils/fs.h> +#include <cutils/sockets.h> +#include <cutils/log.h> +#include <cutils/properties.h> +#include <cutils/multiuser.h> + +#include <private/android_filesystem_config.h> + +#if INCLUDE_SYS_MOUNT_FOR_STATFS +#include <sys/mount.h> +#else +#include <sys/statfs.h> +#endif + +#define SOCKET_PATH "installd" + + +/* elements combined with a valid package name to form paths */ + +#define PRIMARY_USER_PREFIX "data/" +#define SECONDARY_USER_PREFIX "user/" + +#define PKG_DIR_POSTFIX "" + +#define PKG_LIB_POSTFIX "/lib" + +#define CACHE_DIR_POSTFIX "/cache" + +#define APP_SUBDIR "app/" // sub-directory under ANDROID_DATA + +#define APP_LIB_SUBDIR "app-lib/" // sub-directory under ANDROID_DATA + +#define MEDIA_SUBDIR "media/" // sub-directory under ANDROID_DATA + +/* other handy constants */ + +#define PRIVATE_APP_SUBDIR "app-private/" // sub-directory under ANDROID_DATA + +#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" +#define DALVIK_CACHE_POSTFIX "/classes.dex" + +#define UPDATE_COMMANDS_DIR_PREFIX "/system/etc/updatecmds/" + +#define PKG_NAME_MAX 128 /* largest allowed package name */ +#define PKG_PATH_MAX 256 /* max size of any path we use */ + +#define PER_USER_RANGE ((uid_t)100000) /* range of uids per user + uid = persona * PER_USER_RANGE + appid */ + +/* data structures */ + +typedef struct { + char* path; + size_t len; +} dir_rec_t; + +typedef struct { + size_t count; + dir_rec_t* dirs; +} dir_rec_array_t; + +extern dir_rec_t android_app_dir; +extern dir_rec_t android_app_private_dir; +extern dir_rec_t android_app_lib_dir; +extern dir_rec_t android_data_dir; +extern dir_rec_t android_asec_dir; +extern dir_rec_t android_media_dir; +extern dir_rec_array_t android_system_dirs; + +typedef struct cache_dir_struct { + struct cache_dir_struct* parent; + int32_t childCount; + int32_t hiddenCount; + int32_t deleted; + char name[]; +} cache_dir_t; + +typedef struct { + cache_dir_t* dir; + time_t modTime; + char name[]; +} cache_file_t; + +typedef struct { + size_t numDirs; + size_t availDirs; + cache_dir_t** dirs; + size_t numFiles; + size_t availFiles; + cache_file_t** files; + size_t numCollected; + void* memBlocks; + int8_t* curMemBlockAvail; + int8_t* curMemBlockEnd; +} cache_t; + +/* util.c */ + +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix); + +int create_pkg_path(char path[PKG_PATH_MAX], + const char *pkgname, + const char *postfix, + uid_t persona); + +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona); + +int create_persona_media_path(char path[PKG_PATH_MAX], userid_t userid); + +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona); + +int is_valid_package_name(const char* pkgname); + +int create_cache_path(char path[PKG_PATH_MAX], const char *src); + +int delete_dir_contents(const char *pathname, + int also_delete_dir, + const char *ignore); + +int delete_dir_contents_fd(int dfd, const char *name); + +int lookup_media_dir(char basepath[PATH_MAX], const char *dir); + +int64_t data_disk_free(); + +cache_t* start_cache_collection(); + +void add_cache_files(cache_t* cache, const char *basepath, const char *cachedir); + +void clear_cache_files(cache_t* cache, int64_t free_size); + +void finish_cache_collection(cache_t* cache); + +int validate_system_app_path(const char* path); + +int get_path_from_env(dir_rec_t* rec, const char* var); + +int get_path_from_string(dir_rec_t* rec, const char* path); + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix); + +int validate_apk_path(const char *path); + +int append_and_increment(char** dst, const char* src, size_t* dst_size); + +char *build_string2(char *s1, char *s2); +char *build_string3(char *s1, char *s2, char *s3); + +int ensure_dir(const char* path, mode_t mode, uid_t uid, gid_t gid); +int ensure_media_user_dirs(userid_t userid); + +/* commands.c */ + +int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo); +int uninstall(const char *pkgname, uid_t persona); +int renamepkg(const char *oldpkgname, const char *newpkgname); +int fix_uid(const char *pkgname, uid_t uid, gid_t gid); +int delete_user_data(const char *pkgname, uid_t persona); +int make_user_data(const char *pkgname, uid_t uid, uid_t persona); +int delete_persona(uid_t persona); +int delete_cache(const char *pkgname, uid_t persona); +int move_dex(const char *src, const char *dst); +int rm_dex(const char *path); +int protect(char *pkgname, gid_t gid); +int get_size(const char *pkgname, int persona, const char *apkpath, const char *fwdlock_apkpath, + const char *asecpath, int64_t *codesize, int64_t *datasize, int64_t *cachesize, + int64_t *asecsize); +int free_cache(int64_t free_size); +int dexopt(const char *apk_path, uid_t uid, int is_public); +int movefiles(); +int linklib(const char* target, const char* source, int userId); diff --git a/cmds/installd/tests/Android.mk b/cmds/installd/tests/Android.mk new file mode 100644 index 0000000..c0192f4 --- /dev/null +++ b/cmds/installd/tests/Android.mk @@ -0,0 +1,31 @@ +# Build the unit tests for installd +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Build the unit tests. +test_src_files := \ + installd_utils_test.cpp + +shared_libraries := \ + libutils \ + libcutils \ + libstlport + +static_libraries := \ + libinstalld \ + libdiskusage \ + libgtest \ + libgtest_main + +c_includes := \ + frameworks/base/cmds/installd + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_NATIVE_TEST)) \ +) diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp new file mode 100644 index 0000000..7cb9b37 --- /dev/null +++ b/cmds/installd/tests/installd_utils_test.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2011 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> + +#define LOG_TAG "utils_test" +#include <utils/Log.h> + +#include <gtest/gtest.h> + +extern "C" { +#include "installd.h" +} + +#define TEST_DATA_DIR "/data/" +#define TEST_APP_DIR "/data/app/" +#define TEST_APP_PRIVATE_DIR "/data/app-private/" +#define TEST_ASEC_DIR "/mnt/asec/" + +#define TEST_SYSTEM_DIR1 "/system/app/" +#define TEST_SYSTEM_DIR2 "/vendor/app/" + +#define REALLY_LONG_APP_NAME "com.example." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +#define REALLY_LONG_LEAF_NAME "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" + +namespace android { + +class UtilsTest : public testing::Test { +protected: + virtual void SetUp() { + android_app_dir.path = TEST_APP_DIR; + android_app_dir.len = strlen(TEST_APP_DIR); + + android_app_private_dir.path = TEST_APP_PRIVATE_DIR; + android_app_private_dir.len = strlen(TEST_APP_PRIVATE_DIR); + + android_data_dir.path = TEST_DATA_DIR; + android_data_dir.len = strlen(TEST_DATA_DIR); + + android_asec_dir.path = TEST_ASEC_DIR; + android_asec_dir.len = strlen(TEST_ASEC_DIR); + + android_system_dirs.count = 2; + + android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t)); + android_system_dirs.dirs[0].path = TEST_SYSTEM_DIR1; + android_system_dirs.dirs[0].len = strlen(TEST_SYSTEM_DIR1); + + android_system_dirs.dirs[1].path = TEST_SYSTEM_DIR2; + android_system_dirs.dirs[1].len = strlen(TEST_SYSTEM_DIR2); + } + + virtual void TearDown() { + free(android_system_dirs.dirs); + } +}; + +TEST_F(UtilsTest, IsValidApkPath_BadPrefix) { + // Bad prefixes directories + const char *badprefix1 = "/etc/passwd"; + EXPECT_EQ(-1, validate_apk_path(badprefix1)) + << badprefix1 << " should be allowed as a valid path"; + + const char *badprefix2 = "../.." TEST_APP_DIR "../../../blah"; + EXPECT_EQ(-1, validate_apk_path(badprefix2)) + << badprefix2 << " should be allowed as a valid path"; + + const char *badprefix3 = "init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix3)) + << badprefix3 << " should be allowed as a valid path"; + + const char *badprefix4 = "/init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix4)) + << badprefix4 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Internal) { + // Internal directories + const char *internal1 = TEST_APP_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(internal1)) + << internal1 << " should be allowed as a valid path"; + + const char *badint1 = TEST_APP_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint1)) + << badint1 << " should be rejected as a invalid path"; + + const char *badint2 = TEST_APP_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint2)) + << badint2 << " should be rejected as a invalid path"; + + const char *badint3 = TEST_APP_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badint3)) + << badint3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Private) { + // Internal directories + const char *private1 = TEST_APP_PRIVATE_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(private1)) + << private1 << " should be allowed as a valid path"; + + const char *badpriv1 = TEST_APP_PRIVATE_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv1)) + << badpriv1 << " should be rejected as a invalid path"; + + const char *badpriv2 = TEST_APP_PRIVATE_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv2)) + << badpriv2 << " should be rejected as a invalid path"; + + const char *badpriv3 = TEST_APP_PRIVATE_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv3)) + << badpriv3 << " should be rejected as a invalid path"; +} + + +TEST_F(UtilsTest, IsValidApkPath_AsecGood1) { + const char *asec1 = TEST_ASEC_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(asec1)) + << asec1 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_AsecGood2) { + const char *asec2 = TEST_ASEC_DIR "com.example.asec/pkg.apk"; + EXPECT_EQ(0, validate_apk_path(asec2)) + << asec2 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_EscapeFail) { + const char *badasec1 = TEST_ASEC_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec1)) + << badasec1 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_DoubleSlashFail) { + const char *badasec2 = TEST_ASEC_DIR "com.example.asec//pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec2)) + << badasec2 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeFail) { + const char *badasec3 = TEST_ASEC_DIR "com.example.asec/../../../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec3)) + << badasec3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SlashEscapeFail) { + const char *badasec4 = TEST_ASEC_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec4)) + << badasec4 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_CrazyDirFail) { + const char *badasec5 = TEST_ASEC_DIR ".//../.."; + EXPECT_EQ(-1, validate_apk_path(badasec5)) + << badasec5 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeSingleFail) { + const char *badasec6 = TEST_ASEC_DIR "com.example.asec/../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec6)) + << badasec6 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_TwoSubdirFail) { + const char *badasec7 = TEST_ASEC_DIR "com.example.asec/subdir1/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec7)) + << badasec7 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir1) { + const char *sysapp1 = TEST_SYSTEM_DIR1 "Voice.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp1)) + << sysapp1 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir2) { + const char *sysapp2 = TEST_SYSTEM_DIR2 "com.example.myapp.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp2)) + << sysapp2 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_EscapeFail) { + const char *badapp1 = TEST_SYSTEM_DIR1 "../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp1)) + << badapp1 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_DoubleEscapeFail) { + const char *badapp2 = TEST_SYSTEM_DIR2 "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp2)) + << badapp2 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_BadPathEscapeFail) { + const char *badapp3 = TEST_APP_DIR "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp3)) + << badapp3 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, GetPathFromString_NullPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, (const char *) NULL)) + << "Should not allow NULL as a path."; +} + +TEST_F(UtilsTest, GetPathFromString_EmptyPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "")) + << "Should not allow empty paths."; +} + +TEST_F(UtilsTest, GetPathFromString_RelativePathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "mnt/asec")) + << "Should not allow relative paths."; +} + +TEST_F(UtilsTest, GetPathFromString_NonCanonical) { + dir_rec_t test1; + + EXPECT_EQ(0, get_path_from_string(&test1, "/mnt/asec")) + << "Should be able to canonicalize directory /mnt/asec"; + EXPECT_STREQ("/mnt/asec/", test1.path) + << "/mnt/asec should be canonicalized to /mnt/asec/"; + EXPECT_EQ(10, (ssize_t) test1.len) + << "path len should be equal to the length of /mnt/asec/ (10)"; + free(test1.path); +} + +TEST_F(UtilsTest, GetPathFromString_CanonicalPath) { + dir_rec_t test3; + EXPECT_EQ(0, get_path_from_string(&test3, "/data/app/")) + << "Should be able to canonicalize directory /data/app/"; + EXPECT_STREQ("/data/app/", test3.path) + << "/data/app/ should be canonicalized to /data/app/"; + EXPECT_EQ(10, (ssize_t) test3.len) + << "path len should be equal to the length of /data/app/ (10)"; + free(test3.path); +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameSuccess) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(0, create_pkg_path(path, pkgname, "", 0)) + << "Should successfully be able to create package name."; + + const char *prefix = TEST_DATA_DIR PRIMARY_USER_PREFIX; + size_t offset = strlen(prefix); + EXPECT_STREQ(pkgname, path + offset) + << "Package path should be a really long string of a's"; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX + 1; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, pkgname, "", 0)) + << "Should return error because package name is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPostfixFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t postfixSize = PKG_PATH_MAX; + char postfix[postfixSize + 1]; + memset(postfix, 'a', postfixSize); + postfix[postfixSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, "com.example.package", postfix, 0)) + << "Should return error because postfix is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_PrimaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 0)) + << "Should return error because postfix is too long."; + + EXPECT_STREQ(TEST_DATA_DIR PRIMARY_USER_PREFIX "com.example.package", path) + << "Package path should be in /data/data/"; +} + +TEST_F(UtilsTest, CreatePkgPath_SecondaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 1)) + << "Should successfully create package path."; + + EXPECT_STREQ(TEST_DATA_DIR SECONDARY_USER_PREFIX "1/com.example.package", path) + << "Package path should be in /data/user/"; +} + +TEST_F(UtilsTest, CreatePkgPathInDir_ProtectedDir) { + char path[PKG_PATH_MAX]; + + dir_rec_t dir; + dir.path = "/data/app-private/"; + dir.len = strlen(dir.path); + + EXPECT_EQ(0, create_pkg_path_in_dir(path, &dir, "com.example.package", ".apk")) + << "Should successfully create package path."; + + EXPECT_STREQ("/data/app-private/com.example.package.apk", path) + << "Package path should be in /data/app-private/"; +} + +TEST_F(UtilsTest, CreatePersonaPath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 0)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/data/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreatePersonaPath_Secondary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 1)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/user/1/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreateMovePath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_move_path(path, "com.android.test", "shared_prefs", 0)) + << "Should be able to create move path for primary user"; + + EXPECT_STREQ("/data/data/com.android.test/shared_prefs", path) + << "Primary user package directory should be created correctly"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_AppTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, REALLY_LONG_APP_NAME, "shared_prefs", 0)) + << "Should fail to create move path for primary user"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_LeafTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, "com.android.test", REALLY_LONG_LEAF_NAME, 0)) + << "Should fail to create move path for primary user"; +} + +TEST_F(UtilsTest, CopyAndAppend_Normal) { + //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix) + dir_rec_t dst; + dir_rec_t src; + + src.path = "/data/"; + src.len = strlen(src.path); + + EXPECT_EQ(0, copy_and_append(&dst, &src, "app/")) + << "Should return error because postfix is too long."; + + EXPECT_STREQ("/data/app/", dst.path) + << "Appended path should be correct"; + + EXPECT_EQ(10, (ssize_t) dst.len) + << "Appended path should be length of '/data/app/' (10)"; +} + +TEST_F(UtilsTest, AppendAndIncrement_Normal) { + size_t dst_size = 10; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully again"; + + EXPECT_STREQ("FOOFOO", dst) + << "String should append correctly again"; +} + +TEST_F(UtilsTest, AppendAndIncrement_TooBig) { + size_t dst_size = 5; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(-1, append_and_increment(&dstp, src, &dst_size)) + << "String should fail because it's too large to fit"; +} + +} diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c new file mode 100644 index 0000000..625a35e --- /dev/null +++ b/cmds/installd/utils.c @@ -0,0 +1,1006 @@ +/* +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "installd.h" + +#define CACHE_NOISY(x) //x + +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix) +{ + const size_t postfix_len = strlen(postfix); + + const size_t pkgname_len = strlen(pkgname); + if (pkgname_len > PKG_NAME_MAX) { + return -1; + } + + if (is_valid_package_name(pkgname) < 0) { + return -1; + } + + if ((pkgname_len + dir->len + postfix_len) >= PKG_PATH_MAX) { + return -1; + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, dir->path, &dst_size) < 0 + || append_and_increment(&dst, pkgname, &dst_size) < 0 + || append_and_increment(&dst, postfix, &dst_size) < 0) { + ALOGE("Error building APK path"); + return -1; + } + + return 0; +} + +/** + * Create the package path name for a given package name with a postfix for + * a certain persona. Returns 0 on success, and -1 on failure. + */ +int create_pkg_path(char path[PKG_PATH_MAX], + const char *pkgname, + const char *postfix, + uid_t persona) +{ + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + const size_t prefix_len = android_data_dir.len + strlen(persona_prefix) + uid_len + 1 /*slash*/; + char prefix[prefix_len + 1]; + + char *dst = prefix; + size_t dst_size = sizeof(prefix); + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + ALOGE("Error building prefix for APK path"); + return -1; + } + + if (persona != 0) { + int ret = snprintf(dst, dst_size, "%d/", persona); + if (ret < 0 || (size_t) ret != uid_len + 1) { + ALOGW("Error appending UID to APK path"); + return -1; + } + } + + dir_rec_t dir; + dir.path = prefix; + dir.len = prefix_len; + + return create_pkg_path_in_dir(path, &dir, pkgname, postfix); +} + +/** + * Create the path name for user data for a certain persona. + * Returns 0 on success, and -1 on failure. + */ +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona) +{ + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d/", persona); + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + ALOGE("Error building prefix for user path"); + return -1; + } + + if (persona != 0) { + if (dst_size < uid_len + 1) { + ALOGE("Error building user path"); + return -1; + } + int ret = snprintf(dst, dst_size, "%d/", persona); + if (ret < 0 || (size_t) ret != uid_len) { + ALOGE("Error appending persona id to path"); + return -1; + } + } + return 0; +} + +/** + * Create the path name for media for a certain persona. + * Returns 0 on success, and -1 on failure. + */ +int create_persona_media_path(char path[PATH_MAX], userid_t userid) { + if (snprintf(path, PATH_MAX, "%s%d", android_media_dir.path, userid) > PATH_MAX) { + return -1; + } + return 0; +} + +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona) +{ + if ((android_data_dir.len + strlen(PRIMARY_USER_PREFIX) + strlen(pkgname) + strlen(leaf) + 1) + >= PKG_PATH_MAX) { + return -1; + } + + sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); + return 0; +} + +/** + * Checks whether the package name is valid. Returns -1 on error and + * 0 on success. + */ +int is_valid_package_name(const char* pkgname) { + const char *x = pkgname; + int alpha = -1; + + while (*x) { + if (isalnum(*x) || (*x == '_')) { + /* alphanumeric or underscore are fine */ + } else if (*x == '.') { + if ((x == pkgname) || (x[1] == '.') || (x[1] == 0)) { + /* periods must not be first, last, or doubled */ + ALOGE("invalid package name '%s'\n", pkgname); + return -1; + } + } else if (*x == '-') { + /* Suffix -X is fine to let versioning of packages. + But whatever follows should be alphanumeric.*/ + alpha = 1; + } else { + /* anything not A-Z, a-z, 0-9, _, or . is invalid */ + ALOGE("invalid package name '%s'\n", pkgname); + return -1; + } + + x++; + } + + if (alpha == 1) { + // Skip current character + x++; + while (*x) { + if (!isalnum(*x)) { + ALOGE("invalid package name '%s' should include only numbers after -\n", pkgname); + return -1; + } + x++; + } + } + + return 0; +} + +static int _delete_dir_contents(DIR *d, const char *ignore) +{ + int result = 0; + struct dirent *de; + int dfd; + + dfd = dirfd(d); + + if (dfd < 0) return -1; + + while ((de = readdir(d))) { + const char *name = de->d_name; + + /* skip the ignore name if provided */ + if (ignore && !strcmp(name, ignore)) continue; + + if (de->d_type == DT_DIR) { + int r, subfd; + DIR *subdir; + + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + + subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY); + if (subfd < 0) { + ALOGE("Couldn't openat %s: %s\n", name, strerror(errno)); + result = -1; + continue; + } + subdir = fdopendir(subfd); + if (subdir == NULL) { + ALOGE("Couldn't fdopendir %s: %s\n", name, strerror(errno)); + close(subfd); + result = -1; + continue; + } + if (_delete_dir_contents(subdir, 0)) { + result = -1; + } + closedir(subdir); + if (unlinkat(dfd, name, AT_REMOVEDIR) < 0) { + ALOGE("Couldn't unlinkat %s: %s\n", name, strerror(errno)); + result = -1; + } + } else { + if (unlinkat(dfd, name, 0) < 0) { + ALOGE("Couldn't unlinkat %s: %s\n", name, strerror(errno)); + result = -1; + } + } + } + + return result; +} + +int delete_dir_contents(const char *pathname, + int also_delete_dir, + const char *ignore) +{ + int res = 0; + DIR *d; + + d = opendir(pathname); + if (d == NULL) { + ALOGE("Couldn't opendir %s: %s\n", pathname, strerror(errno)); + return -errno; + } + res = _delete_dir_contents(d, ignore); + closedir(d); + if (also_delete_dir) { + if (rmdir(pathname)) { + ALOGE("Couldn't rmdir %s: %s\n", pathname, strerror(errno)); + res = -1; + } + } + return res; +} + +int delete_dir_contents_fd(int dfd, const char *name) +{ + int fd, res; + DIR *d; + + fd = openat(dfd, name, O_RDONLY | O_DIRECTORY); + if (fd < 0) { + ALOGE("Couldn't openat %s: %s\n", name, strerror(errno)); + return -1; + } + d = fdopendir(fd); + if (d == NULL) { + ALOGE("Couldn't fdopendir %s: %s\n", name, strerror(errno)); + close(fd); + return -1; + } + res = _delete_dir_contents(d, 0); + closedir(d); + return res; +} + +int lookup_media_dir(char basepath[PATH_MAX], const char *dir) +{ + DIR *d; + struct dirent *de; + struct stat s; + char* dirpos = basepath + strlen(basepath); + + if ((*(dirpos-1)) != '/') { + *dirpos = '/'; + dirpos++; + } + + CACHE_NOISY(ALOGI("Looking up %s in %s\n", dir, basepath)); + // Verify the path won't extend beyond our buffer, to avoid + // repeated checking later. + if ((dirpos-basepath+strlen(dir)) >= (PATH_MAX-1)) { + ALOGW("Path exceeds limit: %s%s", basepath, dir); + return -1; + } + + // First, can we find this directory with the case that is given? + strcpy(dirpos, dir); + if (stat(basepath, &s) >= 0) { + CACHE_NOISY(ALOGI("Found direct: %s\n", basepath)); + return 0; + } + + // Not found with that case... search through all entries to find + // one that matches regardless of case. + *dirpos = 0; + + d = opendir(basepath); + if (d == NULL) { + return -1; + } + + while ((de = readdir(d))) { + if (strcasecmp(de->d_name, dir) == 0) { + strcpy(dirpos, de->d_name); + closedir(d); + CACHE_NOISY(ALOGI("Found search: %s\n", basepath)); + return 0; + } + } + + ALOGW("Couldn't find %s in %s", dir, basepath); + closedir(d); + return -1; +} + +int64_t data_disk_free() +{ + struct statfs sfs; + if (statfs(android_data_dir.path, &sfs) == 0) { + return sfs.f_bavail * sfs.f_bsize; + } else { + ALOGE("Couldn't statfs %s: %s\n", android_data_dir.path, strerror(errno)); + return -1; + } +} + +cache_t* start_cache_collection() +{ + cache_t* cache = (cache_t*)calloc(1, sizeof(cache_t)); + return cache; +} + +#define CACHE_BLOCK_SIZE (512*1024) + +static void* _cache_malloc(cache_t* cache, size_t len) +{ + len = (len+3)&~3; + if (len > (CACHE_BLOCK_SIZE/2)) { + // It doesn't make sense to try to put this allocation into one + // of our blocks, because it is so big. Instead, make a new dedicated + // block for it. + int8_t* res = (int8_t*)malloc(len+sizeof(void*)); + if (res == NULL) { + return NULL; + } + CACHE_NOISY(ALOGI("Allocated large cache mem block: %p size %d", res, len)); + // Link it into our list of blocks, not disrupting the current one. + if (cache->memBlocks == NULL) { + *(void**)res = NULL; + cache->memBlocks = res; + } else { + *(void**)res = *(void**)cache->memBlocks; + *(void**)cache->memBlocks = res; + } + return res + sizeof(void*); + } + int8_t* res = cache->curMemBlockAvail; + int8_t* nextPos = res + len; + if (cache->memBlocks == NULL || nextPos > cache->curMemBlockEnd) { + int8_t* newBlock = malloc(CACHE_BLOCK_SIZE); + if (newBlock == NULL) { + return NULL; + } + CACHE_NOISY(ALOGI("Allocated new cache mem block: %p", newBlock)); + *(void**)newBlock = cache->memBlocks; + cache->memBlocks = newBlock; + res = cache->curMemBlockAvail = newBlock + sizeof(void*); + cache->curMemBlockEnd = newBlock + CACHE_BLOCK_SIZE; + nextPos = res + len; + } + CACHE_NOISY(ALOGI("cache_malloc: ret %p size %d, block=%p, nextPos=%p", + res, len, cache->memBlocks, nextPos)); + cache->curMemBlockAvail = nextPos; + return res; +} + +static void* _cache_realloc(cache_t* cache, void* cur, size_t origLen, size_t len) +{ + // This isn't really a realloc, but it is good enough for our purposes here. + void* alloc = _cache_malloc(cache, len); + if (alloc != NULL && cur != NULL) { + memcpy(alloc, cur, origLen < len ? origLen : len); + } + return alloc; +} + +static void _inc_num_cache_collected(cache_t* cache) +{ + cache->numCollected++; + if ((cache->numCollected%20000) == 0) { + ALOGI("Collected cache so far: %d directories, %d files", + cache->numDirs, cache->numFiles); + } +} + +static cache_dir_t* _add_cache_dir_t(cache_t* cache, cache_dir_t* parent, const char *name) +{ + size_t nameLen = strlen(name); + cache_dir_t* dir = (cache_dir_t*)_cache_malloc(cache, sizeof(cache_dir_t)+nameLen+1); + if (dir != NULL) { + dir->parent = parent; + dir->childCount = 0; + dir->hiddenCount = 0; + dir->deleted = 0; + strcpy(dir->name, name); + if (cache->numDirs >= cache->availDirs) { + size_t newAvail = cache->availDirs < 1000 ? 1000 : cache->availDirs*2; + cache_dir_t** newDirs = (cache_dir_t**)_cache_realloc(cache, cache->dirs, + cache->availDirs*sizeof(cache_dir_t*), newAvail*sizeof(cache_dir_t*)); + if (newDirs == NULL) { + ALOGE("Failure growing cache dirs array for %s\n", name); + return NULL; + } + cache->availDirs = newAvail; + cache->dirs = newDirs; + } + cache->dirs[cache->numDirs] = dir; + cache->numDirs++; + if (parent != NULL) { + parent->childCount++; + } + _inc_num_cache_collected(cache); + } else { + ALOGE("Failure allocating cache_dir_t for %s\n", name); + } + return dir; +} + +static cache_file_t* _add_cache_file_t(cache_t* cache, cache_dir_t* dir, time_t modTime, + const char *name) +{ + size_t nameLen = strlen(name); + cache_file_t* file = (cache_file_t*)_cache_malloc(cache, sizeof(cache_file_t)+nameLen+1); + if (file != NULL) { + file->dir = dir; + file->modTime = modTime; + strcpy(file->name, name); + if (cache->numFiles >= cache->availFiles) { + size_t newAvail = cache->availFiles < 1000 ? 1000 : cache->availFiles*2; + cache_file_t** newFiles = (cache_file_t**)_cache_realloc(cache, cache->files, + cache->availFiles*sizeof(cache_file_t*), newAvail*sizeof(cache_file_t*)); + if (newFiles == NULL) { + ALOGE("Failure growing cache file array for %s\n", name); + return NULL; + } + cache->availFiles = newAvail; + cache->files = newFiles; + } + CACHE_NOISY(ALOGI("Setting file %p at position %d in array %p", file, + cache->numFiles, cache->files)); + cache->files[cache->numFiles] = file; + cache->numFiles++; + dir->childCount++; + _inc_num_cache_collected(cache); + } else { + ALOGE("Failure allocating cache_file_t for %s\n", name); + } + return file; +} + +static int _add_cache_files(cache_t *cache, cache_dir_t *parentDir, const char *dirName, + DIR* dir, char *pathBase, char *pathPos, size_t pathAvailLen) +{ + struct dirent *de; + cache_dir_t* cacheDir = NULL; + int dfd; + + CACHE_NOISY(ALOGI("_add_cache_files: parent=%p dirName=%s dir=%p pathBase=%s", + parentDir, dirName, dir, pathBase)); + + dfd = dirfd(dir); + + if (dfd < 0) return 0; + + // Sub-directories always get added to the data structure, so if they + // are empty we will know about them to delete them later. + cacheDir = _add_cache_dir_t(cache, parentDir, dirName); + + while ((de = readdir(dir))) { + const char *name = de->d_name; + + if (de->d_type == DT_DIR) { + int subfd; + DIR *subdir; + + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + + subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY); + if (subfd < 0) { + ALOGE("Couldn't openat %s: %s\n", name, strerror(errno)); + continue; + } + subdir = fdopendir(subfd); + if (subdir == NULL) { + ALOGE("Couldn't fdopendir %s: %s\n", name, strerror(errno)); + close(subfd); + continue; + } + if (cacheDir == NULL) { + cacheDir = _add_cache_dir_t(cache, parentDir, dirName); + } + if (cacheDir != NULL) { + // Update pathBase for the new path... this may change dirName + // if that is also pointing to the path, but we are done with it + // now. + size_t finallen = snprintf(pathPos, pathAvailLen, "/%s", name); + CACHE_NOISY(ALOGI("Collecting dir %s\n", pathBase)); + if (finallen < pathAvailLen) { + _add_cache_files(cache, cacheDir, name, subdir, pathBase, + pathPos+finallen, pathAvailLen-finallen); + } else { + // Whoops, the final path is too long! We'll just delete + // this directory. + ALOGW("Cache dir %s truncated in path %s; deleting dir\n", + name, pathBase); + _delete_dir_contents(subdir, NULL); + if (unlinkat(dfd, name, AT_REMOVEDIR) < 0) { + ALOGE("Couldn't unlinkat %s: %s\n", name, strerror(errno)); + } + } + } + closedir(subdir); + } else if (de->d_type == DT_REG) { + // Skip files that start with '.'; they will be deleted if + // their entire directory is deleted. This allows for metadata + // like ".nomedia" to remain in the directory until the entire + // directory is deleted. + if (cacheDir == NULL) { + cacheDir = _add_cache_dir_t(cache, parentDir, dirName); + } + if (name[0] == '.') { + cacheDir->hiddenCount++; + continue; + } + if (cacheDir != NULL) { + // Build final full path for file... this may change dirName + // if that is also pointing to the path, but we are done with it + // now. + size_t finallen = snprintf(pathPos, pathAvailLen, "/%s", name); + CACHE_NOISY(ALOGI("Collecting file %s\n", pathBase)); + if (finallen < pathAvailLen) { + struct stat s; + if (stat(pathBase, &s) >= 0) { + _add_cache_file_t(cache, cacheDir, s.st_mtime, name); + } else { + ALOGW("Unable to stat cache file %s; deleting\n", pathBase); + if (unlink(pathBase) < 0) { + ALOGE("Couldn't unlink %s: %s\n", pathBase, strerror(errno)); + } + } + } else { + // Whoops, the final path is too long! We'll just delete + // this file. + ALOGW("Cache file %s truncated in path %s; deleting\n", + name, pathBase); + if (unlinkat(dfd, name, 0) < 0) { + *pathPos = 0; + ALOGE("Couldn't unlinkat %s in %s: %s\n", name, pathBase, + strerror(errno)); + } + } + } + } else { + cacheDir->hiddenCount++; + } + } + return 0; +} + +void add_cache_files(cache_t* cache, const char *basepath, const char *cachedir) +{ + DIR *d; + struct dirent *de; + char dirname[PATH_MAX]; + + CACHE_NOISY(ALOGI("add_cache_files: base=%s cachedir=%s\n", basepath, cachedir)); + + d = opendir(basepath); + if (d == NULL) { + return; + } + + while ((de = readdir(d))) { + if (de->d_type == DT_DIR) { + DIR* subdir; + const char *name = de->d_name; + char* pathpos; + + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + + strcpy(dirname, basepath); + pathpos = dirname + strlen(dirname); + if ((*(pathpos-1)) != '/') { + *pathpos = '/'; + pathpos++; + *pathpos = 0; + } + if (cachedir != NULL) { + snprintf(pathpos, sizeof(dirname)-(pathpos-dirname), "%s/%s", name, cachedir); + } else { + snprintf(pathpos, sizeof(dirname)-(pathpos-dirname), "%s", name); + } + CACHE_NOISY(ALOGI("Adding cache files from dir: %s\n", dirname)); + subdir = opendir(dirname); + if (subdir != NULL) { + size_t dirnameLen = strlen(dirname); + _add_cache_files(cache, NULL, dirname, subdir, dirname, dirname+dirnameLen, + PATH_MAX - dirnameLen); + closedir(subdir); + } + } + } + + closedir(d); +} + +static char *create_dir_path(char path[PATH_MAX], cache_dir_t* dir) +{ + char *pos = path; + if (dir->parent != NULL) { + pos = create_dir_path(path, dir->parent); + } + // Note that we don't need to worry about going beyond the buffer, + // since when we were constructing the cache entries our maximum + // buffer size for full paths was PATH_MAX. + strcpy(pos, dir->name); + pos += strlen(pos); + *pos = '/'; + pos++; + *pos = 0; + return pos; +} + +static void delete_cache_dir(char path[PATH_MAX], cache_dir_t* dir) +{ + if (dir->parent != NULL) { + create_dir_path(path, dir); + ALOGI("DEL DIR %s\n", path); + if (dir->hiddenCount <= 0) { + if (rmdir(path)) { + ALOGE("Couldn't rmdir %s: %s\n", path, strerror(errno)); + return; + } + } else { + // The directory contains hidden files so we need to delete + // them along with the directory itself. + if (delete_dir_contents(path, 1, NULL)) { + return; + } + } + dir->parent->childCount--; + dir->deleted = 1; + if (dir->parent->childCount <= 0) { + delete_cache_dir(path, dir->parent); + } + } else if (dir->hiddenCount > 0) { + // This is a root directory, but it has hidden files. Get rid of + // all of those files, but not the directory itself. + create_dir_path(path, dir); + ALOGI("DEL CONTENTS %s\n", path); + delete_dir_contents(path, 0, NULL); + } +} + +static int cache_modtime_sort(const void *lhsP, const void *rhsP) +{ + const cache_file_t *lhs = *(const cache_file_t**)lhsP; + const cache_file_t *rhs = *(const cache_file_t**)rhsP; + return lhs->modTime < rhs->modTime ? -1 : (lhs->modTime > rhs->modTime ? 1 : 0); +} + +void clear_cache_files(cache_t* cache, int64_t free_size) +{ + size_t i; + int skip = 0; + char path[PATH_MAX]; + + ALOGI("Collected cache files: %d directories, %d files", + cache->numDirs, cache->numFiles); + + CACHE_NOISY(ALOGI("Sorting files...")); + qsort(cache->files, cache->numFiles, sizeof(cache_file_t*), + cache_modtime_sort); + + CACHE_NOISY(ALOGI("Cleaning empty directories...")); + for (i=cache->numDirs; i>0; i--) { + cache_dir_t* dir = cache->dirs[i-1]; + if (dir->childCount <= 0 && !dir->deleted) { + delete_cache_dir(path, dir); + } + } + + CACHE_NOISY(ALOGI("Trimming files...")); + for (i=0; i<cache->numFiles; i++) { + skip++; + if (skip > 10) { + if (data_disk_free() > free_size) { + return; + } + skip = 0; + } + cache_file_t* file = cache->files[i]; + strcpy(create_dir_path(path, file->dir), file->name); + ALOGI("DEL (mod %d) %s\n", (int)file->modTime, path); + if (unlink(path) < 0) { + ALOGE("Couldn't unlink %s: %s\n", path, strerror(errno)); + } + file->dir->childCount--; + if (file->dir->childCount <= 0) { + delete_cache_dir(path, file->dir); + } + } +} + +void finish_cache_collection(cache_t* cache) +{ + size_t i; + + CACHE_NOISY(ALOGI("clear_cache_files: %d dirs, %d files\n", cache->numDirs, cache->numFiles)); + CACHE_NOISY( + for (i=0; i<cache->numDirs; i++) { + cache_dir_t* dir = cache->dirs[i]; + ALOGI("dir #%d: %p %s parent=%p\n", i, dir, dir->name, dir->parent); + }) + CACHE_NOISY( + for (i=0; i<cache->numFiles; i++) { + cache_file_t* file = cache->files[i]; + ALOGI("file #%d: %p %s time=%d dir=%p\n", i, file, file->name, + (int)file->modTime, file->dir); + }) + void* block = cache->memBlocks; + while (block != NULL) { + void* nextBlock = *(void**)block; + CACHE_NOISY(ALOGI("Freeing cache mem block: %p", block)); + free(block); + block = nextBlock; + } + free(cache); +} + +/** + * Checks whether a path points to a system app (.apk file). Returns 0 + * if it is a system app or -1 if it is not. + */ +int validate_system_app_path(const char* path) { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + const size_t dir_len = android_system_dirs.dirs[i].len; + if (!strncmp(path, android_system_dirs.dirs[i].path, dir_len)) { + if (path[dir_len] == '.' || strchr(path + dir_len, '/') != NULL) { + ALOGE("invalid system apk path '%s' (trickery)\n", path); + return -1; + } + return 0; + } + } + + return -1; +} + +/** + * Get the contents of a environment variable that contains a path. Caller + * owns the string that is inserted into the directory record. Returns + * 0 on success and -1 on error. + */ +int get_path_from_env(dir_rec_t* rec, const char* var) { + const char* path = getenv(var); + int ret = get_path_from_string(rec, path); + if (ret < 0) { + ALOGW("Problem finding value for environment variable %s\n", var); + } + return ret; +} + +/** + * Puts the string into the record as a directory. Appends '/' to the end + * of all paths. Caller owns the string that is inserted into the directory + * record. A null value will result in an error. + * + * Returns 0 on success and -1 on error. + */ +int get_path_from_string(dir_rec_t* rec, const char* path) { + if (path == NULL) { + return -1; + } else { + const size_t path_len = strlen(path); + if (path_len <= 0) { + return -1; + } + + // Make sure path is absolute. + if (path[0] != '/') { + return -1; + } + + if (path[path_len - 1] == '/') { + // Path ends with a forward slash. Make our own copy. + + rec->path = strdup(path); + if (rec->path == NULL) { + return -1; + } + + rec->len = path_len; + } else { + // Path does not end with a slash. Generate a new string. + char *dst; + + // Add space for slash and terminating null. + size_t dst_size = path_len + 2; + + rec->path = malloc(dst_size); + if (rec->path == NULL) { + return -1; + } + + dst = rec->path; + + if (append_and_increment(&dst, path, &dst_size) < 0 + || append_and_increment(&dst, "/", &dst_size)) { + ALOGE("Error canonicalizing path"); + return -1; + } + + rec->len = dst - rec->path; + } + } + return 0; +} + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix) { + dst->len = src->len + strlen(suffix); + const size_t dstSize = dst->len + 1; + dst->path = (char*) malloc(dstSize); + + if (dst->path == NULL + || snprintf(dst->path, dstSize, "%s%s", src->path, suffix) + != (ssize_t) dst->len) { + ALOGE("Could not allocate memory to hold appended path; aborting\n"); + return -1; + } + + return 0; +} + +/** + * Check whether path points to a valid path for an APK file. An ASEC + * directory is allowed to have one level of subdirectory names. Returns -1 + * when an invalid path is encountered and 0 when a valid path is encountered. + */ +int validate_apk_path(const char *path) +{ + int allowsubdir = 0; + char *subdir = NULL; + size_t dir_len; + size_t path_len; + + if (!strncmp(path, android_app_dir.path, android_app_dir.len)) { + dir_len = android_app_dir.len; + } else if (!strncmp(path, android_app_private_dir.path, android_app_private_dir.len)) { + dir_len = android_app_private_dir.len; + } else if (!strncmp(path, android_asec_dir.path, android_asec_dir.len)) { + dir_len = android_asec_dir.len; + allowsubdir = 1; + } else { + ALOGE("invalid apk path '%s' (bad prefix)\n", path); + return -1; + } + + path_len = strlen(path); + + /* + * Only allow the path to have a subdirectory if it's been marked as being allowed. + */ + if ((subdir = strchr(path + dir_len, '/')) != NULL) { + ++subdir; + if (!allowsubdir + || (path_len > (size_t) (subdir - path) && (strchr(subdir, '/') != NULL))) { + ALOGE("invalid apk path '%s' (subdir?)\n", path); + return -1; + } + } + + /* + * Directories can't have a period directly after the directory markers + * to prevent ".." + */ + if (path[dir_len] == '.' + || (subdir != NULL && ((*subdir == '.') || (strchr(subdir, '/') != NULL)))) { + ALOGE("invalid apk path '%s' (trickery)\n", path); + return -1; + } + + return 0; +} + +int append_and_increment(char** dst, const char* src, size_t* dst_size) { + ssize_t ret = strlcpy(*dst, src, *dst_size); + if (ret < 0 || (size_t) ret >= *dst_size) { + return -1; + } + *dst += ret; + *dst_size -= ret; + return 0; +} + +char *build_string2(char *s1, char *s2) { + if (s1 == NULL || s2 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len = len_s1 + len_s2 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + + return result; +} + +char *build_string3(char *s1, char *s2, char *s3) { + if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len_s3 = strlen(s3); + int len = len_s1 + len_s2 + len_s3 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + strcpy(result + len_s1 + len_s2, s3); + + return result; +} + +/* Ensure that /data/media directories are prepared for given user. */ +int ensure_media_user_dirs(userid_t userid) { + char media_user_path[PATH_MAX]; + char path[PATH_MAX]; + + // Ensure /data/media/<userid> exists + create_persona_media_path(media_user_path, userid); + if (fs_prepare_dir(media_user_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + return -1; + } + + return 0; +} diff --git a/cmds/ip-up-vpn/Android.mk b/cmds/ip-up-vpn/Android.mk new file mode 100644 index 0000000..de81889 --- /dev/null +++ b/cmds/ip-up-vpn/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := ip-up-vpn.c +LOCAL_SHARED_LIBRARIES := libcutils +LOCAL_MODULE := ip-up-vpn +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/ppp +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/cmds/ip-up-vpn/ip-up-vpn.c b/cmds/ip-up-vpn/ip-up-vpn.c new file mode 100644 index 0000000..9fcc950 --- /dev/null +++ b/cmds/ip-up-vpn/ip-up-vpn.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 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 <errno.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <linux/if.h> +#include <linux/route.h> + +#define LOG_TAG "ip-up-vpn" +#include <cutils/log.h> + +#define DIR "/data/misc/vpn/" + +static const char *env(const char *name) { + const char *value = getenv(name); + return value ? value : ""; +} + +static int set_address(struct sockaddr *sa, const char *address) { + sa->sa_family = AF_INET; + errno = EINVAL; + return inet_pton(AF_INET, address, &((struct sockaddr_in *)sa)->sin_addr); +} + +/* + * The primary goal is to create a file with VPN parameters. Currently they + * are interface, addresses, routes, DNS servers, and search domains. Each + * parameter occupies one line in the file, and it can be an empty string or + * space-separated values. The order and the format must be consistent with + * com.android.server.connectivity.Vpn. Here is an example. + * + * ppp0 + * 192.168.1.100/24 + * 0.0.0.0/0 + * 192.168.1.1 192.168.1.2 + * example.org + * + * The secondary goal is to unify the outcome of VPN. The current baseline + * is to have an interface configured with the given address and netmask + * and maybe add a host route to protect the tunnel. PPP-based VPN already + * does this, but others might not. Routes, DNS servers, and search domains + * are handled by the framework since they can be overridden by the users. + */ +int main(int argc, char **argv) +{ + FILE *state = fopen(DIR ".tmp", "wb"); + if (!state) { + ALOGE("Cannot create state: %s", strerror(errno)); + return 1; + } + + if (argc >= 6) { + /* Invoked by pppd. */ + fprintf(state, "%s\n", argv[1]); + fprintf(state, "%s/32\n", argv[4]); + fprintf(state, "0.0.0.0/0\n"); + fprintf(state, "%s %s\n", env("DNS1"), env("DNS2")); + fprintf(state, "\n"); + } else if (argc == 2) { + /* Invoked by racoon. */ + const char *interface = env("INTERFACE"); + const char *address = env("INTERNAL_ADDR4"); + const char *routes = env("SPLIT_INCLUDE_CIDR"); + + int s = socket(AF_INET, SOCK_DGRAM, 0); + struct rtentry rt; + struct ifreq ifr; + + memset(&rt, 0, sizeof(rt)); + memset(&ifr, 0, sizeof(ifr)); + + /* Remove the old host route. There could be more than one. */ + rt.rt_flags |= RTF_UP | RTF_HOST; + if (set_address(&rt.rt_dst, env("REMOTE_ADDR"))) { + while (!ioctl(s, SIOCDELRT, &rt)); + } + if (errno != ESRCH) { + ALOGE("Cannot remove host route: %s", strerror(errno)); + return 1; + } + + /* Create a new host route. */ + rt.rt_flags |= RTF_GATEWAY; + if (!set_address(&rt.rt_gateway, argv[1]) || + (ioctl(s, SIOCADDRT, &rt) && errno != EEXIST)) { + ALOGE("Cannot create host route: %s", strerror(errno)); + return 1; + } + + /* Bring up the interface. */ + ifr.ifr_flags = IFF_UP; + strncpy(ifr.ifr_name, interface, IFNAMSIZ); + if (ioctl(s, SIOCSIFFLAGS, &ifr)) { + ALOGE("Cannot bring up %s: %s", interface, strerror(errno)); + return 1; + } + + /* Set the address. */ + if (!set_address(&ifr.ifr_addr, address) || + ioctl(s, SIOCSIFADDR, &ifr)) { + ALOGE("Cannot set address: %s", strerror(errno)); + return 1; + } + + /* Set the netmask. */ + if (set_address(&ifr.ifr_netmask, env("INTERNAL_NETMASK4"))) { + if (ioctl(s, SIOCSIFNETMASK, &ifr)) { + ALOGE("Cannot set netmask: %s", strerror(errno)); + return 1; + } + } + + /* TODO: Send few packets to trigger phase 2? */ + + fprintf(state, "%s\n", interface); + fprintf(state, "%s/%s\n", address, env("INTERNAL_CIDR4")); + fprintf(state, "%s\n", routes[0] ? routes : "0.0.0.0/0"); + fprintf(state, "%s\n", env("INTERNAL_DNS4_LIST")); + fprintf(state, "%s\n", env("DEFAULT_DOMAIN")); + } else { + ALOGE("Cannot parse parameters"); + return 1; + } + + fclose(state); + if (chmod(DIR ".tmp", 0444) || rename(DIR ".tmp", DIR "state")) { + ALOGE("Cannot write state: %s", strerror(errno)); + return 1; + } + return 0; +} diff --git a/cmds/rawbu/Android.mk b/cmds/rawbu/Android.mk new file mode 100644 index 0000000..b580390 --- /dev/null +++ b/cmds/rawbu/Android.mk @@ -0,0 +1,15 @@ +# Copyright 2009 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= backup.cpp + +LOCAL_SHARED_LIBRARIES := libcutils libc + +LOCAL_MODULE:= rawbu + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug + +include $(BUILD_EXECUTABLE) diff --git a/cmds/rawbu/NOTICE b/cmds/rawbu/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/cmds/rawbu/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/rawbu/backup.cpp b/cmds/rawbu/backup.cpp new file mode 100644 index 0000000..70e7b57 --- /dev/null +++ b/cmds/rawbu/backup.cpp @@ -0,0 +1,746 @@ +// Copyright 2009 The Android Open Source Project + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <dirent.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> +#include <utime.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <stdint.h> + +#include <cutils/properties.h> + +#include <private/android_filesystem_config.h> + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +// First version. +#define FILE_VERSION_1 0xffff0001 + +// Introduces backup all option to header. +#define FILE_VERSION_2 0xffff0002 + +#define FILE_VERSION FILE_VERSION_2 + +namespace android { + +static char nameBuffer[PATH_MAX]; +static struct stat statBuffer; + +static char copyBuffer[8192]; +static char *backupFilePath = NULL; + +static uint32_t inputFileVersion; + +static int opt_backupAll; + +#define SPECIAL_NO_TOUCH 0 +#define SPECIAL_NO_BACKUP 1 + +struct special_dir { + const char* path; + int type; +}; + +/* Directory paths that we will not backup/restore */ +static const struct special_dir SKIP_PATHS[] = { + { "/data/misc", SPECIAL_NO_TOUCH }, + { "/data/system/batterystats.bin", SPECIAL_NO_TOUCH }, + { "/data/system/location", SPECIAL_NO_TOUCH }, + { "/data/dalvik-cache", SPECIAL_NO_BACKUP }, + { NULL, 0 }, +}; + +/* This is just copied from the shell's built-in wipe command. */ +static int wipe (const char *path) +{ + DIR *dir; + struct dirent *de; + int ret; + int i; + + dir = opendir(path); + + if (dir == NULL) { + fprintf (stderr, "Error opendir'ing %s: %s\n", + path, strerror(errno)); + return 0; + } + + char *filenameOffset; + + strcpy(nameBuffer, path); + strcat(nameBuffer, "/"); + + filenameOffset = nameBuffer + strlen(nameBuffer); + + for (;;) { + de = readdir(dir); + + if (de == NULL) { + break; + } + + if (0 == strcmp(de->d_name, ".") + || 0 == strcmp(de->d_name, "..") + || 0 == strcmp(de->d_name, "lost+found") + ) { + continue; + } + + strcpy(filenameOffset, de->d_name); + bool noBackup = false; + + /* See if this is a path we should skip. */ + for (i = 0; SKIP_PATHS[i].path; i++) { + if (strcmp(SKIP_PATHS[i].path, nameBuffer) == 0) { + if (opt_backupAll || SKIP_PATHS[i].type == SPECIAL_NO_BACKUP) { + // In this case we didn't back up the directory -- + // we do want to wipe its contents, but not the + // directory itself, since the restore file won't + // contain the directory. + noBackup = true; + } + break; + } + } + + if (!noBackup && SKIP_PATHS[i].path != NULL) { + // This is a SPECIAL_NO_TOUCH directory. + continue; + } + + ret = lstat (nameBuffer, &statBuffer); + + if (ret != 0) { + fprintf(stderr, "warning -- stat() error on '%s': %s\n", + nameBuffer, strerror(errno)); + continue; + } + + if(S_ISDIR(statBuffer.st_mode)) { + int i; + char *newpath; + + newpath = strdup(nameBuffer); + if (wipe(newpath) == 0) { + free(newpath); + closedir(dir); + return 0; + } + + if (!noBackup) { + ret = rmdir(newpath); + if (ret != 0) { + fprintf(stderr, "warning -- rmdir() error on '%s': %s\n", + newpath, strerror(errno)); + } + } + + free(newpath); + + strcpy(nameBuffer, path); + strcat(nameBuffer, "/"); + + } else { + // Don't delete the backup file + if (backupFilePath && strcmp(backupFilePath, nameBuffer) == 0) { + continue; + } + ret = unlink(nameBuffer); + + if (ret != 0) { + fprintf(stderr, "warning -- unlink() error on '%s': %s\n", + nameBuffer, strerror(errno)); + } + } + } + + closedir(dir); + + return 1; +} + +static int write_int32(FILE* fh, int32_t val) +{ + int res = fwrite(&val, 1, sizeof(val), fh); + if (res != sizeof(val)) { + fprintf(stderr, "unable to write int32 (%d bytes): %s\n", res, strerror(errno)); + return 0; + } + + return 1; +} + +static int write_int64(FILE* fh, int64_t val) +{ + int res = fwrite(&val, 1, sizeof(val), fh); + if (res != sizeof(val)) { + fprintf(stderr, "unable to write int64 (%d bytes): %s\n", res, strerror(errno)); + return 0; + } + + return 1; +} + +static int copy_file(FILE* dest, FILE* src, off_t size, const char* destName, + const char* srcName) +{ + errno = 0; + + off_t origSize = size; + + while (size > 0) { + int amt = size > (off_t)sizeof(copyBuffer) ? sizeof(copyBuffer) : (int)size; + int readLen = fread(copyBuffer, 1, amt, src); + if (readLen <= 0) { + if (srcName != NULL) { + fprintf(stderr, "unable to read source (%d of %ld bytes) file '%s': %s\n", + amt, origSize, srcName, errno != 0 ? strerror(errno) : "unexpected EOF"); + } else { + fprintf(stderr, "unable to read buffer (%d of %ld bytes): %s\n", + amt, origSize, errno != 0 ? strerror(errno) : "unexpected EOF"); + } + return 0; + } + int writeLen = fwrite(copyBuffer, 1, readLen, dest); + if (writeLen != readLen) { + if (destName != NULL) { + fprintf(stderr, "unable to write file (%d of %d bytes) '%s': '%s'\n", + writeLen, readLen, destName, strerror(errno)); + } else { + fprintf(stderr, "unable to write buffer (%d of %d bytes): '%s'\n", + writeLen, readLen, strerror(errno)); + } + return 0; + } + size -= readLen; + } + return 1; +} + +#define TYPE_END 0 +#define TYPE_DIR 1 +#define TYPE_FILE 2 + +static int write_header(FILE* fh, int type, const char* path, const struct stat* st) +{ + int pathLen = strlen(path); + if (!write_int32(fh, type)) return 0; + if (!write_int32(fh, pathLen)) return 0; + if (fwrite(path, 1, pathLen, fh) != (size_t)pathLen) { + fprintf(stderr, "unable to write: %s\n", strerror(errno)); + return 0; + } + + if (!write_int32(fh, st->st_uid)) return 0; + if (!write_int32(fh, st->st_gid)) return 0; + if (!write_int32(fh, st->st_mode)) return 0; + if (!write_int64(fh, ((int64_t)st->st_atime)*1000*1000*1000)) return 0; + if (!write_int64(fh, ((int64_t)st->st_mtime)*1000*1000*1000)) return 0; + if (!write_int64(fh, ((int64_t)st->st_ctime)*1000*1000*1000)) return 0; + + return 1; +} + +static int backup_dir(FILE* fh, const char* srcPath) +{ + DIR *dir; + struct dirent *de; + char* fullPath = NULL; + int srcLen = strlen(srcPath); + int result = 1; + int i; + + dir = opendir(srcPath); + + if (dir == NULL) { + fprintf (stderr, "error opendir'ing '%s': %s\n", + srcPath, strerror(errno)); + return 0; + } + + for (;;) { + de = readdir(dir); + + if (de == NULL) { + break; + } + + if (0 == strcmp(de->d_name, ".") + || 0 == strcmp(de->d_name, "..") + || 0 == strcmp(de->d_name, "lost+found") + ) { + continue; + } + + if (fullPath != NULL) { + free(fullPath); + } + fullPath = (char*)malloc(srcLen + strlen(de->d_name) + 2); + strcpy(fullPath, srcPath); + fullPath[srcLen] = '/'; + strcpy(fullPath+srcLen+1, de->d_name); + + /* See if this is a path we should skip. */ + if (!opt_backupAll) { + for (i = 0; SKIP_PATHS[i].path; i++) { + if (strcmp(SKIP_PATHS[i].path, fullPath) == 0) { + break; + } + } + if (SKIP_PATHS[i].path != NULL) { + continue; + } + } + + int ret = lstat(fullPath, &statBuffer); + + if (ret != 0) { + fprintf(stderr, "stat() error on '%s': %s\n", + fullPath, strerror(errno)); + result = 0; + goto done; + } + + if(S_ISDIR(statBuffer.st_mode)) { + printf("Saving dir %s...\n", fullPath); + + if (write_header(fh, TYPE_DIR, fullPath, &statBuffer) == 0) { + result = 0; + goto done; + } + if (backup_dir(fh, fullPath) == 0) { + result = 0; + goto done; + } + } else if (S_ISREG(statBuffer.st_mode)) { + // Skip the backup file + if (backupFilePath && strcmp(fullPath, backupFilePath) == 0) { + printf("Skipping backup file %s...\n", backupFilePath); + continue; + } else { + printf("Saving file %s...\n", fullPath); + } + if (write_header(fh, TYPE_FILE, fullPath, &statBuffer) == 0) { + result = 0; + goto done; + } + + off_t size = statBuffer.st_size; + if (!write_int64(fh, size)) { + result = 0; + goto done; + } + + FILE* src = fopen(fullPath, "r"); + if (src == NULL) { + fprintf(stderr, "unable to open source file '%s': %s\n", + fullPath, strerror(errno)); + result = 0; + goto done; + } + + int copyres = copy_file(fh, src, size, NULL, fullPath); + fclose(src); + if (!copyres) { + result = 0; + goto done; + } + } + } + +done: + if (fullPath != NULL) { + free(fullPath); + } + + closedir(dir); + + return result; +} + +static int backup_data(const char* destPath) +{ + int res = -1; + + FILE* fh = fopen(destPath, "w"); + if (fh == NULL) { + fprintf(stderr, "unable to open destination '%s': %s\n", + destPath, strerror(errno)); + return -1; + } + + printf("Backing up /data to %s...\n", destPath); + + // The path that shouldn't be backed up + backupFilePath = strdup(destPath); + + if (!write_int32(fh, FILE_VERSION)) goto done; + if (!write_int32(fh, opt_backupAll)) goto done; + if (!backup_dir(fh, "/data")) goto done; + if (!write_int32(fh, 0)) goto done; + + res = 0; + +done: + if (fflush(fh) != 0) { + fprintf(stderr, "error flushing destination '%s': %s\n", + destPath, strerror(errno)); + res = -1; + goto donedone; + } + if (fsync(fileno(fh)) != 0) { + fprintf(stderr, "error syncing destination '%s': %s\n", + destPath, strerror(errno)); + res = -1; + goto donedone; + } + fclose(fh); + sync(); + +donedone: + return res; +} + +static int32_t read_int32(FILE* fh, int32_t defVal) +{ + int32_t val; + if (fread(&val, 1, sizeof(val), fh) != sizeof(val)) { + fprintf(stderr, "unable to read: %s\n", strerror(errno)); + return defVal; + } + + return val; +} + +static int64_t read_int64(FILE* fh, int64_t defVal) +{ + int64_t val; + if (fread(&val, 1, sizeof(val), fh) != sizeof(val)) { + fprintf(stderr, "unable to read: %s\n", strerror(errno)); + return defVal; + } + + return val; +} + +static int read_header(FILE* fh, int* type, char** path, struct stat* st) +{ + *type = read_int32(fh, -1); + if (*type == TYPE_END) { + return 1; + } + + if (*type < 0) { + fprintf(stderr, "bad token %d in restore file\n", *type); + return 0; + } + + int32_t pathLen = read_int32(fh, -1); + if (pathLen <= 0) { + fprintf(stderr, "bad path length %d in restore file\n", pathLen); + return 0; + } + char* readPath = (char*)malloc(pathLen+1); + if (fread(readPath, 1, pathLen, fh) != (size_t)pathLen) { + fprintf(stderr, "truncated path in restore file\n"); + free(readPath); + return 0; + } + readPath[pathLen] = 0; + *path = readPath; + + st->st_uid = read_int32(fh, -1); + if (st->st_uid == (uid_t)-1) { + fprintf(stderr, "bad uid in restore file at '%s'\n", readPath); + return 0; + } + st->st_gid = read_int32(fh, -1); + if (st->st_gid == (gid_t)-1) { + fprintf(stderr, "bad gid in restore file at '%s'\n", readPath); + return 0; + } + st->st_mode = read_int32(fh, -1); + if (st->st_mode == (mode_t)-1) { + fprintf(stderr, "bad mode in restore file at '%s'\n", readPath); + return 0; + } + int64_t ltime = read_int64(fh, -1); + if (ltime < 0) { + fprintf(stderr, "bad atime in restore file at '%s'\n", readPath); + return 0; + } + st->st_atime = (time_t)(ltime/1000/1000/1000); + ltime = read_int64(fh, -1); + if (ltime < 0) { + fprintf(stderr, "bad mtime in restore file at '%s'\n", readPath); + return 0; + } + st->st_mtime = (time_t)(ltime/1000/1000/1000); + ltime = read_int64(fh, -1); + if (ltime < 0) { + fprintf(stderr, "bad ctime in restore file at '%s'\n", readPath); + return 0; + } + st->st_ctime = (time_t)(ltime/1000/1000/1000); + + st->st_mode &= (S_IRWXU|S_IRWXG|S_IRWXO); + + return 1; +} + +static int restore_data(const char* srcPath) +{ + int res = -1; + + FILE* fh = fopen(srcPath, "r"); + if (fh == NULL) { + fprintf(stderr, "Unable to open source '%s': %s\n", + srcPath, strerror(errno)); + return -1; + } + + inputFileVersion = read_int32(fh, 0); + if (inputFileVersion < FILE_VERSION_1 || inputFileVersion > FILE_VERSION) { + fprintf(stderr, "Restore file has bad version: 0x%x\n", inputFileVersion); + goto done; + } + + if (inputFileVersion >= FILE_VERSION_2) { + opt_backupAll = read_int32(fh, 0); + } else { + opt_backupAll = 0; + } + + // The path that shouldn't be deleted + backupFilePath = strdup(srcPath); + + printf("Wiping contents of /data...\n"); + if (!wipe("/data")) { + goto done; + } + + printf("Restoring from %s to /data...\n", srcPath); + + while (1) { + int type; + char* path = NULL; + if (read_header(fh, &type, &path, &statBuffer) == 0) { + goto done; + } + if (type == 0) { + break; + } + + const char* typeName = "?"; + + if (type == TYPE_DIR) { + typeName = "dir"; + + printf("Restoring dir %s...\n", path); + + if (mkdir(path, statBuffer.st_mode) != 0) { + if (errno != EEXIST) { + fprintf(stderr, "unable to create directory '%s': %s\n", + path, strerror(errno)); + free(path); + goto done; + } + } + + } else if (type == TYPE_FILE) { + typeName = "file"; + off_t size = read_int64(fh, -1); + if (size < 0) { + fprintf(stderr, "bad file size %ld in restore file\n", size); + free(path); + goto done; + } + + printf("Restoring file %s...\n", path); + + FILE* dest = fopen(path, "w"); + if (dest == NULL) { + fprintf(stderr, "unable to open destination file '%s': %s\n", + path, strerror(errno)); + free(path); + goto done; + } + + int copyres = copy_file(dest, fh, size, path, NULL); + fclose(dest); + if (!copyres) { + free(path); + goto done; + } + + } else { + fprintf(stderr, "unknown node type %d\n", type); + goto done; + } + + // Do this even for directories, since the dir may have already existed + // so we need to make sure it gets the correct mode. + if (chmod(path, statBuffer.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO)) != 0) { + fprintf(stderr, "unable to chmod destination %s '%s' to 0x%x: %s\n", + typeName, path, statBuffer.st_mode, strerror(errno)); + free(path); + goto done; + } + + if (chown(path, statBuffer.st_uid, statBuffer.st_gid) != 0) { + fprintf(stderr, "unable to chown destination %s '%s' to uid %d / gid %d: %s\n", + typeName, path, (int)statBuffer.st_uid, (int)statBuffer.st_gid, strerror(errno)); + free(path); + goto done; + } + + struct utimbuf timbuf; + timbuf.actime = statBuffer.st_atime; + timbuf.modtime = statBuffer.st_mtime; + if (utime(path, &timbuf) != 0) { + fprintf(stderr, "unable to utime destination %s '%s': %s\n", + typeName, path, strerror(errno)); + free(path); + goto done; + } + + + free(path); + } + + res = 0; + +done: + fclose(fh); + + return res; +} + +static void show_help(const char *cmd) +{ + fprintf(stderr,"Usage: %s COMMAND [options] [backup-file-path]\n", cmd); + + fprintf(stderr, "commands are:\n" + " help Show this help text.\n" + " backup Perform a backup of /data.\n" + " restore Perform a restore of /data.\n"); + fprintf(stderr, "options include:\n" + " -h Show this help text.\n" + " -a Backup all files.\n"); + fprintf(stderr, "\nThe %s command allows you to perform low-level\n" + "backup and restore of the /data partition. This is\n" + "where all user data is kept, allowing for a fairly\n" + "complete restore of a device's state. Note that\n" + "because this is low-level, it will only work across\n" + "builds of the same (or very similar) device software.\n", + cmd); +} + +} /* namespace android */ + +int main (int argc, char **argv) +{ + int restore = 0; + + if (getuid() != AID_ROOT) { + fprintf(stderr, "error -- %s must run as root\n", argv[0]); + exit(-1); + } + + if (argc < 2) { + fprintf(stderr, "No command specified.\n"); + android::show_help(argv[0]); + exit(-1); + } + + if (0 == strcmp(argv[1], "restore")) { + restore = 1; + } else if (0 == strcmp(argv[1], "help")) { + android::show_help(argv[0]); + exit(0); + } else if (0 != strcmp(argv[1], "backup")) { + fprintf(stderr, "Unknown command: %s\n", argv[1]); + android::show_help(argv[0]); + exit(-1); + } + + android::opt_backupAll = 0; + + optind = 2; + + for (;;) { + int ret; + + ret = getopt(argc, argv, "ah"); + + if (ret < 0) { + break; + } + + switch(ret) { + case 'a': + android::opt_backupAll = 1; + if (restore) fprintf(stderr, "Warning: -a option ignored on restore\n"); + break; + case 'h': + android::show_help(argv[0]); + exit(0); + break; + + default: + fprintf(stderr,"Unrecognized Option\n"); + android::show_help(argv[0]); + exit(-1); + break; + } + } + + const char* backupFile = "/sdcard/backup.dat"; + + if (argc > optind) { + backupFile = argv[optind]; + optind++; + if (argc != optind) { + fprintf(stderr, "Too many arguments\n"); + android::show_help(argv[0]); + exit(-1); + } + } + + printf("Stopping system...\n"); + property_set("ctl.stop", "runtime"); + property_set("ctl.stop", "zygote"); + sleep(1); + + int res; + if (restore) { + res = android::restore_data(backupFile); + if (res != 0) { + // Don't restart system, since the data partition is hosed. + return res; + } + printf("Restore complete! Restarting system, cross your fingers...\n"); + } else { + res = android::backup_data(backupFile); + if (res == 0) { + printf("Backup complete! Restarting system...\n"); + } else { + printf("Restarting system...\n"); + } + } + + property_set("ctl.start", "zygote"); + property_set("ctl.start", "runtime"); +} diff --git a/cmds/screenshot/Android.mk b/cmds/screenshot/Android.mk new file mode 100644 index 0000000..73a8e22 --- /dev/null +++ b/cmds/screenshot/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := screenshot.c + +LOCAL_MODULE := screenshot + +LOCAL_SHARED_LIBRARIES := libcutils libz +LOCAL_STATIC_LIBRARIES := libpng +LOCAL_C_INCLUDES += external/zlib + +include $(BUILD_EXECUTABLE) diff --git a/cmds/screenshot/screenshot.c b/cmds/screenshot/screenshot.c new file mode 100644 index 0000000..cca80c3 --- /dev/null +++ b/cmds/screenshot/screenshot.c @@ -0,0 +1,171 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + +#include <linux/fb.h> + +#include <zlib.h> +#include <libpng/png.h> + +#include "private/android_filesystem_config.h" + +#define LOG_TAG "screenshot" +#include <utils/Log.h> + +void take_screenshot(FILE *fb_in, FILE *fb_out) { + int fb; + char imgbuf[0x10000]; + struct fb_var_screeninfo vinfo; + png_structp png; + png_infop info; + unsigned int r,c,rowlen; + unsigned int bytespp,offset; + + fb = fileno(fb_in); + if(fb < 0) { + ALOGE("failed to open framebuffer\n"); + return; + } + fb_in = fdopen(fb, "r"); + + if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) { + ALOGE("failed to get framebuffer info\n"); + return; + } + fcntl(fb, F_SETFD, FD_CLOEXEC); + + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png == NULL) { + ALOGE("failed png_create_write_struct\n"); + fclose(fb_in); + return; + } + + png_init_io(png, fb_out); + info = png_create_info_struct(png); + if (info == NULL) { + ALOGE("failed png_create_info_struct\n"); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + if (setjmp(png_jmpbuf(png))) { + ALOGE("failed png setjmp\n"); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + + bytespp = vinfo.bits_per_pixel / 8; + png_set_IHDR(png, info, + vinfo.xres, vinfo.yres, vinfo.bits_per_pixel / 4, + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_write_info(png, info); + + rowlen=vinfo.xres * bytespp; + if (rowlen > sizeof(imgbuf)) { + ALOGE("crazy rowlen: %d\n", rowlen); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + + offset = vinfo.xoffset * bytespp + vinfo.xres * vinfo.yoffset * bytespp; + fseek(fb_in, offset, SEEK_SET); + + for(r=0; r<vinfo.yres; r++) { + int len = fread(imgbuf, 1, rowlen, fb_in); + if (len <= 0) break; + png_write_row(png, (png_bytep)imgbuf); + } + + png_write_end(png, info); + fclose(fb_in); + png_destroy_write_struct(&png, NULL); +} + +void fork_sound(const char* path) { + pid_t pid = fork(); + if (pid == 0) { + execl("/system/bin/stagefright", "stagefright", "-o", "-a", path, NULL); + } +} + +void usage() { + fprintf(stderr, + "usage: screenshot [-s soundfile] filename.png\n" + " -s: play a sound effect to signal success\n" + " -i: autoincrement to avoid overwriting filename.png\n" + ); +} + +int main(int argc, char**argv) { + FILE *png = NULL; + FILE *fb_in = NULL; + char outfile[PATH_MAX] = ""; + + char * soundfile = NULL; + int do_increment = 0; + + int c; + while ((c = getopt(argc, argv, "s:i")) != -1) { + switch (c) { + case 's': soundfile = optarg; break; + case 'i': do_increment = 1; break; + case '?': + case 'h': + usage(); exit(1); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); exit(1); + } + + strlcpy(outfile, argv[0], PATH_MAX); + if (do_increment) { + struct stat st; + char base[PATH_MAX] = ""; + int i = 0; + while (stat(outfile, &st) == 0) { + if (!base[0]) { + char *p = strrchr(outfile, '.'); + if (p) *p = '\0'; + strcpy(base, outfile); + } + snprintf(outfile, PATH_MAX, "%s-%d.png", base, ++i); + } + } + + fb_in = fopen("/dev/graphics/fb0", "r"); + if (!fb_in) { + fprintf(stderr, "error: could not read framebuffer\n"); + exit(1); + } + + /* switch to non-root user and group */ + gid_t groups[] = { AID_LOG, AID_SDCARD_RW }; + setgroups(sizeof(groups)/sizeof(groups[0]), groups); + setuid(AID_SHELL); + + png = fopen(outfile, "w"); + if (!png) { + fprintf(stderr, "error: writing file %s: %s\n", + outfile, strerror(errno)); + exit(1); + } + + take_screenshot(fb_in, png); + + if (soundfile) { + fork_sound(soundfile); + } + + exit(0); +} diff --git a/cmds/service/Android.mk b/cmds/service/Android.mk new file mode 100644 index 0000000..275bbb2 --- /dev/null +++ b/cmds/service/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + service.cpp + +LOCAL_SHARED_LIBRARIES := libutils libbinder + +ifeq ($(TARGET_OS),linux) + LOCAL_CFLAGS += -DXP_UNIX + #LOCAL_SHARED_LIBRARIES += librt +endif + +LOCAL_MODULE:= service + +include $(BUILD_EXECUTABLE) diff --git a/cmds/service/MODULE_LICENSE_APACHE2 b/cmds/service/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cmds/service/MODULE_LICENSE_APACHE2 diff --git a/cmds/service/NOTICE b/cmds/service/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/cmds/service/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp new file mode 100644 index 0000000..32db83b --- /dev/null +++ b/cmds/service/service.cpp @@ -0,0 +1,275 @@ +/* + * Command line access to services. + * + */ + +#include <binder/Parcel.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> +#include <utils/TextOutput.h> + +#include <getopt.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +using namespace android; + +void writeString16(Parcel& parcel, const char* string) +{ + if (string != NULL) + { + parcel.writeString16(String16(string)); + } + else + { + parcel.writeInt32(-1); + } +} + +// get the name of the generic interface we hold a reference to +static String16 get_interface_name(sp<IBinder> service) +{ + if (service != NULL) { + Parcel data, reply; + status_t err = service->transact(IBinder::INTERFACE_TRANSACTION, data, &reply); + if (err == NO_ERROR) { + return reply.readString16(); + } + } + return String16(); +} + +static String8 good_old_string(const String16& src) +{ + String8 name8; + char ch8[2]; + ch8[1] = 0; + for (unsigned j = 0; j < src.size(); j++) { + char16_t ch = src[j]; + if (ch < 128) ch8[0] = (char)ch; + name8.append(ch8); + } + return name8; +} + +int main(int argc, char* const argv[]) +{ + sp<IServiceManager> sm = defaultServiceManager(); + fflush(stdout); + if (sm == NULL) { + aerr << "service: Unable to get default service manager!" << endl; + return 20; + } + + bool wantsUsage = false; + int result = 0; + + while (1) { + int ic = getopt(argc, argv, "h?"); + if (ic < 0) + break; + + switch (ic) { + case 'h': + case '?': + wantsUsage = true; + break; + default: + aerr << "service: Unknown option -" << ic << endl; + wantsUsage = true; + result = 10; + break; + } + } + + if (optind >= argc) { + wantsUsage = true; + } else if (!wantsUsage) { + if (strcmp(argv[optind], "check") == 0) { + optind++; + if (optind < argc) { + sp<IBinder> service = sm->checkService(String16(argv[optind])); + aout << "Service " << argv[optind] << + (service == NULL ? ": not found" : ": found") << endl; + } else { + aerr << "service: No service specified for check" << endl; + wantsUsage = true; + result = 10; + } + } + else if (strcmp(argv[optind], "list") == 0) { + Vector<String16> services = sm->listServices(); + aout << "Found " << services.size() << " services:" << endl; + for (unsigned i = 0; i < services.size(); i++) { + String16 name = services[i]; + sp<IBinder> service = sm->checkService(name); + aout << i + << "\t" << good_old_string(name) + << ": [" << good_old_string(get_interface_name(service)) << "]" + << endl; + } + } else if (strcmp(argv[optind], "call") == 0) { + optind++; + if (optind+1 < argc) { + int serviceArg = optind; + sp<IBinder> service = sm->checkService(String16(argv[optind++])); + String16 ifName = get_interface_name(service); + int32_t code = atoi(argv[optind++]); + if (service != NULL && ifName.size() > 0) { + Parcel data, reply; + + // the interface name is first + data.writeInterfaceToken(ifName); + + // then the rest of the call arguments + while (optind < argc) { + if (strcmp(argv[optind], "i32") == 0) { + optind++; + if (optind >= argc) { + aerr << "service: no integer supplied for 'i32'" << endl; + wantsUsage = true; + result = 10; + break; + } + data.writeInt32(atoi(argv[optind++])); + } else if (strcmp(argv[optind], "s16") == 0) { + optind++; + if (optind >= argc) { + aerr << "service: no string supplied for 's16'" << endl; + wantsUsage = true; + result = 10; + break; + } + data.writeString16(String16(argv[optind++])); + } else if (strcmp(argv[optind], "null") == 0) { + optind++; + data.writeStrongBinder(NULL); + } else if (strcmp(argv[optind], "intent") == 0) { + + char* action = NULL; + char* dataArg = NULL; + char* type = NULL; + int launchFlags = 0; + char* component = NULL; + int categoryCount = 0; + char* categories[16]; + + char* context1 = NULL; + + optind++; + + while (optind < argc) + { + char* key = strtok_r(argv[optind], "=", &context1); + char* value = strtok_r(NULL, "=", &context1); + + // we have reached the end of the XXX=XXX args. + if (key == NULL) break; + + if (strcmp(key, "action") == 0) + { + action = value; + } + else if (strcmp(key, "data") == 0) + { + dataArg = value; + } + else if (strcmp(key, "type") == 0) + { + type = value; + } + else if (strcmp(key, "launchFlags") == 0) + { + launchFlags = atoi(value); + } + else if (strcmp(key, "component") == 0) + { + component = value; + } + else if (strcmp(key, "categories") == 0) + { + char* context2 = NULL; + int categoryCount = 0; + categories[categoryCount] = strtok_r(value, ",", &context2); + + while (categories[categoryCount] != NULL) + { + categoryCount++; + categories[categoryCount] = strtok_r(NULL, ",", &context2); + } + } + + optind++; + } + + writeString16(data, action); + writeString16(data, dataArg); + writeString16(data, type); + data.writeInt32(launchFlags); + writeString16(data, component); + + if (categoryCount > 0) + { + data.writeInt32(categoryCount); + for (int i = 0 ; i < categoryCount ; i++) + { + writeString16(data, categories[i]); + } + } + else + { + data.writeInt32(0); + } + + // for now just set the extra field to be null. + data.writeInt32(-1); + } else { + aerr << "service: unknown option " << argv[optind] << endl; + wantsUsage = true; + result = 10; + break; + } + } + + service->transact(code, data, &reply); + aout << "Result: " << reply << endl; + } else { + aerr << "service: Service " << argv[serviceArg] + << " does not exist" << endl; + result = 10; + } + } else { + if (optind < argc) { + aerr << "service: No service specified for call" << endl; + } else { + aerr << "service: No code specified for call" << endl; + } + wantsUsage = true; + result = 10; + } + } else { + aerr << "service: Unknown command " << argv[optind] << endl; + wantsUsage = true; + result = 10; + } + } + + if (wantsUsage) { + aout << "Usage: service [-h|-?]\n" + " service list\n" + " service check SERVICE\n" + " service call SERVICE CODE [i32 INT | s16 STR] ...\n" + "Options:\n" + " i32: Write the integer INT into the send parcel.\n" + " s16: Write the UTF-16 string STR into the send parcel.\n"; +// " intent: Write and Intent int the send parcel. ARGS can be\n" +// " action=STR data=STR type=STR launchFlags=INT component=STR categories=STR[,STR,...]\n"; + return result; + } + + return result; +} + diff --git a/cmds/servicemanager/Android.mk b/cmds/servicemanager/Android.mk new file mode 100644 index 0000000..8840867 --- /dev/null +++ b/cmds/servicemanager/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) + +#include $(CLEAR_VARS) +#LOCAL_SRC_FILES := bctest.c binder.c +#LOCAL_MODULE := bctest +#include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_SRC_FILES := service_manager.c binder.c +LOCAL_MODULE := servicemanager +include $(BUILD_EXECUTABLE) diff --git a/cmds/servicemanager/bctest.c b/cmds/servicemanager/bctest.c new file mode 100644 index 0000000..ff5aced --- /dev/null +++ b/cmds/servicemanager/bctest.c @@ -0,0 +1,103 @@ +/* Copyright 2008 The Android Open Source Project + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include "binder.h" + +void *svcmgr_lookup(struct binder_state *bs, void *target, const char *name) +{ + void *ptr; + unsigned iodata[512/4]; + struct binder_io msg, reply; + + bio_init(&msg, iodata, sizeof(iodata), 4); + bio_put_uint32(&msg, 0); // strict mode header + bio_put_string16_x(&msg, SVC_MGR_NAME); + bio_put_string16_x(&msg, name); + + if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE)) + return 0; + + ptr = bio_get_ref(&reply); + + if (ptr) + binder_acquire(bs, ptr); + + binder_done(bs, &msg, &reply); + + return ptr; +} + +int svcmgr_publish(struct binder_state *bs, void *target, const char *name, void *ptr) +{ + unsigned status; + unsigned iodata[512/4]; + struct binder_io msg, reply; + + bio_init(&msg, iodata, sizeof(iodata), 4); + bio_put_uint32(&msg, 0); // strict mode header + bio_put_string16_x(&msg, SVC_MGR_NAME); + bio_put_string16_x(&msg, name); + bio_put_obj(&msg, ptr); + + if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)) + return -1; + + status = bio_get_uint32(&reply); + + binder_done(bs, &msg, &reply); + + return status; +} + +unsigned token; + +int main(int argc, char **argv) +{ + int fd; + struct binder_state *bs; + void *svcmgr = BINDER_SERVICE_MANAGER; + + bs = binder_open(128*1024); + + argc--; + argv++; + while (argc > 0) { + if (!strcmp(argv[0],"alt")) { + void *ptr = svcmgr_lookup(bs, svcmgr, "alt_svc_mgr"); + if (!ptr) { + fprintf(stderr,"cannot find alt_svc_mgr\n"); + return -1; + } + svcmgr = ptr; + fprintf(stderr,"svcmgr is via %p\n", ptr); + } else if (!strcmp(argv[0],"lookup")) { + void *ptr; + if (argc < 2) { + fprintf(stderr,"argument required\n"); + return -1; + } + ptr = svcmgr_lookup(bs, svcmgr, argv[1]); + fprintf(stderr,"lookup(%s) = %p\n", argv[1], ptr); + argc--; + argv++; + } else if (!strcmp(argv[0],"publish")) { + if (argc < 2) { + fprintf(stderr,"argument required\n"); + return -1; + } + svcmgr_publish(bs, svcmgr, argv[1], &token); + argc--; + argv++; + } else { + fprintf(stderr,"unknown command %s\n", argv[0]); + return -1; + } + argc--; + argv++; + } + return 0; +} diff --git a/cmds/servicemanager/binder.c b/cmds/servicemanager/binder.c new file mode 100644 index 0000000..1985756 --- /dev/null +++ b/cmds/servicemanager/binder.c @@ -0,0 +1,616 @@ +/* Copyright 2008 The Android Open Source Project + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include "binder.h" + +#define MAX_BIO_SIZE (1 << 30) + +#define TRACE 0 + +#define LOG_TAG "Binder" +#include <cutils/log.h> + +void bio_init_from_txn(struct binder_io *io, struct binder_txn *txn); + +#if TRACE +void hexdump(void *_data, unsigned len) +{ + unsigned char *data = _data; + unsigned count; + + for (count = 0; count < len; count++) { + if ((count & 15) == 0) + fprintf(stderr,"%04x:", count); + fprintf(stderr," %02x %c", *data, + (*data < 32) || (*data > 126) ? '.' : *data); + data++; + if ((count & 15) == 15) + fprintf(stderr,"\n"); + } + if ((count & 15) != 0) + fprintf(stderr,"\n"); +} + +void binder_dump_txn(struct binder_txn *txn) +{ + struct binder_object *obj; + unsigned *offs = txn->offs; + unsigned count = txn->offs_size / 4; + + fprintf(stderr," target %p cookie %p code %08x flags %08x\n", + txn->target, txn->cookie, txn->code, txn->flags); + fprintf(stderr," pid %8d uid %8d data %8d offs %8d\n", + txn->sender_pid, txn->sender_euid, txn->data_size, txn->offs_size); + hexdump(txn->data, txn->data_size); + while (count--) { + obj = (void*) (((char*) txn->data) + *offs++); + fprintf(stderr," - type %08x flags %08x ptr %p cookie %p\n", + obj->type, obj->flags, obj->pointer, obj->cookie); + } +} + +#define NAME(n) case n: return #n +const char *cmd_name(uint32_t cmd) +{ + switch(cmd) { + NAME(BR_NOOP); + NAME(BR_TRANSACTION_COMPLETE); + NAME(BR_INCREFS); + NAME(BR_ACQUIRE); + NAME(BR_RELEASE); + NAME(BR_DECREFS); + NAME(BR_TRANSACTION); + NAME(BR_REPLY); + NAME(BR_FAILED_REPLY); + NAME(BR_DEAD_REPLY); + NAME(BR_DEAD_BINDER); + default: return "???"; + } +} +#else +#define hexdump(a,b) do{} while (0) +#define binder_dump_txn(txn) do{} while (0) +#endif + +#define BIO_F_SHARED 0x01 /* needs to be buffer freed */ +#define BIO_F_OVERFLOW 0x02 /* ran out of space */ +#define BIO_F_IOERROR 0x04 +#define BIO_F_MALLOCED 0x08 /* needs to be free()'d */ + +struct binder_state +{ + int fd; + void *mapped; + unsigned mapsize; +}; + +struct binder_state *binder_open(unsigned mapsize) +{ + struct binder_state *bs; + + bs = malloc(sizeof(*bs)); + if (!bs) { + errno = ENOMEM; + return 0; + } + + bs->fd = open("/dev/binder", O_RDWR); + if (bs->fd < 0) { + fprintf(stderr,"binder: cannot open device (%s)\n", + strerror(errno)); + goto fail_open; + } + + bs->mapsize = mapsize; + bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); + if (bs->mapped == MAP_FAILED) { + fprintf(stderr,"binder: cannot map device (%s)\n", + strerror(errno)); + goto fail_map; + } + + /* TODO: check version */ + + return bs; + +fail_map: + close(bs->fd); +fail_open: + free(bs); + return 0; +} + +void binder_close(struct binder_state *bs) +{ + munmap(bs->mapped, bs->mapsize); + close(bs->fd); + free(bs); +} + +int binder_become_context_manager(struct binder_state *bs) +{ + return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); +} + +int binder_write(struct binder_state *bs, void *data, unsigned len) +{ + struct binder_write_read bwr; + int res; + bwr.write_size = len; + bwr.write_consumed = 0; + bwr.write_buffer = (unsigned) data; + bwr.read_size = 0; + bwr.read_consumed = 0; + bwr.read_buffer = 0; + res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); + if (res < 0) { + fprintf(stderr,"binder_write: ioctl failed (%s)\n", + strerror(errno)); + } + return res; +} + +void binder_send_reply(struct binder_state *bs, + struct binder_io *reply, + void *buffer_to_free, + int status) +{ + struct { + uint32_t cmd_free; + void *buffer; + uint32_t cmd_reply; + struct binder_txn txn; + } __attribute__((packed)) data; + + data.cmd_free = BC_FREE_BUFFER; + data.buffer = buffer_to_free; + data.cmd_reply = BC_REPLY; + data.txn.target = 0; + data.txn.cookie = 0; + data.txn.code = 0; + if (status) { + data.txn.flags = TF_STATUS_CODE; + data.txn.data_size = sizeof(int); + data.txn.offs_size = 0; + data.txn.data = &status; + data.txn.offs = 0; + } else { + data.txn.flags = 0; + data.txn.data_size = reply->data - reply->data0; + data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0); + data.txn.data = reply->data0; + data.txn.offs = reply->offs0; + } + binder_write(bs, &data, sizeof(data)); +} + +int binder_parse(struct binder_state *bs, struct binder_io *bio, + uint32_t *ptr, uint32_t size, binder_handler func) +{ + int r = 1; + uint32_t *end = ptr + (size / 4); + + while (ptr < end) { + uint32_t cmd = *ptr++; +#if TRACE + fprintf(stderr,"%s:\n", cmd_name(cmd)); +#endif + switch(cmd) { + case BR_NOOP: + break; + case BR_TRANSACTION_COMPLETE: + break; + case BR_INCREFS: + case BR_ACQUIRE: + case BR_RELEASE: + case BR_DECREFS: +#if TRACE + fprintf(stderr," %08x %08x\n", ptr[0], ptr[1]); +#endif + ptr += 2; + break; + case BR_TRANSACTION: { + struct binder_txn *txn = (void *) ptr; + if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) { + ALOGE("parse: txn too small!\n"); + return -1; + } + binder_dump_txn(txn); + if (func) { + unsigned rdata[256/4]; + struct binder_io msg; + struct binder_io reply; + int res; + + bio_init(&reply, rdata, sizeof(rdata), 4); + bio_init_from_txn(&msg, txn); + res = func(bs, txn, &msg, &reply); + binder_send_reply(bs, &reply, txn->data, res); + } + ptr += sizeof(*txn) / sizeof(uint32_t); + break; + } + case BR_REPLY: { + struct binder_txn *txn = (void*) ptr; + if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) { + ALOGE("parse: reply too small!\n"); + return -1; + } + binder_dump_txn(txn); + if (bio) { + bio_init_from_txn(bio, txn); + bio = 0; + } else { + /* todo FREE BUFFER */ + } + ptr += (sizeof(*txn) / sizeof(uint32_t)); + r = 0; + break; + } + case BR_DEAD_BINDER: { + struct binder_death *death = (void*) *ptr++; + death->func(bs, death->ptr); + break; + } + case BR_FAILED_REPLY: + r = -1; + break; + case BR_DEAD_REPLY: + r = -1; + break; + default: + ALOGE("parse: OOPS %d\n", cmd); + return -1; + } + } + + return r; +} + +void binder_acquire(struct binder_state *bs, void *ptr) +{ + uint32_t cmd[2]; + cmd[0] = BC_ACQUIRE; + cmd[1] = (uint32_t) ptr; + binder_write(bs, cmd, sizeof(cmd)); +} + +void binder_release(struct binder_state *bs, void *ptr) +{ + uint32_t cmd[2]; + cmd[0] = BC_RELEASE; + cmd[1] = (uint32_t) ptr; + binder_write(bs, cmd, sizeof(cmd)); +} + +void binder_link_to_death(struct binder_state *bs, void *ptr, struct binder_death *death) +{ + uint32_t cmd[3]; + cmd[0] = BC_REQUEST_DEATH_NOTIFICATION; + cmd[1] = (uint32_t) ptr; + cmd[2] = (uint32_t) death; + binder_write(bs, cmd, sizeof(cmd)); +} + + +int binder_call(struct binder_state *bs, + struct binder_io *msg, struct binder_io *reply, + void *target, uint32_t code) +{ + int res; + struct binder_write_read bwr; + struct { + uint32_t cmd; + struct binder_txn txn; + } writebuf; + unsigned readbuf[32]; + + if (msg->flags & BIO_F_OVERFLOW) { + fprintf(stderr,"binder: txn buffer overflow\n"); + goto fail; + } + + writebuf.cmd = BC_TRANSACTION; + writebuf.txn.target = target; + writebuf.txn.code = code; + writebuf.txn.flags = 0; + writebuf.txn.data_size = msg->data - msg->data0; + writebuf.txn.offs_size = ((char*) msg->offs) - ((char*) msg->offs0); + writebuf.txn.data = msg->data0; + writebuf.txn.offs = msg->offs0; + + bwr.write_size = sizeof(writebuf); + bwr.write_consumed = 0; + bwr.write_buffer = (unsigned) &writebuf; + + hexdump(msg->data0, msg->data - msg->data0); + for (;;) { + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (unsigned) readbuf; + + res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); + + if (res < 0) { + fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno)); + goto fail; + } + + res = binder_parse(bs, reply, readbuf, bwr.read_consumed, 0); + if (res == 0) return 0; + if (res < 0) goto fail; + } + +fail: + memset(reply, 0, sizeof(*reply)); + reply->flags |= BIO_F_IOERROR; + return -1; +} + +void binder_loop(struct binder_state *bs, binder_handler func) +{ + int res; + struct binder_write_read bwr; + unsigned readbuf[32]; + + bwr.write_size = 0; + bwr.write_consumed = 0; + bwr.write_buffer = 0; + + readbuf[0] = BC_ENTER_LOOPER; + binder_write(bs, readbuf, sizeof(unsigned)); + + for (;;) { + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (unsigned) readbuf; + + res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); + + if (res < 0) { + ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno)); + break; + } + + res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func); + if (res == 0) { + ALOGE("binder_loop: unexpected reply?!\n"); + break; + } + if (res < 0) { + ALOGE("binder_loop: io error %d %s\n", res, strerror(errno)); + break; + } + } +} + +void bio_init_from_txn(struct binder_io *bio, struct binder_txn *txn) +{ + bio->data = bio->data0 = txn->data; + bio->offs = bio->offs0 = txn->offs; + bio->data_avail = txn->data_size; + bio->offs_avail = txn->offs_size / 4; + bio->flags = BIO_F_SHARED; +} + +void bio_init(struct binder_io *bio, void *data, + uint32_t maxdata, uint32_t maxoffs) +{ + uint32_t n = maxoffs * sizeof(uint32_t); + + if (n > maxdata) { + bio->flags = BIO_F_OVERFLOW; + bio->data_avail = 0; + bio->offs_avail = 0; + return; + } + + bio->data = bio->data0 = (char *) data + n; + bio->offs = bio->offs0 = data; + bio->data_avail = maxdata - n; + bio->offs_avail = maxoffs; + bio->flags = 0; +} + +static void *bio_alloc(struct binder_io *bio, uint32_t size) +{ + size = (size + 3) & (~3); + if (size > bio->data_avail) { + bio->flags |= BIO_F_OVERFLOW; + return 0; + } else { + void *ptr = bio->data; + bio->data += size; + bio->data_avail -= size; + return ptr; + } +} + +void binder_done(struct binder_state *bs, + struct binder_io *msg, + struct binder_io *reply) +{ + if (reply->flags & BIO_F_SHARED) { + uint32_t cmd[2]; + cmd[0] = BC_FREE_BUFFER; + cmd[1] = (uint32_t) reply->data0; + binder_write(bs, cmd, sizeof(cmd)); + reply->flags = 0; + } +} + +static struct binder_object *bio_alloc_obj(struct binder_io *bio) +{ + struct binder_object *obj; + + obj = bio_alloc(bio, sizeof(*obj)); + + if (obj && bio->offs_avail) { + bio->offs_avail--; + *bio->offs++ = ((char*) obj) - ((char*) bio->data0); + return obj; + } + + bio->flags |= BIO_F_OVERFLOW; + return 0; +} + +void bio_put_uint32(struct binder_io *bio, uint32_t n) +{ + uint32_t *ptr = bio_alloc(bio, sizeof(n)); + if (ptr) + *ptr = n; +} + +void bio_put_obj(struct binder_io *bio, void *ptr) +{ + struct binder_object *obj; + + obj = bio_alloc_obj(bio); + if (!obj) + return; + + obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj->type = BINDER_TYPE_BINDER; + obj->pointer = ptr; + obj->cookie = 0; +} + +void bio_put_ref(struct binder_io *bio, void *ptr) +{ + struct binder_object *obj; + + if (ptr) + obj = bio_alloc_obj(bio); + else + obj = bio_alloc(bio, sizeof(*obj)); + + if (!obj) + return; + + obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj->type = BINDER_TYPE_HANDLE; + obj->pointer = ptr; + obj->cookie = 0; +} + +void bio_put_string16(struct binder_io *bio, const uint16_t *str) +{ + uint32_t len; + uint16_t *ptr; + + if (!str) { + bio_put_uint32(bio, 0xffffffff); + return; + } + + len = 0; + while (str[len]) len++; + + if (len >= (MAX_BIO_SIZE / sizeof(uint16_t))) { + bio_put_uint32(bio, 0xffffffff); + return; + } + + bio_put_uint32(bio, len); + len = (len + 1) * sizeof(uint16_t); + ptr = bio_alloc(bio, len); + if (ptr) + memcpy(ptr, str, len); +} + +void bio_put_string16_x(struct binder_io *bio, const char *_str) +{ + unsigned char *str = (unsigned char*) _str; + uint32_t len; + uint16_t *ptr; + + if (!str) { + bio_put_uint32(bio, 0xffffffff); + return; + } + + len = strlen(_str); + + if (len >= (MAX_BIO_SIZE / sizeof(uint16_t))) { + bio_put_uint32(bio, 0xffffffff); + return; + } + + bio_put_uint32(bio, len); + ptr = bio_alloc(bio, (len + 1) * sizeof(uint16_t)); + if (!ptr) + return; + + while (*str) + *ptr++ = *str++; + *ptr++ = 0; +} + +static void *bio_get(struct binder_io *bio, uint32_t size) +{ + size = (size + 3) & (~3); + + if (bio->data_avail < size){ + bio->data_avail = 0; + bio->flags |= BIO_F_OVERFLOW; + return 0; + } else { + void *ptr = bio->data; + bio->data += size; + bio->data_avail -= size; + return ptr; + } +} + +uint32_t bio_get_uint32(struct binder_io *bio) +{ + uint32_t *ptr = bio_get(bio, sizeof(*ptr)); + return ptr ? *ptr : 0; +} + +uint16_t *bio_get_string16(struct binder_io *bio, unsigned *sz) +{ + unsigned len; + len = bio_get_uint32(bio); + if (sz) + *sz = len; + return bio_get(bio, (len + 1) * sizeof(uint16_t)); +} + +static struct binder_object *_bio_get_obj(struct binder_io *bio) +{ + unsigned n; + unsigned off = bio->data - bio->data0; + + /* TODO: be smarter about this? */ + for (n = 0; n < bio->offs_avail; n++) { + if (bio->offs[n] == off) + return bio_get(bio, sizeof(struct binder_object)); + } + + bio->data_avail = 0; + bio->flags |= BIO_F_OVERFLOW; + return 0; +} + +void *bio_get_ref(struct binder_io *bio) +{ + struct binder_object *obj; + + obj = _bio_get_obj(bio); + if (!obj) + return 0; + + if (obj->type == BINDER_TYPE_HANDLE) + return obj->pointer; + + return 0; +} diff --git a/cmds/servicemanager/binder.h b/cmds/servicemanager/binder.h new file mode 100644 index 0000000..d8c51ef --- /dev/null +++ b/cmds/servicemanager/binder.h @@ -0,0 +1,119 @@ +/* Copyright 2008 The Android Open Source Project + */ + +#ifndef _BINDER_H_ +#define _BINDER_H_ + +#include <sys/ioctl.h> +#include <linux/binder.h> + +struct binder_state; + +struct binder_object +{ + uint32_t type; + uint32_t flags; + void *pointer; + void *cookie; +}; + +struct binder_txn +{ + void *target; + void *cookie; + uint32_t code; + uint32_t flags; + + uint32_t sender_pid; + uint32_t sender_euid; + + uint32_t data_size; + uint32_t offs_size; + void *data; + void *offs; +}; + +struct binder_io +{ + char *data; /* pointer to read/write from */ + uint32_t *offs; /* array of offsets */ + uint32_t data_avail; /* bytes available in data buffer */ + uint32_t offs_avail; /* entries available in offsets array */ + + char *data0; /* start of data buffer */ + uint32_t *offs0; /* start of offsets buffer */ + uint32_t flags; + uint32_t unused; +}; + +struct binder_death { + void (*func)(struct binder_state *bs, void *ptr); + void *ptr; +}; + +/* the one magic object */ +#define BINDER_SERVICE_MANAGER ((void*) 0) + +#define SVC_MGR_NAME "android.os.IServiceManager" + +enum { + SVC_MGR_GET_SERVICE = 1, + SVC_MGR_CHECK_SERVICE, + SVC_MGR_ADD_SERVICE, + SVC_MGR_LIST_SERVICES, +}; + +typedef int (*binder_handler)(struct binder_state *bs, + struct binder_txn *txn, + struct binder_io *msg, + struct binder_io *reply); + +struct binder_state *binder_open(unsigned mapsize); +void binder_close(struct binder_state *bs); + +/* initiate a blocking binder call + * - returns zero on success + */ +int binder_call(struct binder_state *bs, + struct binder_io *msg, struct binder_io *reply, + void *target, uint32_t code); + +/* release any state associate with the binder_io + * - call once any necessary data has been extracted from the + * binder_io after binder_call() returns + * - can safely be called even if binder_call() fails + */ +void binder_done(struct binder_state *bs, + struct binder_io *msg, struct binder_io *reply); + +/* manipulate strong references */ +void binder_acquire(struct binder_state *bs, void *ptr); +void binder_release(struct binder_state *bs, void *ptr); + +void binder_link_to_death(struct binder_state *bs, void *ptr, struct binder_death *death); + +void binder_loop(struct binder_state *bs, binder_handler func); + +int binder_become_context_manager(struct binder_state *bs); + +/* allocate a binder_io, providing a stack-allocated working + * buffer, size of the working buffer, and how many object + * offset entries to reserve from the buffer + */ +void bio_init(struct binder_io *bio, void *data, + uint32_t maxdata, uint32_t maxobjects); + +void bio_destroy(struct binder_io *bio); + +void bio_put_obj(struct binder_io *bio, void *ptr); +void bio_put_ref(struct binder_io *bio, void *ptr); +void bio_put_uint32(struct binder_io *bio, uint32_t n); +void bio_put_string16(struct binder_io *bio, const uint16_t *str); +void bio_put_string16_x(struct binder_io *bio, const char *_str); + +uint32_t bio_get_uint32(struct binder_io *bio); +uint16_t *bio_get_string16(struct binder_io *bio, uint32_t *sz); +void *bio_get_obj(struct binder_io *bio); +void *bio_get_ref(struct binder_io *bio); + +#endif diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c new file mode 100644 index 0000000..3eaf1eb --- /dev/null +++ b/cmds/servicemanager/service_manager.c @@ -0,0 +1,287 @@ +/* Copyright 2008 The Android Open Source Project + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include <private/android_filesystem_config.h> + +#include "binder.h" + +#if 0 +#define ALOGI(x...) fprintf(stderr, "svcmgr: " x) +#define ALOGE(x...) fprintf(stderr, "svcmgr: " x) +#else +#define LOG_TAG "ServiceManager" +#include <cutils/log.h> +#endif + +/* TODO: + * These should come from a config file or perhaps be + * based on some namespace rules of some sort (media + * uid can register media.*, etc) + */ +static struct { + unsigned uid; + const char *name; +} allowed[] = { + { AID_MEDIA, "media.audio_flinger" }, + { AID_MEDIA, "media.log" }, + { AID_MEDIA, "media.player" }, + { AID_MEDIA, "media.camera" }, + { AID_MEDIA, "media.audio_policy" }, + { AID_DRM, "drm.drmManager" }, + { AID_NFC, "nfc" }, + { AID_BLUETOOTH, "bluetooth" }, + { AID_RADIO, "radio.phone" }, + { AID_RADIO, "radio.sms" }, + { AID_RADIO, "radio.phonesubinfo" }, + { AID_RADIO, "radio.simphonebook" }, +/* TODO: remove after phone services are updated: */ + { AID_RADIO, "phone" }, + { AID_RADIO, "sip" }, + { AID_RADIO, "isms" }, + { AID_RADIO, "iphonesubinfo" }, + { AID_RADIO, "simphonebook" }, + { AID_MEDIA, "common_time.clock" }, + { AID_MEDIA, "common_time.config" }, + { AID_KEYSTORE, "android.security.keystore" }, +}; + +void *svcmgr_handle; + +const char *str8(uint16_t *x) +{ + static char buf[128]; + unsigned max = 127; + char *p = buf; + + if (x) { + while (*x && max--) { + *p++ = *x++; + } + } + *p++ = 0; + return buf; +} + +int str16eq(uint16_t *a, const char *b) +{ + while (*a && *b) + if (*a++ != *b++) return 0; + if (*a || *b) + return 0; + return 1; +} + +int svc_can_register(unsigned uid, uint16_t *name) +{ + unsigned n; + + if ((uid == 0) || (uid == AID_SYSTEM)) + return 1; + + for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++) + if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name)) + return 1; + + return 0; +} + +struct svcinfo +{ + struct svcinfo *next; + void *ptr; + struct binder_death death; + int allow_isolated; + unsigned len; + uint16_t name[0]; +}; + +struct svcinfo *svclist = 0; + +struct svcinfo *find_svc(uint16_t *s16, unsigned len) +{ + struct svcinfo *si; + + for (si = svclist; si; si = si->next) { + if ((len == si->len) && + !memcmp(s16, si->name, len * sizeof(uint16_t))) { + return si; + } + } + return 0; +} + +void svcinfo_death(struct binder_state *bs, void *ptr) +{ + struct svcinfo *si = ptr; + ALOGI("service '%s' died\n", str8(si->name)); + if (si->ptr) { + binder_release(bs, si->ptr); + si->ptr = 0; + } +} + +uint16_t svcmgr_id[] = { + 'a','n','d','r','o','i','d','.','o','s','.', + 'I','S','e','r','v','i','c','e','M','a','n','a','g','e','r' +}; + + +void *do_find_service(struct binder_state *bs, uint16_t *s, unsigned len, unsigned uid) +{ + struct svcinfo *si; + si = find_svc(s, len); + +// ALOGI("check_service('%s') ptr = %p\n", str8(s), si ? si->ptr : 0); + if (si && si->ptr) { + if (!si->allow_isolated) { + // If this service doesn't allow access from isolated processes, + // then check the uid to see if it is isolated. + unsigned appid = uid % AID_USER; + if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) { + return 0; + } + } + return si->ptr; + } else { + return 0; + } +} + +int do_add_service(struct binder_state *bs, + uint16_t *s, unsigned len, + void *ptr, unsigned uid, int allow_isolated) +{ + struct svcinfo *si; + //ALOGI("add_service('%s',%p,%s) uid=%d\n", str8(s), ptr, + // allow_isolated ? "allow_isolated" : "!allow_isolated", uid); + + if (!ptr || (len == 0) || (len > 127)) + return -1; + + if (!svc_can_register(uid, s)) { + ALOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n", + str8(s), ptr, uid); + return -1; + } + + si = find_svc(s, len); + if (si) { + if (si->ptr) { + ALOGE("add_service('%s',%p) uid=%d - ALREADY REGISTERED, OVERRIDE\n", + str8(s), ptr, uid); + svcinfo_death(bs, si); + } + si->ptr = ptr; + } else { + si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t)); + if (!si) { + ALOGE("add_service('%s',%p) uid=%d - OUT OF MEMORY\n", + str8(s), ptr, uid); + return -1; + } + si->ptr = ptr; + si->len = len; + memcpy(si->name, s, (len + 1) * sizeof(uint16_t)); + si->name[len] = '\0'; + si->death.func = svcinfo_death; + si->death.ptr = si; + si->allow_isolated = allow_isolated; + si->next = svclist; + svclist = si; + } + + binder_acquire(bs, ptr); + binder_link_to_death(bs, ptr, &si->death); + return 0; +} + +int svcmgr_handler(struct binder_state *bs, + struct binder_txn *txn, + struct binder_io *msg, + struct binder_io *reply) +{ + struct svcinfo *si; + uint16_t *s; + unsigned len; + void *ptr; + uint32_t strict_policy; + int allow_isolated; + +// ALOGI("target=%p code=%d pid=%d uid=%d\n", +// txn->target, txn->code, txn->sender_pid, txn->sender_euid); + + if (txn->target != svcmgr_handle) + return -1; + + // Equivalent to Parcel::enforceInterface(), reading the RPC + // header with the strict mode policy mask and the interface name. + // Note that we ignore the strict_policy and don't propagate it + // further (since we do no outbound RPCs anyway). + strict_policy = bio_get_uint32(msg); + s = bio_get_string16(msg, &len); + if ((len != (sizeof(svcmgr_id) / 2)) || + memcmp(svcmgr_id, s, sizeof(svcmgr_id))) { + fprintf(stderr,"invalid id %s\n", str8(s)); + return -1; + } + + switch(txn->code) { + case SVC_MGR_GET_SERVICE: + case SVC_MGR_CHECK_SERVICE: + s = bio_get_string16(msg, &len); + ptr = do_find_service(bs, s, len, txn->sender_euid); + if (!ptr) + break; + bio_put_ref(reply, ptr); + return 0; + + case SVC_MGR_ADD_SERVICE: + s = bio_get_string16(msg, &len); + ptr = bio_get_ref(msg); + allow_isolated = bio_get_uint32(msg) ? 1 : 0; + if (do_add_service(bs, s, len, ptr, txn->sender_euid, allow_isolated)) + return -1; + break; + + case SVC_MGR_LIST_SERVICES: { + unsigned n = bio_get_uint32(msg); + + si = svclist; + while ((n-- > 0) && si) + si = si->next; + if (si) { + bio_put_string16(reply, si->name); + return 0; + } + return -1; + } + default: + ALOGE("unknown code %d\n", txn->code); + return -1; + } + + bio_put_uint32(reply, 0); + return 0; +} + +int main(int argc, char **argv) +{ + struct binder_state *bs; + void *svcmgr = BINDER_SERVICE_MANAGER; + + bs = binder_open(128*1024); + + if (binder_become_context_manager(bs)) { + ALOGE("cannot become context manager (%s)\n", strerror(errno)); + return -1; + } + + svcmgr_handle = svcmgr; + binder_loop(bs, svcmgr_handler); + return 0; +} diff --git a/cmds/surfaceflinger/main_surfaceflinger.cpp b/cmds/surfaceflinger/main_surfaceflinger.cpp index 28e58e4..ce7fde0 100644 --- a/cmds/surfaceflinger/main_surfaceflinger.cpp +++ b/cmds/surfaceflinger/main_surfaceflinger.cpp @@ -20,9 +20,9 @@ using namespace android; int main(int argc, char** argv) { - SurfaceFlinger::publishAndJoinThreadPool(true); // When SF is launched in its own process, limit the number of // binder threads to 4. ProcessState::self()->setThreadPoolMaxThreadCount(4); + SurfaceFlinger::publishAndJoinThreadPool(true); return 0; } |