| /* |
| * Copyright (C) 2016 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 DEBUG true |
| #include "Log.h" |
| |
| #include "StatsService.h" |
| #include "config/ConfigKey.h" |
| #include "config/ConfigManager.h" |
| #include "storage/DropboxReader.h" |
| |
| #include <android-base/file.h> |
| #include <binder/IPCThreadState.h> |
| #include <binder/IServiceManager.h> |
| #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h> |
| #include <private/android_filesystem_config.h> |
| #include <utils/Looper.h> |
| #include <utils/String16.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/system_properties.h> |
| #include <unistd.h> |
| |
| using namespace android; |
| |
| namespace android { |
| namespace os { |
| namespace statsd { |
| |
| constexpr const char* kPermissionDump = "android.permission.DUMP"; |
| |
| // ====================================================================== |
| /** |
| * Watches for the death of the stats companion (system process). |
| */ |
| class CompanionDeathRecipient : public IBinder::DeathRecipient { |
| public: |
| CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor); |
| virtual void binderDied(const wp<IBinder>& who); |
| |
| private: |
| const sp<AnomalyMonitor> mAnomalyMonitor; |
| }; |
| |
| CompanionDeathRecipient::CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor) |
| : mAnomalyMonitor(anomalyMonitor) { |
| } |
| |
| void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) { |
| ALOGW("statscompanion service died"); |
| mAnomalyMonitor->setStatsCompanionService(nullptr); |
| } |
| |
| // ====================================================================== |
| StatsService::StatsService(const sp<Looper>& handlerLooper) |
| : mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better |
| { |
| mUidMap = new UidMap(); |
| mConfigManager = new ConfigManager(); |
| mProcessor = new StatsLogProcessor(mUidMap, [](const vector<uint8_t>& log) { |
| // TODO: Update how we send data out of StatsD. |
| }); |
| |
| mConfigManager->AddListener(mProcessor); |
| |
| init_system_properties(); |
| } |
| |
| StatsService::~StatsService() { |
| } |
| |
| void StatsService::init_system_properties() { |
| mEngBuild = false; |
| const prop_info* buildType = __system_property_find("ro.build.type"); |
| if (buildType != NULL) { |
| __system_property_read_callback(buildType, init_build_type_callback, this); |
| } |
| } |
| |
| void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value, |
| uint32_t serial) { |
| if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) { |
| reinterpret_cast<StatsService*>(cookie)->mEngBuild = true; |
| } |
| } |
| |
| /** |
| * Implement our own because the default binder implementation isn't |
| * properly handling SHELL_COMMAND_TRANSACTION. |
| */ |
| status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, |
| uint32_t flags) { |
| status_t err; |
| |
| switch (code) { |
| case SHELL_COMMAND_TRANSACTION: { |
| int in = data.readFileDescriptor(); |
| int out = data.readFileDescriptor(); |
| int err = data.readFileDescriptor(); |
| int argc = data.readInt32(); |
| Vector<String8> args; |
| for (int i = 0; i < argc && data.dataAvail() > 0; i++) { |
| args.add(String8(data.readString16())); |
| } |
| sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder()); |
| sp<IResultReceiver> resultReceiver = |
| IResultReceiver::asInterface(data.readStrongBinder()); |
| |
| FILE* fin = fdopen(in, "r"); |
| FILE* fout = fdopen(out, "w"); |
| FILE* ferr = fdopen(err, "w"); |
| |
| if (fin == NULL || fout == NULL || ferr == NULL) { |
| resultReceiver->send(NO_MEMORY); |
| } else { |
| err = command(fin, fout, ferr, args); |
| resultReceiver->send(err); |
| } |
| |
| if (fin != NULL) { |
| fflush(fin); |
| fclose(fin); |
| } |
| if (fout != NULL) { |
| fflush(fout); |
| fclose(fout); |
| } |
| if (fout != NULL) { |
| fflush(ferr); |
| fclose(ferr); |
| } |
| |
| return NO_ERROR; |
| } |
| default: { return BnStatsManager::onTransact(code, data, reply, flags); } |
| } |
| } |
| |
| /** |
| * Write debugging data about statsd. |
| */ |
| status_t StatsService::dump(int fd, const Vector<String16>& args) { |
| FILE* out = fdopen(fd, "w"); |
| if (out == NULL) { |
| return NO_MEMORY; // the fd is already open |
| } |
| |
| // TODO: Proto format for incident reports |
| dump_impl(out); |
| |
| fclose(out); |
| return NO_ERROR; |
| } |
| |
| /** |
| * Write debugging data about statsd in text format. |
| */ |
| void StatsService::dump_impl(FILE* out) { |
| mConfigManager->Dump(out); |
| } |
| |
| /** |
| * Implementation of the adb shell cmd stats command. |
| */ |
| status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args) { |
| // TODO: Permission check |
| |
| const int argCount = args.size(); |
| if (argCount >= 1) { |
| // adb shell cmd stats config ... |
| if (!args[0].compare(String8("config"))) { |
| return cmd_config(in, out, err, args); |
| } |
| |
| // adb shell cmd stats print-stats-log |
| if (!args[0].compare(String8("print-stats-log")) && args.size() > 1) { |
| return cmd_print_stats_log(out, args); |
| } |
| |
| if (!args[0].compare(String8("print-uid-map"))) { |
| return cmd_print_uid_map(out); |
| } |
| |
| if (!args[0].compare(String8("dump-report"))) { |
| return cmd_dump_report(out, err, args); |
| } |
| |
| if (!args[0].compare(String8("pull-source")) && args.size() > 1) { |
| return cmd_print_pulled_metrics(out, args); |
| } |
| |
| if (!args[0].compare(String8("send-broadcast"))) { |
| return cmd_trigger_broadcast(args); |
| } |
| } |
| |
| print_cmd_help(out); |
| return NO_ERROR; |
| } |
| |
| void StatsService::print_cmd_help(FILE* out) { |
| fprintf(out, |
| "usage: adb shell cmd stats print-stats-log [tag_required] " |
| "[timestamp_nsec_optional]\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "usage: adb shell cmd stats print-uid-map \n"); |
| fprintf(out, "\n"); |
| fprintf(out, " Prints the UID, app name, version mapping.\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "usage: adb shell cmds stats pull-source [int] \n"); |
| fprintf(out, "\n"); |
| fprintf(out, " Prints the output of a pulled metrics source (int indicates source)\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n"); |
| fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n"); |
| fprintf(out, "\n"); |
| fprintf(out, " Adds, updates or removes a configuration. The proto should be in\n"); |
| fprintf(out, " wire-encoded protobuf format and passed via stdin.\n"); |
| fprintf(out, "\n"); |
| fprintf(out, " UID The uid to use. It is only possible to pass the UID\n"); |
| fprintf(out, " parameter on eng builds. If UID is omitted the calling\n"); |
| fprintf(out, " uid is used.\n"); |
| fprintf(out, " NAME The per-uid name to use\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n"); |
| fprintf(out, " Dump all metric data for a configuration.\n"); |
| fprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); |
| fprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); |
| fprintf(out, " calling uid is used.\n"); |
| fprintf(out, " NAME The name of the configuration\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "\n"); |
| fprintf(out, "usage: adb shell cmd stats send-broadcast PACKAGE CLASS\n"); |
| fprintf(out, " Send a broadcast that triggers one subscriber to fetch metrics.\n"); |
| fprintf(out, " PACKAGE The name of the package to receive the broadcast.\n"); |
| fprintf(out, " CLASS The name of the class to receive the broadcast.\n"); |
| } |
| |
| status_t StatsService::cmd_trigger_broadcast(Vector<String8>& args) { |
| auto sc = getStatsCompanionService(); |
| sc->sendBroadcast(String16(args[1]), String16(args[2])); |
| ALOGD("StatsService::trigger broadcast succeeded"); |
| return NO_ERROR; |
| } |
| |
| status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args) { |
| const int argCount = args.size(); |
| if (argCount >= 2) { |
| if (args[1] == "update" || args[1] == "remove") { |
| bool good = false; |
| int uid = -1; |
| string name; |
| |
| if (argCount == 3) { |
| // Automatically pick the UID |
| uid = IPCThreadState::self()->getCallingUid(); |
| // TODO: What if this isn't a binder call? Should we fail? |
| name.assign(args[2].c_str(), args[2].size()); |
| good = true; |
| } else if (argCount == 4) { |
| // If it's a userdebug or eng build, then the shell user can |
| // impersonate other uids. |
| if (mEngBuild) { |
| const char* s = args[2].c_str(); |
| if (*s != '\0') { |
| char* end = NULL; |
| uid = strtol(s, &end, 0); |
| if (*end == '\0') { |
| name.assign(args[3].c_str(), args[3].size()); |
| good = true; |
| } |
| } |
| } else { |
| fprintf(err, |
| "The config can only be set for other UIDs on eng or userdebug " |
| "builds.\n"); |
| } |
| } |
| |
| if (!good) { |
| // If arg parsing failed, print the help text and return an error. |
| print_cmd_help(out); |
| return UNKNOWN_ERROR; |
| } |
| |
| if (args[1] == "update") { |
| // Read stream into buffer. |
| string buffer; |
| if (!android::base::ReadFdToString(fileno(in), &buffer)) { |
| fprintf(err, "Error reading stream for StatsConfig.\n"); |
| return UNKNOWN_ERROR; |
| } |
| |
| // Parse buffer. |
| StatsdConfig config; |
| if (!config.ParseFromString(buffer)) { |
| fprintf(err, "Error parsing proto stream for StatsConfig.\n"); |
| return UNKNOWN_ERROR; |
| } |
| |
| // Add / update the config. |
| mConfigManager->UpdateConfig(ConfigKey(uid, name), config); |
| } else { |
| // Remove the config. |
| mConfigManager->RemoveConfig(ConfigKey(uid, name)); |
| } |
| |
| return NO_ERROR; |
| } |
| } |
| print_cmd_help(out); |
| return UNKNOWN_ERROR; |
| } |
| |
| status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args) { |
| if (mProcessor != nullptr) { |
| const int argCount = args.size(); |
| bool good = false; |
| int uid; |
| string name; |
| if (argCount == 2) { |
| // Automatically pick the UID |
| uid = IPCThreadState::self()->getCallingUid(); |
| // TODO: What if this isn't a binder call? Should we fail? |
| name.assign(args[1].c_str(), args[1].size()); |
| good = true; |
| } else if (argCount == 3) { |
| // If it's a userdebug or eng build, then the shell user can |
| // impersonate other uids. |
| if (mEngBuild) { |
| const char* s = args[1].c_str(); |
| if (*s != '\0') { |
| char* end = NULL; |
| uid = strtol(s, &end, 0); |
| if (*end == '\0') { |
| name.assign(args[2].c_str(), args[2].size()); |
| good = true; |
| } |
| } |
| } else { |
| fprintf(out, |
| "The metrics can only be dumped for other UIDs on eng or userdebug " |
| "builds.\n"); |
| } |
| } |
| if (good) { |
| mProcessor->onDumpReport(ConfigKey(uid, name)); |
| // TODO: print the returned StatsLogReport to file instead of printing to logcat. |
| fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str()); |
| fprintf(out, "See the StatsLogReport in logcat...\n"); |
| return android::OK; |
| } else { |
| // If arg parsing failed, print the help text and return an error. |
| print_cmd_help(out); |
| return UNKNOWN_ERROR; |
| } |
| } else { |
| fprintf(out, "Log processor does not exist...\n"); |
| return UNKNOWN_ERROR; |
| } |
| } |
| |
| status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) { |
| long msec = 0; |
| |
| if (args.size() > 2) { |
| msec = strtol(args[2].string(), NULL, 10); |
| } |
| return DropboxReader::readStatsLogs(out, args[1].string(), msec); |
| } |
| |
| status_t StatsService::cmd_print_uid_map(FILE* out) { |
| mUidMap->printUidMap(out); |
| return NO_ERROR; |
| } |
| |
| status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) { |
| int s = atoi(args[1].c_str()); |
| vector<shared_ptr<LogEvent> > stats; |
| if (mStatsPullerManager.Pull(s, &stats)) { |
| for (const auto& it : stats) { |
| fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str()); |
| } |
| fprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size()); |
| return NO_ERROR; |
| } |
| return UNKNOWN_ERROR; |
| } |
| |
| Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version, |
| const vector<String16>& app) { |
| if (DEBUG) ALOGD("StatsService::informAllUidData was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call informAllUidData"); |
| } |
| |
| mUidMap->updateMap(uid, version, app); |
| if (DEBUG) ALOGD("StatsService::informAllUidData succeeded"); |
| |
| return Status::ok(); |
| } |
| |
| Status StatsService::informOnePackage(const String16& app, int32_t uid, int32_t version) { |
| if (DEBUG) ALOGD("StatsService::informOnePackage was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call informOnePackage"); |
| } |
| mUidMap->updateApp(app, uid, version); |
| return Status::ok(); |
| } |
| |
| Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) { |
| if (DEBUG) ALOGD("StatsService::informOnePackageRemoved was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call informOnePackageRemoved"); |
| } |
| mUidMap->removeApp(app, uid); |
| return Status::ok(); |
| } |
| |
| Status StatsService::informAnomalyAlarmFired() { |
| if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call informAnomalyAlarmFired"); |
| } |
| |
| if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded"); |
| // TODO: check through all counters/timers and see if an anomaly has indeed occurred. |
| |
| return Status::ok(); |
| } |
| |
| Status StatsService::informPollAlarmFired() { |
| if (DEBUG) ALOGD("StatsService::informPollAlarmFired was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call informPollAlarmFired"); |
| } |
| |
| mStatsPullerManager.OnAlarmFired(); |
| |
| if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded"); |
| |
| return Status::ok(); |
| } |
| |
| Status StatsService::systemRunning() { |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call systemRunning"); |
| } |
| |
| // When system_server is up and running, schedule the dropbox task to run. |
| ALOGD("StatsService::systemRunning"); |
| |
| sayHiToStatsCompanion(); |
| |
| return Status::ok(); |
| } |
| |
| void StatsService::sayHiToStatsCompanion() { |
| // TODO: This method needs to be private. It is temporarily public and unsecured for testing |
| // purposes. |
| sp<IStatsCompanionService> statsCompanion = getStatsCompanionService(); |
| if (statsCompanion != nullptr) { |
| if (DEBUG) ALOGD("Telling statsCompanion that statsd is ready"); |
| statsCompanion->statsdReady(); |
| } else { |
| if (DEBUG) ALOGD("Could not access statsCompanion"); |
| } |
| } |
| |
| sp<IStatsCompanionService> StatsService::getStatsCompanionService() { |
| sp<IStatsCompanionService> statsCompanion = nullptr; |
| // Get statscompanion service from service manager |
| const sp<IServiceManager> sm(defaultServiceManager()); |
| if (sm != nullptr) { |
| const String16 name("statscompanion"); |
| statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name)); |
| if (statsCompanion == nullptr) { |
| ALOGW("statscompanion service unavailable!"); |
| return nullptr; |
| } |
| } |
| return statsCompanion; |
| } |
| |
| Status StatsService::statsCompanionReady() { |
| if (DEBUG) ALOGD("StatsService::statsCompanionReady was called"); |
| |
| if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { |
| return Status::fromExceptionCode(Status::EX_SECURITY, |
| "Only system uid can call statsCompanionReady"); |
| } |
| |
| sp<IStatsCompanionService> statsCompanion = getStatsCompanionService(); |
| if (statsCompanion == nullptr) { |
| return Status::fromExceptionCode( |
| Status::EX_NULL_POINTER, |
| "statscompanion unavailable despite it contacting statsd!"); |
| } |
| if (DEBUG) ALOGD("StatsService::statsCompanionReady linking to statsCompanion."); |
| IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor)); |
| mAnomalyMonitor->setStatsCompanionService(statsCompanion); |
| |
| return Status::ok(); |
| } |
| |
| void StatsService::Startup() { |
| mConfigManager->Startup(); |
| } |
| |
| void StatsService::OnLogEvent(const LogEvent& event) { |
| mProcessor->OnLogEvent(event); |
| } |
| |
| Status StatsService::getData(const String16& key, vector<uint8_t>* output) { |
| IPCThreadState* ipc = IPCThreadState::self(); |
| if (checkCallingPermission(String16(kPermissionDump), |
| reinterpret_cast<int32_t*>(ipc->getCallingPid()), |
| reinterpret_cast<int32_t*>(ipc->getCallingUid()))) { |
| // TODO: Implement this. |
| return Status::ok(); |
| } else { |
| return Status::fromExceptionCode(binder::Status::EX_SECURITY); |
| } |
| } |
| |
| Status StatsService::addConfiguration(const String16& key, |
| const vector <uint8_t>& config, |
| const String16& package, const String16& cls, |
| bool* success) { |
| IPCThreadState* ipc = IPCThreadState::self(); |
| int32_t* uid = reinterpret_cast<int32_t*>(ipc->getCallingUid()); |
| if (checkCallingPermission(String16(kPermissionDump), |
| reinterpret_cast<int32_t*>(ipc->getCallingPid()), uid)) { |
| string keyString = string(String8(key).string()); |
| ConfigKey configKey(*uid, keyString); |
| StatsdConfig cfg; |
| cfg.ParseFromArray(&config[0], config.size()); |
| mConfigManager->UpdateConfig(configKey, cfg); |
| mConfigManager->SetConfigReceiver(configKey, string(String8(package).string()), |
| string(String8(cls).string())); |
| *success = true; |
| return Status::ok(); |
| } else { |
| return Status::fromExceptionCode(binder::Status::EX_SECURITY); |
| } |
| } |
| |
| Status StatsService::removeConfiguration(const String16& key, bool* success) { |
| IPCThreadState* ipc = IPCThreadState::self(); |
| if (checkCallingPermission(String16(kPermissionDump), |
| reinterpret_cast<int32_t*>(ipc->getCallingPid()), |
| reinterpret_cast<int32_t*>(ipc->getCallingUid()))) { |
| // TODO: Implement this. |
| return Status::ok(); |
| } else { |
| *success = false; |
| return Status::fromExceptionCode(binder::Status::EX_SECURITY); |
| } |
| } |
| |
| void StatsService::binderDied(const wp<IBinder>& who) { |
| for (size_t i = 0; i < mCallbacks.size(); i++) { |
| if (IInterface::asBinder(mCallbacks[i]) == who) { |
| mCallbacks.removeAt(i); |
| break; |
| } |
| } |
| } |
| |
| } // namespace statsd |
| } // namespace os |
| } // namespace android |