diff options
-rw-r--r-- | cmds/dumpstate/dumpstate.cpp | 121 | ||||
-rw-r--r-- | cmds/dumpstate/tests/dumpstate_test.cpp | 2 | ||||
-rw-r--r-- | libs/binder/Android.bp | 1 | ||||
-rw-r--r-- | libs/binder/ndk/ibinder.cpp | 49 | ||||
-rw-r--r-- | libs/binder/ndk/ibinder_internal.h | 2 | ||||
-rw-r--r-- | libs/binder/ndk/include_ndk/android/binder_interface_utils.h | 43 | ||||
-rw-r--r-- | libs/binder/ndk/include_platform/android/binder_shell.h | 55 | ||||
-rw-r--r-- | libs/binder/ndk/libbinder_ndk.map.txt | 1 | ||||
-rw-r--r-- | libs/binder/ndk/test/Android.bp | 1 | ||||
-rw-r--r-- | libs/binder/ndk/test/libbinder_ndk_unit_test.cpp | 100 |
10 files changed, 339 insertions, 36 deletions
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index aa627d767f..4e14efbcde 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -135,6 +135,11 @@ typedef Dumpstate::ConsentCallback::ConsentResult UserConsentResult; static char cmdline_buf[16384] = "(unknown)"; static const char *dump_traces_path = nullptr; static const uint64_t USER_CONSENT_TIMEOUT_MS = 30 * 1000; +// Because telephony reports are significantly faster to collect (< 10 seconds vs. > 2 minutes), +// it's often the case that they time out far too quickly for consent with such a hefty dialog for +// the user to read. For telephony reports only, we increase the default timeout to 2 minutes to +// roughly match full reports' durations. +static const uint64_t TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS = 2 * 60 * 1000; // TODO: variables and functions below should be part of dumpstate object @@ -890,6 +895,14 @@ static void DoSystemLogcat(time_t since) { CommandOptions::WithTimeoutInMs(timeout_ms).Build()); } +static void DoRadioLogcat() { + unsigned long timeout_ms = logcat_timeout({"radio"}); + RunCommand( + "RADIO LOG", + {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"}, + CommandOptions::WithTimeoutInMs(timeout_ms).Build(), true /* verbose_duration */); +} + static void DoLogcat() { unsigned long timeout_ms; // DumpFile("EVENT LOG TAGS", "/etc/event-log-tags"); @@ -908,11 +921,7 @@ static void DoLogcat() { "STATS LOG", {"logcat", "-b", "stats", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"}, CommandOptions::WithTimeoutInMs(timeout_ms).Build(), true /* verbose_duration */); - timeout_ms = logcat_timeout({"radio"}); - RunCommand( - "RADIO LOG", - {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"}, - CommandOptions::WithTimeoutInMs(timeout_ms).Build(), true /* verbose_duration */); + DoRadioLogcat(); RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"}); @@ -1602,8 +1611,10 @@ static Dumpstate::RunStatus DumpstateDefault() { return status; } -// This method collects common dumpsys for telephony and wifi -static void DumpstateRadioCommon() { +// This method collects common dumpsys for telephony and wifi. Typically, wifi +// reports are fine to include all information, but telephony reports on user +// builds need to strip some content (see DumpstateTelephonyOnly). +static void DumpstateRadioCommon(bool include_sensitive_info = true) { DumpIpTablesAsRoot(); ds.AddDir(LOGPERSIST_DATA_DIR, false); @@ -1612,26 +1623,51 @@ static void DumpstateRadioCommon() { return; } - do_dmesg(); - DoLogcat(); + // We need to be picky about some stuff for telephony reports on user builds. + if (!include_sensitive_info) { + // Only dump the radio log buffer (other buffers and dumps contain too much unrelated info). + DoRadioLogcat(); + } else { + // Contains various system properties and process startup info. + do_dmesg(); + // Logs other than the radio buffer may contain package/component names and potential PII. + DoLogcat(); + // Too broad for connectivity problems. + DoKmsg(); + // Contains unrelated hardware info (camera, NFC, biometrics, ...). + DumpHals(); + } + DumpPacketStats(); - DoKmsg(); DumpIpAddrAndRules(); dump_route_tables(); - DumpHals(); - RunDumpsys("NETWORK DIAGNOSTICS", {"connectivity", "--diag"}, CommandOptions::WithTimeout(10).Build()); } -// This method collects dumpsys for telephony debugging only +// We use "telephony" here for legacy reasons, though this now really means "connectivity" (cellular +// + wifi + networking). This method collects dumpsys for connectivity debugging only. General rules +// for what can be included on user builds: all reported information MUST directly relate to +// connectivity debugging or customer support and MUST NOT contain unrelated personally identifiable +// information. This information MUST NOT identify user-installed packages (UIDs are OK, package +// names are not), and MUST NOT contain logs of user application traffic. +// TODO(b/148168577) rename this and other related fields/methods to "connectivity" instead. static void DumpstateTelephonyOnly() { DurationReporter duration_reporter("DUMPSTATE"); + const CommandOptions DUMPSYS_COMPONENTS_OPTIONS = CommandOptions::WithTimeout(60).Build(); - DumpstateRadioCommon(); + const bool include_sensitive_info = !PropertiesHelper::IsUserBuild(); - RunCommand("SYSTEM PROPERTIES", {"getprop"}); + DumpstateRadioCommon(include_sensitive_info); + + if (include_sensitive_info) { + // Contains too much unrelated PII, and given the unstructured nature of sysprops, we can't + // really cherrypick all of the connectivity-related ones. Apps generally have no business + // reading these anyway, and there should be APIs to supply the info in a more app-friendly + // way. + RunCommand("SYSTEM PROPERTIES", {"getprop"}); + } printf("========================================================\n"); printf("== Android Framework Services\n"); @@ -1639,15 +1675,28 @@ static void DumpstateTelephonyOnly() { RunDumpsys("DUMPSYS", {"connectivity"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); - RunDumpsys("DUMPSYS", {"connmetrics"}, CommandOptions::WithTimeout(90).Build(), - SEC_TO_MSEC(10)); - RunDumpsys("DUMPSYS", {"netd"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); + // TODO(b/146521742) build out an argument to include bound services here for user builds RunDumpsys("DUMPSYS", {"carrier_config"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); RunDumpsys("DUMPSYS", {"wifi"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); - RunDumpsys("BATTERYSTATS", {"batterystats"}, CommandOptions::WithTimeout(90).Build(), + RunDumpsys("DUMPSYS", {"netpolicy"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); + RunDumpsys("DUMPSYS", {"network_management"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); + if (include_sensitive_info) { + // Contains raw IP addresses, omit from reports on user builds. + RunDumpsys("DUMPSYS", {"netd"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); + // Contains raw destination IP/MAC addresses, omit from reports on user builds. + RunDumpsys("DUMPSYS", {"connmetrics"}, CommandOptions::WithTimeout(90).Build(), + SEC_TO_MSEC(10)); + // Contains package/component names, omit from reports on user builds. + RunDumpsys("BATTERYSTATS", {"batterystats"}, CommandOptions::WithTimeout(90).Build(), + SEC_TO_MSEC(10)); + // Contains package names, but should be relatively simple to remove them (also contains + // UIDs already), omit from reports on user builds. + RunDumpsys("BATTERYSTATS", {"deviceidle"}, CommandOptions::WithTimeout(90).Build(), + SEC_TO_MSEC(10)); + } printf("========================================================\n"); printf("== Running Application Services\n"); @@ -1655,18 +1704,24 @@ static void DumpstateTelephonyOnly() { RunDumpsys("TELEPHONY SERVICES", {"activity", "service", "TelephonyDebugService"}); - printf("========================================================\n"); - printf("== Running Application Services (non-platform)\n"); - printf("========================================================\n"); + if (include_sensitive_info) { + printf("========================================================\n"); + printf("== Running Application Services (non-platform)\n"); + printf("========================================================\n"); - RunDumpsys("APP SERVICES NON-PLATFORM", {"activity", "service", "all-non-platform"}, - DUMPSYS_COMPONENTS_OPTIONS); + // Contains package/component names and potential PII, omit from reports on user builds. + // To get dumps of the active CarrierService(s) on user builds, we supply an argument to the + // carrier_config dumpsys instead. + RunDumpsys("APP SERVICES NON-PLATFORM", {"activity", "service", "all-non-platform"}, + DUMPSYS_COMPONENTS_OPTIONS); - printf("========================================================\n"); - printf("== Checkins\n"); - printf("========================================================\n"); + printf("========================================================\n"); + printf("== Checkins\n"); + printf("========================================================\n"); - RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"}); + // Contains package/component names, omit from reports on user builds. + RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"}); + } printf("========================================================\n"); printf("== dumpstate: done (id %d)\n", ds.id_); @@ -2278,6 +2333,7 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt break; case Dumpstate::BugreportMode::BUGREPORT_TELEPHONY: options->telephony_only = true; + options->do_progress_updates = true; options->do_fb = false; options->do_broadcast = true; break; @@ -2828,8 +2884,13 @@ Dumpstate::RunStatus Dumpstate::CopyBugreportIfUserConsented() { if (consent_result == UserConsentResult::UNAVAILABLE) { // User has not responded yet. uint64_t elapsed_ms = consent_callback_->getElapsedTimeMs(); - if (elapsed_ms < USER_CONSENT_TIMEOUT_MS) { - uint delay_seconds = (USER_CONSENT_TIMEOUT_MS - elapsed_ms) / 1000; + // Telephony is a fast report type, particularly on user builds where information may be + // more aggressively limited. To give the user time to read the consent dialog, increase the + // timeout. + uint64_t timeout_ms = options_->telephony_only ? TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS + : USER_CONSENT_TIMEOUT_MS; + if (elapsed_ms < timeout_ms) { + uint delay_seconds = (timeout_ms - elapsed_ms) / 1000; MYLOGD("Did not receive user consent yet; going to wait for %d seconds", delay_seconds); sleep(delay_seconds); } diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index cff1d439d9..99d482f034 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -372,12 +372,12 @@ TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { EXPECT_TRUE(options_.do_broadcast); EXPECT_TRUE(options_.do_zip_file); EXPECT_TRUE(options_.telephony_only); + EXPECT_TRUE(options_.do_progress_updates); // Other options retain default values EXPECT_TRUE(options_.do_vibrate); EXPECT_FALSE(options_.use_control_socket); EXPECT_FALSE(options_.show_header_only); - EXPECT_FALSE(options_.do_progress_updates); EXPECT_FALSE(options_.is_remote_mode); EXPECT_FALSE(options_.use_socket); } diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index 5f9d4004f4..bc541f4d31 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -170,7 +170,6 @@ aidl_interface { name: "libbinder_aidl_test_stub", local_include_dir: "aidl", srcs: [":libbinder_aidl"], - visibility: [":__subpackages__"], vendor_available: true, backend: { java: { diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp index e752c45d60..75dcdc8389 100644 --- a/libs/binder/ndk/ibinder.cpp +++ b/libs/binder/ndk/ibinder.cpp @@ -24,10 +24,13 @@ #include <android-base/logging.h> #include <binder/IPCThreadState.h> +#include <binder/IResultReceiver.h> +#include <private/android_filesystem_config.h> using DeathRecipient = ::android::IBinder::DeathRecipient; using ::android::IBinder; +using ::android::IResultReceiver; using ::android::Parcel; using ::android::sp; using ::android::status_t; @@ -158,6 +161,45 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce binder_status_t status = getClass()->onTransact(this, code, &in, &out); return PruneStatusT(status); + } else if (code == SHELL_COMMAND_TRANSACTION) { + int in = data.readFileDescriptor(); + int out = data.readFileDescriptor(); + int err = data.readFileDescriptor(); + + int argc = data.readInt32(); + std::vector<String8> utf8Args; // owns memory of utf8s + std::vector<const char*> utf8Pointers; // what can be passed over NDK API + for (int i = 0; i < argc && data.dataAvail() > 0; i++) { + utf8Args.push_back(String8(data.readString16())); + utf8Pointers.push_back(utf8Args[i].c_str()); + } + + data.readStrongBinder(); // skip over the IShellCallback + sp<IResultReceiver> resultReceiver = IResultReceiver::asInterface(data.readStrongBinder()); + + // Shell commands should only be callable by ADB. + uid_t uid = AIBinder_getCallingUid(); + if (uid != AID_ROOT && uid != AID_SHELL) { + if (resultReceiver != nullptr) { + resultReceiver->send(-1); + } + return STATUS_PERMISSION_DENIED; + } + + // Check that the file descriptors are valid. + if (in == STATUS_BAD_TYPE || out == STATUS_BAD_TYPE || err == STATUS_BAD_TYPE) { + if (resultReceiver != nullptr) { + resultReceiver->send(-1); + } + return STATUS_BAD_VALUE; + } + + binder_status_t status = getClass()->handleShellCommand( + this, in, out, err, utf8Pointers.data(), utf8Pointers.size()); + if (resultReceiver != nullptr) { + resultReceiver->send(status); + } + return status; } else { return BBinder::onTransact(code, data, reply, flags); } @@ -266,6 +308,13 @@ void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) { clazz->onDump = onDump; } +void AIBinder_Class_setHandleShellCommand(AIBinder_Class* clazz, + AIBinder_handleShellCommand handleShellCommand) { + CHECK(clazz != nullptr) << "setHandleShellCommand requires non-null clazz"; + + clazz->handleShellCommand = handleShellCommand; +} + void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) { CHECK(who == mWho); diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h index 5cb68c291b..57794279f2 100644 --- a/libs/binder/ndk/ibinder_internal.h +++ b/libs/binder/ndk/ibinder_internal.h @@ -17,6 +17,7 @@ #pragma once #include <android/binder_ibinder.h> +#include <android/binder_shell.h> #include "ibinder_internal.h" #include <atomic> @@ -115,6 +116,7 @@ struct AIBinder_Class { // optional methods for a class AIBinder_onDump onDump; + AIBinder_handleShellCommand handleShellCommand; private: // This must be a String16 since BBinder virtual getInterfaceDescriptor returns a reference to diff --git a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h index 83a10488e0..7331ba20c4 100644 --- a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h +++ b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h @@ -30,6 +30,11 @@ #include <android/binder_auto_utils.h> #include <android/binder_ibinder.h> +#if __has_include(<android/binder_shell.h>) +#include <android/binder_shell.h> +#define HAS_BINDER_SHELL_COMMAND +#endif //_has_include + #include <assert.h> #include <memory> @@ -108,7 +113,15 @@ class ICInterface : public SharedRefBase { /** * Dumps information about the interface. By default, dumps nothing. */ - virtual inline binder_status_t dump(int /*fd*/, const char** /*args*/, uint32_t /*numArgs*/); + virtual inline binder_status_t dump(int fd, const char** args, uint32_t numArgs); + +#ifdef HAS_BINDER_SHELL_COMMAND + /** + * Process shell commands. By default, does nothing. + */ + virtual inline binder_status_t handleShellCommand(int in, int out, int err, const char** argv, + uint32_t argc); +#endif /** * Interprets this binder as this underlying interface if this has stored an ICInterface in the @@ -136,6 +149,11 @@ class ICInterface : public SharedRefBase { static inline void onDestroy(void* userData); static inline binder_status_t onDump(AIBinder* binder, int fd, const char** args, uint32_t numArgs); + +#ifdef HAS_BINDER_SHELL_COMMAND + static inline binder_status_t handleShellCommand(AIBinder* binder, int in, int out, int err, + const char** argv, uint32_t argc); +#endif }; }; @@ -191,6 +209,13 @@ binder_status_t ICInterface::dump(int /*fd*/, const char** /*args*/, uint32_t /* return STATUS_OK; } +#ifdef HAS_BINDER_SHELL_COMMAND +binder_status_t ICInterface::handleShellCommand(int /*in*/, int /*out*/, int /*err*/, + const char** /*argv*/, uint32_t /*argc*/) { + return STATUS_OK; +} +#endif + std::shared_ptr<ICInterface> ICInterface::asInterface(AIBinder* binder) { return ICInterfaceData::getInterface(binder); } @@ -203,9 +228,12 @@ AIBinder_Class* ICInterface::defineClass(const char* interfaceDescriptor, return nullptr; } - // We can't know if this method is overriden by a subclass interface, so we must register - // ourselves. The default (nothing to dump) is harmless. + // We can't know if these methods are overridden by a subclass interface, so we must register + // ourselves. The defaults are harmless. AIBinder_Class_setOnDump(clazz, ICInterfaceData::onDump); +#ifdef HAS_BINDER_SHELL_COMMAND + AIBinder_Class_setHandleShellCommand(clazz, ICInterfaceData::handleShellCommand); +#endif return clazz; } @@ -234,6 +262,15 @@ binder_status_t ICInterface::ICInterfaceData::onDump(AIBinder* binder, int fd, c return interface->dump(fd, args, numArgs); } +#ifdef HAS_BINDER_SHELL_COMMAND +binder_status_t ICInterface::ICInterfaceData::handleShellCommand(AIBinder* binder, int in, int out, + int err, const char** argv, + uint32_t argc) { + std::shared_ptr<ICInterface> interface = getInterface(binder); + return interface->handleShellCommand(in, out, err, argv, argc); +} +#endif + template <typename INTERFACE> SpAIBinder BnCInterface<INTERFACE>::asBinder() { std::lock_guard<std::mutex> l(mMutex); diff --git a/libs/binder/ndk/include_platform/android/binder_shell.h b/libs/binder/ndk/include_platform/android/binder_shell.h new file mode 100644 index 0000000000..17b38b0dae --- /dev/null +++ b/libs/binder/ndk/include_platform/android/binder_shell.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include <android/binder_ibinder.h> + +__BEGIN_DECLS + +/** + * Function to execute a shell command. + * + * Available since API level 30. + * + * \param binder the binder executing the command + * \param in input file descriptor, should be flushed, ownership is not passed + * \param out output file descriptor, should be flushed, ownership is not passed + * \param err error file descriptor, should be flushed, ownership is not passed + * \param argv array of null-terminated strings for command (may be null if argc + * is 0) + * \param argc length of argv array + * + * \return binder_status_t result of transaction + */ +typedef binder_status_t (*AIBinder_handleShellCommand)(AIBinder* binder, int in, int out, int err, + const char** argv, uint32_t argc); + +/** + * This sets the implementation of handleShellCommand for a class. + * + * If this isn't set, nothing will be executed when handleShellCommand is called. + * + * Available since API level 30. + * + * \param handleShellCommand function to call when a shell transaction is + * received + */ +void AIBinder_Class_setHandleShellCommand(AIBinder_Class* clazz, + AIBinder_handleShellCommand handleShellCommand) + __INTRODUCED_IN(30); + +__END_DECLS diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt index f3158d7e18..7e72f22d64 100644 --- a/libs/binder/ndk/libbinder_ndk.map.txt +++ b/libs/binder/ndk/libbinder_ndk.map.txt @@ -110,6 +110,7 @@ LIBBINDER_NDK30 { # introduced=30 AIBinder_markSystemStability; # apex AIBinder_markVendorStability; # llndk AIBinder_markVintfStability; # apex llndk + AIBinder_Class_setHandleShellCommand; # apex llndk local: *; }; diff --git a/libs/binder/ndk/test/Android.bp b/libs/binder/ndk/test/Android.bp index 513d8c2eba..cb4b20ff9d 100644 --- a/libs/binder/ndk/test/Android.bp +++ b/libs/binder/ndk/test/Android.bp @@ -60,6 +60,7 @@ cc_test { defaults: ["test_libbinder_ndk_test_defaults"], srcs: ["libbinder_ndk_unit_test.cpp"], static_libs: [ + "IBinderNdkUnitTest-cpp", "IBinderNdkUnitTest-ndk_platform", ], test_suites: ["general-tests"], diff --git a/libs/binder/ndk/test/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/test/libbinder_ndk_unit_test.cpp index 51dd169ec0..fd30d87c76 100644 --- a/libs/binder/ndk/test/libbinder_ndk_unit_test.cpp +++ b/libs/binder/ndk/test/libbinder_ndk_unit_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <IBinderNdkUnitTest.h> #include <aidl/BnBinderNdkUnitTest.h> #include <aidl/BnEmpty.h> #include <android-base/logging.h> @@ -26,13 +27,16 @@ // warning: this is assuming that libbinder_ndk is using the same copy // of libbinder that we are. #include <binder/IPCThreadState.h> +#include <binder/IResultReceiver.h> +#include <binder/IServiceManager.h> +#include <binder/IShellCallback.h> #include <sys/prctl.h> #include <chrono> #include <condition_variable> #include <mutex> -using ::android::sp; +using namespace android; constexpr char kExistingNonNdkService[] = "SurfaceFlinger"; constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest"; @@ -48,6 +52,14 @@ class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest { android::IPCThreadState::self()->flushCommands(); return ndk::ScopedAStatus::ok(); } + binder_status_t handleShellCommand(int /*in*/, int out, int /*err*/, const char** args, + uint32_t numArgs) override { + for (uint32_t i = 0; i < numArgs; i++) { + dprintf(out, "%s", args[i]); + } + fsync(out); + return STATUS_OK; + } }; int generatedService() { @@ -296,6 +308,92 @@ TEST(NdkBinder, SentAidlBinderCanBeDestroyed) { EXPECT_TRUE(destroyed); } +class MyResultReceiver : public BnResultReceiver { + public: + Mutex mMutex; + Condition mCondition; + bool mHaveResult = false; + int32_t mResult = 0; + + virtual void send(int32_t resultCode) { + AutoMutex _l(mMutex); + mResult = resultCode; + mHaveResult = true; + mCondition.signal(); + } + + int32_t waitForResult() { + AutoMutex _l(mMutex); + while (!mHaveResult) { + mCondition.wait(mMutex); + } + return mResult; + } +}; + +class MyShellCallback : public BnShellCallback { + public: + virtual int openFile(const String16& /*path*/, const String16& /*seLinuxContext*/, + const String16& /*mode*/) { + // Empty implementation. + return 0; + } +}; + +bool ReadFdToString(int fd, std::string* content) { + char buf[64]; + ssize_t n; + while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], sizeof(buf)))) > 0) { + content->append(buf, n); + } + return (n == 0) ? true : false; +} + +std::string shellCmdToString(sp<IBinder> unitTestService, const std::vector<const char*>& args) { + int inFd[2] = {-1, -1}; + int outFd[2] = {-1, -1}; + int errFd[2] = {-1, -1}; + + EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, inFd)); + EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, outFd)); + EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, errFd)); + + sp<MyShellCallback> cb = new MyShellCallback(); + sp<MyResultReceiver> resultReceiver = new MyResultReceiver(); + + Vector<String16> argsVec; + for (int i = 0; i < args.size(); i++) { + argsVec.add(String16(args[i])); + } + status_t error = IBinder::shellCommand(unitTestService, inFd[0], outFd[0], errFd[0], argsVec, + cb, resultReceiver); + EXPECT_EQ(error, android::OK); + + status_t res = resultReceiver->waitForResult(); + EXPECT_EQ(res, android::OK); + + close(inFd[0]); + close(inFd[1]); + close(outFd[0]); + close(errFd[0]); + close(errFd[1]); + + std::string ret; + EXPECT_TRUE(ReadFdToString(outFd[1], &ret)); + close(outFd[1]); + return ret; +} + +TEST(NdkBinder, UseHandleShellCommand) { + static const sp<android::IServiceManager> sm(android::defaultServiceManager()); + sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestService)); + + EXPECT_EQ("", shellCmdToString(testService, {})); + EXPECT_EQ("", shellCmdToString(testService, {"", ""})); + EXPECT_EQ("Hello world!", shellCmdToString(testService, {"Hello ", "world!"})); + EXPECT_EQ("CMD", shellCmdToString(testService, {"C", "M", "D"})); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); |