From 27cfab02a013175fafa117b1f49651ba834c2dee Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Mon, 12 Aug 2019 14:34:16 -0700 Subject: servicemanager: notifications You can now register and receive notifications for new services. Eye towards: - avoiding while(true) getService loops - need it to make AIDL dynamic HALs - need it for feature parity w/ HIDL Bug: 136027762 Test: servicemanager_test Change-Id: Iffbf984e87214aa9c9b7af8a5ae682e127ba40a5 --- cmds/servicemanager/ServiceManager.cpp | 97 ++++++++++++ cmds/servicemanager/ServiceManager.h | 25 +++- cmds/servicemanager/test_sm.cpp | 170 +++++++++++++++++++++- libs/binder/Android.bp | 1 + libs/binder/aidl/android/os/IServiceCallback.aidl | 30 ++++ libs/binder/aidl/android/os/IServiceManager.aidl | 12 ++ 6 files changed, 325 insertions(+), 10 deletions(-) create mode 100644 libs/binder/aidl/android/os/IServiceCallback.aidl diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index 119e4c387c..b3aa342a19 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -28,6 +28,13 @@ ServiceManager::ServiceManager(std::unique_ptr&& access) : mAccess(std:: ServiceManager::~ServiceManager() { // this should only happen in tests + for (const auto& [name, callbacks] : mNameToCallback) { + CHECK(!callbacks.empty()) << name; + for (const auto& callback : callbacks) { + CHECK(callback != nullptr) << name; + } + } + for (const auto& [name, service] : mNameToService) { CHECK(service.binder != nullptr) << name; } @@ -117,6 +124,14 @@ Status ServiceManager::addService(const std::string& name, const sp& bi .dumpPriority = dumpPriority, }; + auto it = mNameToCallback.find(name); + if (it != mNameToCallback.end()) { + for (const sp& cb : it->second) { + // permission checked in registerForNotifications + cb->onRegistration(name, binder); + } + } + return Status::ok(); } @@ -146,6 +161,84 @@ Status ServiceManager::listServices(int32_t dumpPriority, std::vector& callback) { + auto ctx = mAccess->getCallingContext(); + + if (!mAccess->canFind(ctx, name)) { + return Status::fromExceptionCode(Status::EX_SECURITY); + } + + if (!isValidServiceName(name)) { + LOG(ERROR) << "Invalid service name: " << name; + return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT); + } + + if (callback == nullptr) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER); + } + + if (OK != IInterface::asBinder(callback)->linkToDeath(this)) { + LOG(ERROR) << "Could not linkToDeath when adding " << name; + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); + } + + mNameToCallback[name].push_back(callback); + + if (auto it = mNameToService.find(name); it != mNameToService.end()) { + const sp& binder = it->second.binder; + + // never null if an entry exists + CHECK(binder != nullptr) << name; + callback->onRegistration(name, binder); + } + + return Status::ok(); +} +Status ServiceManager::unregisterForNotifications( + const std::string& name, const sp& callback) { + auto ctx = mAccess->getCallingContext(); + + if (!mAccess->canFind(ctx, name)) { + return Status::fromExceptionCode(Status::EX_SECURITY); + } + + bool found = false; + + auto it = mNameToCallback.find(name); + if (it != mNameToCallback.end()) { + removeCallback(IInterface::asBinder(callback), &it, &found); + } + + if (!found) { + LOG(ERROR) << "Trying to unregister callback, but none exists " << name; + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); + } + + return Status::ok(); +} + +void ServiceManager::removeCallback(const wp& who, + CallbackMap::iterator* it, + bool* found) { + std::vector>& listeners = (*it)->second; + + for (auto lit = listeners.begin(); lit != listeners.end();) { + if (IInterface::asBinder(*lit) == who) { + if(found) *found = true; + lit = listeners.erase(lit); + } else { + ++lit; + } + } + + if (listeners.empty()) { + *it = mNameToCallback.erase(*it); + } else { + it++; + } +} + void ServiceManager::binderDied(const wp& who) { for (auto it = mNameToService.begin(); it != mNameToService.end();) { if (who == it->second.binder) { @@ -154,6 +247,10 @@ void ServiceManager::binderDied(const wp& who) { ++it; } } + + for (auto it = mNameToCallback.begin(); it != mNameToCallback.end();) { + removeCallback(who, &it, nullptr /*found*/); + } } } // namespace android diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h index 43723c52c7..fcc5124fa2 100644 --- a/cmds/servicemanager/ServiceManager.h +++ b/cmds/servicemanager/ServiceManager.h @@ -17,11 +17,14 @@ #pragma once #include +#include #include "Access.h" namespace android { +using os::IServiceCallback; + class ServiceManager : public os::BnServiceManager, public IBinder::DeathRecipient { public: ServiceManager(std::unique_ptr&& access); @@ -29,19 +32,35 @@ public: binder::Status getService(const std::string& name, sp* outBinder) override; binder::Status checkService(const std::string& name, sp* outBinder) override; - binder::Status addService(const std::string& name, const sp& binder, bool allowIsolated, int32_t dumpPriority) override; + binder::Status addService(const std::string& name, const sp& binder, + bool allowIsolated, int32_t dumpPriority) override; binder::Status listServices(int32_t dumpPriority, std::vector* outList) override; + binder::Status registerForNotifications(const std::string& name, + const sp& callback) override; + binder::Status unregisterForNotifications(const std::string& name, + const sp& callback) override; void binderDied(const wp& who) override; private: struct Service { - sp binder; + sp binder; // not null bool allowIsolated; int32_t dumpPriority; }; - std::map mNameToService; + using CallbackMap = std::map>>; + using ServiceMap = std::map; + + // removes a callback from mNameToCallback, removing it if the vector is empty + // this updates iterator to the next location + void removeCallback(const wp& who, + CallbackMap::iterator* it, + bool* found); + + CallbackMap mNameToCallback; + ServiceMap mNameToService; + std::unique_ptr mAccess; }; diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp index 91485e456b..3c211d285f 100644 --- a/cmds/servicemanager/test_sm.cpp +++ b/cmds/servicemanager/test_sm.cpp @@ -14,7 +14,10 @@ * limitations under the License. */ +#include +#include #include +#include #include #include #include @@ -24,8 +27,11 @@ using android::sp; using android::Access; +using android::BBinder; using android::IBinder; using android::ServiceManager; +using android::binder::Status; +using android::os::BnServiceCallback; using android::os::IServiceManager; using testing::_; using testing::ElementsAre; @@ -33,9 +39,14 @@ using testing::NiceMock; using testing::Return; static sp getBinder() { - // It doesn't matter what remote binder it is, we just need one so that linkToDeath will work. - // The context manager (servicemanager) is easy to get and is in another process. - return android::ProcessState::self()->getContextObject(nullptr); + class LinkableBinder : public BBinder { + android::status_t linkToDeath(const sp&, void*, uint32_t) override { + // let SM linkToDeath + return android::OK; + } + }; + + return new LinkableBinder; } class MockAccess : public Access { @@ -132,12 +143,14 @@ TEST(AddService, NoPermissions) { TEST(GetService, HappyHappy) { auto sm = getPermissiveServiceManager(); - EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/, + sp service = getBinder(); + + EXPECT_TRUE(sm->addService("foo", service, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); sp out; EXPECT_TRUE(sm->getService("foo", &out).isOk()); - EXPECT_EQ(getBinder(), out); + EXPECT_EQ(service, out); } TEST(GetService, NonExistant) { @@ -181,12 +194,13 @@ TEST(GetService, AllowedFromIsolated) { sp sm = new ServiceManager(std::move(access)); - EXPECT_TRUE(sm->addService("foo", getBinder(), true /*allowIsolated*/, + sp service = getBinder(); + EXPECT_TRUE(sm->addService("foo", service, true /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); sp out; EXPECT_TRUE(sm->getService("foo", &out).isOk()); - EXPECT_EQ(getBinder(), out.get()); + EXPECT_EQ(service, out.get()); } TEST(GetService, NotAllowedFromIsolated) { @@ -265,3 +279,145 @@ TEST(ListServices, CriticalServices) { // all there and in the right order EXPECT_THAT(out, ElementsAre("sa")); } + +class CallbackHistorian : public BnServiceCallback { + Status onRegistration(const std::string& name, const sp& binder) override { + registrations.push_back(name); + binders.push_back(binder); + return Status::ok(); + } + + android::status_t linkToDeath(const sp&, void*, uint32_t) override { + // let SM linkToDeath + return android::OK; + } + +public: + std::vector registrations; + std::vector> binders; +}; + +TEST(ServiceNotifications, NoPermissionsRegister) { + std::unique_ptr access = std::make_unique>(); + + EXPECT_CALL(*access, getCallingContext()).WillOnce(Return(Access::CallingContext{})); + EXPECT_CALL(*access, canFind(_,_)).WillOnce(Return(false)); + + sp sm = new ServiceManager(std::move(access)); + + sp cb = new CallbackHistorian; + + EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), + Status::EX_SECURITY); +} + +TEST(ServiceNotifications, NoPermissionsUnregister) { + std::unique_ptr access = std::make_unique>(); + + EXPECT_CALL(*access, getCallingContext()).WillOnce(Return(Access::CallingContext{})); + EXPECT_CALL(*access, canFind(_,_)).WillOnce(Return(false)); + + sp sm = new ServiceManager(std::move(access)); + + sp cb = new CallbackHistorian; + + // should always hit security error first + EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(), + Status::EX_SECURITY); +} + +TEST(ServiceNotifications, InvalidName) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + EXPECT_EQ(sm->registerForNotifications("foo@foo", cb).exceptionCode(), + Status::EX_ILLEGAL_ARGUMENT); +} + +TEST(ServiceNotifications, NullCallback) { + auto sm = getPermissiveServiceManager(); + + EXPECT_EQ(sm->registerForNotifications("foofoo", nullptr).exceptionCode(), + Status::EX_NULL_POINTER); +} + +TEST(ServiceNotifications, Unregister) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + EXPECT_TRUE(sm->registerForNotifications("foofoo", cb).isOk()); + EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(), 0); +} + +TEST(ServiceNotifications, UnregisterWhenNoRegistrationExists) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(), + Status::EX_ILLEGAL_STATE); +} + +TEST(ServiceNotifications, NoNotification) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + EXPECT_TRUE(sm->registerForNotifications("foofoo", cb).isOk()); + EXPECT_TRUE(sm->addService("otherservice", getBinder(), + false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); + + EXPECT_THAT(cb->registrations, ElementsAre()); + EXPECT_THAT(cb->binders, ElementsAre()); +} + +TEST(ServiceNotifications, GetNotification) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + sp service = getBinder(); + + EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk()); + EXPECT_TRUE(sm->addService("asdfasdf", service, + false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); + + EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf")); + EXPECT_THAT(cb->binders, ElementsAre(service)); +} + +TEST(ServiceNotifications, GetNotificationForAlreadyRegisteredService) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + sp service = getBinder(); + + EXPECT_TRUE(sm->addService("asdfasdf", service, + false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); + + EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk()); + + EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf")); + EXPECT_THAT(cb->binders, ElementsAre(service)); +} + +TEST(ServiceNotifications, GetMultipleNotification) { + auto sm = getPermissiveServiceManager(); + + sp cb = new CallbackHistorian; + + sp binder1 = getBinder(); + sp binder2 = getBinder(); + + EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk()); + EXPECT_TRUE(sm->addService("asdfasdf", binder1, + false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); + EXPECT_TRUE(sm->addService("asdfasdf", binder2, + false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()); + + EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf", "asdfasdf")); + EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf", "asdfasdf")); +} diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index 027418a035..86f19c5873 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -145,6 +145,7 @@ filegroup { name: "libbinder_aidl", srcs: [ "aidl/android/content/pm/IPackageManagerNative.aidl", + "aidl/android/os/IServiceCallback.aidl", "aidl/android/os/IServiceManager.aidl", ], path: "aidl", diff --git a/libs/binder/aidl/android/os/IServiceCallback.aidl b/libs/binder/aidl/android/os/IServiceCallback.aidl new file mode 100644 index 0000000000..b29dfedf70 --- /dev/null +++ b/libs/binder/aidl/android/os/IServiceCallback.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 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. + */ + +package android.os; + +/** + * @hide + */ +oneway interface IServiceCallback { + /** + * Called when a service is registered. + * + * @param name the service name that has been registered with + * @param binder the binder that is registered + */ + void onRegistration(@utf8InCpp String name, IBinder binder); +} diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl index 50a72aa9e4..60c2cceaf2 100644 --- a/libs/binder/aidl/android/os/IServiceManager.aidl +++ b/libs/binder/aidl/android/os/IServiceManager.aidl @@ -16,6 +16,8 @@ package android.os; +import android.os.IServiceCallback; + /** * Basic interface for finding and publishing system services. * @@ -77,4 +79,14 @@ interface IServiceManager { * Return a list of all currently running services. */ @utf8InCpp String[] listServices(int dumpPriority); + + /** + * Request a callback when a service is registered. + */ + void registerForNotifications(@utf8InCpp String name, IServiceCallback callback); + + /** + * Unregisters all requests for notifications for a specific callback. + */ + void unregisterForNotifications(@utf8InCpp String name, IServiceCallback callback); } -- cgit v1.2.3-59-g8ed1b