| /* | |
| * 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 false | |
| #include "Log.h" | |
| #include "IncidentService.h" | |
| #include "FdBuffer.h" | |
| #include "PrivacyFilter.h" | |
| #include "Reporter.h" | |
| #include "incidentd_util.h" | |
| #include "section_list.h" | |
| #include <android/os/IncidentReportArgs.h> | |
| #include <binder/IPCThreadState.h> | |
| #include <binder/IResultReceiver.h> | |
| #include <binder/IServiceManager.h> | |
| #include <binder/IShellCallback.h> | |
| #include <log/log.h> | |
| #include <private/android_filesystem_config.h> | |
| #include <utils/Looper.h> | |
| #include <thread> | |
| #include <unistd.h> | |
| enum { | |
| WHAT_TAKE_REPORT = 1, | |
| WHAT_SEND_BROADCASTS = 2 | |
| }; | |
| #define DEFAULT_DELAY_NS (1000000000LL) | |
| #define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024) // 96MB | |
| #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day | |
| // Skip these sections (for dumpstate only) | |
| // Skip logs (1100 - 1108) and traces (1200 - 1202) because they are already in the bug report. | |
| #define SKIPPED_DUMPSTATE_SECTIONS { \ | |
| 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \ | |
| 1200, 1201, 1202, /* Native, hal, java traces */ } | |
| namespace android { | |
| namespace os { | |
| namespace incidentd { | |
| String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS"); | |
| String16 const DUMP_PERMISSION("android.permission.DUMP"); | |
| String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); | |
| static Status checkIncidentPermissions(const IncidentReportArgs& args) { | |
| uid_t callingUid = IPCThreadState::self()->getCallingUid(); | |
| pid_t callingPid = IPCThreadState::self()->getCallingPid(); | |
| if (callingUid == AID_ROOT || callingUid == AID_SHELL) { | |
| // Root and shell are ok. | |
| return Status::ok(); | |
| } | |
| if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) { | |
| // Permission controller (this is a singleton permission that is always granted | |
| // exactly for PermissionController) is allowed to access incident reports | |
| // so it can show the user info about what they are approving. | |
| return Status::ok(); | |
| } | |
| // checking calling permission. | |
| if (!checkCallingPermission(DUMP_PERMISSION)) { | |
| ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", | |
| callingPid, callingUid); | |
| return Status::fromExceptionCode( | |
| Status::EX_SECURITY, | |
| "Calling process does not have permission: android.permission.DUMP"); | |
| } | |
| if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { | |
| ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", | |
| callingPid, callingUid); | |
| return Status::fromExceptionCode( | |
| Status::EX_SECURITY, | |
| "Calling process does not have permission: android.permission.USAGE_STATS"); | |
| } | |
| // checking calling request uid permission. | |
| switch (args.getPrivacyPolicy()) { | |
| case PRIVACY_POLICY_LOCAL: | |
| if (callingUid != AID_SHELL && callingUid != AID_ROOT) { | |
| ALOGW("Calling pid %d and uid %d does not have permission to get local data.", | |
| callingPid, callingUid); | |
| return Status::fromExceptionCode( | |
| Status::EX_SECURITY, | |
| "Calling process does not have permission to get local data."); | |
| } | |
| break; | |
| case PRIVACY_POLICY_EXPLICIT: | |
| if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD && | |
| callingUid != AID_SYSTEM) { | |
| ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.", | |
| callingPid, callingUid); | |
| return Status::fromExceptionCode( | |
| Status::EX_SECURITY, | |
| "Calling process does not have permission to get explicit data."); | |
| } | |
| break; | |
| } | |
| return Status::ok(); | |
| } | |
| static string build_uri(const string& pkg, const string& cls, const string& id) { | |
| return "content://android.os.IncidentManager/pending?pkg=" | |
| + pkg + "&receiver=" + cls + "&r=" + id; | |
| } | |
| // ================================================================================ | |
| ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory, | |
| const sp<Broadcaster>& broadcaster, | |
| const sp<Looper>& handlerLooper, | |
| const sp<Throttler>& throttler, | |
| const vector<BringYourOwnSection*>& registeredSections) | |
| :mLock(), | |
| mWorkDirectory(workDirectory), | |
| mBroadcaster(broadcaster), | |
| mHandlerLooper(handlerLooper), | |
| mBacklogDelay(DEFAULT_DELAY_NS), | |
| mThrottler(throttler), | |
| mRegisteredSections(registeredSections), | |
| mBatch(new ReportBatch()) { | |
| } | |
| ReportHandler::~ReportHandler() { | |
| } | |
| void ReportHandler::handleMessage(const Message& message) { | |
| switch (message.what) { | |
| case WHAT_TAKE_REPORT: | |
| take_report(); | |
| break; | |
| case WHAT_SEND_BROADCASTS: | |
| send_broadcasts(); | |
| break; | |
| } | |
| } | |
| void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) { | |
| unique_lock<mutex> lock(mLock); | |
| mBatch->addPersistedReport(args); | |
| mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); | |
| mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); | |
| } | |
| void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args, | |
| const sp<IIncidentReportStatusListener>& listener, int streamFd) { | |
| unique_lock<mutex> lock(mLock); | |
| mBatch->addStreamingReport(args, listener, streamFd); | |
| mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); | |
| mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); | |
| } | |
| void ReportHandler::scheduleSendBacklog() { | |
| unique_lock<mutex> lock(mLock); | |
| mBacklogDelay = DEFAULT_DELAY_NS; | |
| schedule_send_broadcasts_locked(); | |
| } | |
| void ReportHandler::schedule_send_broadcasts_locked() { | |
| mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS); | |
| mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS)); | |
| } | |
| void ReportHandler::take_report() { | |
| // Cycle the batch and throttle. | |
| sp<ReportBatch> batch; | |
| { | |
| unique_lock<mutex> lock(mLock); | |
| batch = mThrottler->filterBatch(mBatch); | |
| } | |
| if (batch->empty()) { | |
| // Nothing to do. | |
| return; | |
| } | |
| sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections); | |
| // Take the report, which might take a while. More requests might queue | |
| // up while we're doing this, and we'll handle them in their next batch. | |
| // TODO: We should further rate-limit the reports to no more than N per time-period. | |
| // TODO: Move this inside reporter. | |
| size_t reportByteSize = 0; | |
| reporter->runReport(&reportByteSize); | |
| // Tell the throttler how big it was, for the next throttling. | |
| // TODO: This still isn't ideal. The throttler really should just track the | |
| // persisted reqeusts, but changing Reporter::runReport() to track that individually | |
| // will be a big change. | |
| if (batch->hasPersistedReports()) { | |
| mThrottler->addReportSize(reportByteSize); | |
| } | |
| // Kick off the next steps, one of which is to send any new or otherwise remaining | |
| // approvals, and one of which is to send any new or remaining broadcasts. | |
| { | |
| unique_lock<mutex> lock(mLock); | |
| schedule_send_broadcasts_locked(); | |
| } | |
| } | |
| void ReportHandler::send_broadcasts() { | |
| Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts(); | |
| if (result == Broadcaster::BROADCASTS_FINISHED) { | |
| // We're done. | |
| unique_lock<mutex> lock(mLock); | |
| mBacklogDelay = DEFAULT_DELAY_NS; | |
| } else if (result == Broadcaster::BROADCASTS_REPEAT) { | |
| // It worked, but there are more. | |
| unique_lock<mutex> lock(mLock); | |
| mBacklogDelay = DEFAULT_DELAY_NS; | |
| schedule_send_broadcasts_locked(); | |
| } else if (result == Broadcaster::BROADCASTS_BACKOFF) { | |
| // There was a failure. Exponential backoff. | |
| unique_lock<mutex> lock(mLock); | |
| mBacklogDelay *= 2; | |
| ALOGI("Error sending to dropbox. Trying again in %lld minutes", | |
| (mBacklogDelay / (1000000000LL * 60))); | |
| schedule_send_broadcasts_locked(); | |
| } | |
| } | |
| // ================================================================================ | |
| IncidentService::IncidentService(const sp<Looper>& handlerLooper) { | |
| mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS); | |
| mWorkDirectory = new WorkDirectory(); | |
| mBroadcaster = new Broadcaster(mWorkDirectory); | |
| mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper, | |
| mThrottler, mRegisteredSections); | |
| mBroadcaster->setHandler(mHandler); | |
| } | |
| IncidentService::~IncidentService() {} | |
| Status IncidentService::reportIncident(const IncidentReportArgs& args) { | |
| IncidentReportArgs argsCopy(args); | |
| // Validate that the privacy policy is one of the real ones. | |
| // If it isn't, clamp it to the next more restrictive real one. | |
| argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy())); | |
| // TODO: Check that the broadcast recevier has the proper permissions | |
| // TODO: Maybe we should consider relaxing the permissions if it's going to | |
| // dropbox, but definitely not if it's going to the broadcaster. | |
| Status status = checkIncidentPermissions(args); | |
| if (!status.isOk()) { | |
| return status; | |
| } | |
| // If they asked for the LOCAL privacy policy, give them EXPLICT. LOCAL has to | |
| // be streamed. (This only applies to shell/root, because everyone else would have | |
| // been rejected by checkIncidentPermissions()). | |
| if (argsCopy.getPrivacyPolicy() < PRIVACY_POLICY_EXPLICIT) { | |
| ALOGI("Demoting privacy policy to EXPLICT for persisted report."); | |
| argsCopy.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT); | |
| } | |
| // If they didn't specify a component, use dropbox. | |
| if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) { | |
| argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName()); | |
| argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName()); | |
| } | |
| mHandler->schedulePersistedReport(argsCopy); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args, | |
| const sp<IIncidentReportStatusListener>& listener, | |
| unique_fd stream) { | |
| IncidentReportArgs argsCopy(args); | |
| // Streaming reports can not also be broadcast. | |
| argsCopy.setReceiverPkg(""); | |
| argsCopy.setReceiverCls(""); | |
| // Validate that the privacy policy is one of the real ones. | |
| // If it isn't, clamp it to the next more restrictive real one. | |
| argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy())); | |
| Status status = checkIncidentPermissions(argsCopy); | |
| if (!status.isOk()) { | |
| return status; | |
| } | |
| // The ReportRequest takes ownership of the fd, so we need to dup it. | |
| int fd = dup(stream.get()); | |
| if (fd < 0) { | |
| return Status::fromStatusT(-errno); | |
| } | |
| mHandler->scheduleStreamingReport(argsCopy, listener, fd); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::reportIncidentToDumpstate(unique_fd stream, | |
| const sp<IIncidentReportStatusListener>& listener) { | |
| uid_t caller = IPCThreadState::self()->getCallingUid(); | |
| if (caller != AID_ROOT && caller != AID_SHELL) { | |
| ALOGW("Calling uid %d does not have permission: only ROOT or SHELL allowed", caller); | |
| return Status::fromExceptionCode(Status::EX_SECURITY, "Only ROOT or SHELL allowed"); | |
| } | |
| ALOGD("Stream incident report to dumpstate"); | |
| IncidentReportArgs incidentArgs; | |
| // Privacy policy for dumpstate incident reports is always EXPLICIT. | |
| incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT); | |
| int skipped[] = SKIPPED_DUMPSTATE_SECTIONS; | |
| for (const Section** section = SECTION_LIST; *section; section++) { | |
| const int id = (*section)->id; | |
| if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped) | |
| && !section_requires_specific_mention(id)) { | |
| incidentArgs.addSection(id); | |
| } | |
| } | |
| for (const Section* section : mRegisteredSections) { | |
| if (!section_requires_specific_mention(section->id)) { | |
| incidentArgs.addSection(section->id); | |
| } | |
| } | |
| // The ReportRequest takes ownership of the fd, so we need to dup it. | |
| int fd = dup(stream.get()); | |
| if (fd < 0) { | |
| return Status::fromStatusT(-errno); | |
| } | |
| mHandler->scheduleStreamingReport(incidentArgs, listener, fd); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::registerSection(const int id, const String16& name16, | |
| const sp<IIncidentDumpCallback>& callback) { | |
| const String8 name = String8(name16); | |
| const uid_t callingUid = IPCThreadState::self()->getCallingUid(); | |
| ALOGI("Uid %d registers section %d '%s'", callingUid, id, name.c_str()); | |
| if (callback == nullptr) { | |
| return Status::fromExceptionCode(Status::EX_NULL_POINTER); | |
| } | |
| for (int i = 0; i < mRegisteredSections.size(); i++) { | |
| if (mRegisteredSections.at(i)->id == id) { | |
| if (mRegisteredSections.at(i)->uid != callingUid) { | |
| ALOGW("Error registering section %d: calling uid does not match", id); | |
| return Status::fromExceptionCode(Status::EX_SECURITY); | |
| } | |
| mRegisteredSections.at(i) = new BringYourOwnSection(id, name.c_str(), callingUid, callback); | |
| return Status::ok(); | |
| } | |
| } | |
| mRegisteredSections.push_back(new BringYourOwnSection(id, name.c_str(), callingUid, callback)); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::unregisterSection(const int id) { | |
| uid_t callingUid = IPCThreadState::self()->getCallingUid(); | |
| ALOGI("Uid %d unregisters section %d", callingUid, id); | |
| for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) { | |
| if ((*it)->id == id) { | |
| if ((*it)->uid != callingUid) { | |
| ALOGW("Error unregistering section %d: calling uid does not match", id); | |
| return Status::fromExceptionCode(Status::EX_SECURITY); | |
| } | |
| mRegisteredSections.erase(it); | |
| return Status::ok(); | |
| } | |
| } | |
| ALOGW("Section %d not found", id); | |
| return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); | |
| } | |
| Status IncidentService::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. | |
| mBroadcaster->reset(); | |
| mHandler->scheduleSendBacklog(); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16, | |
| vector<String16>* result) { | |
| status_t err; | |
| const string pkg(String8(pkg16).string()); | |
| const string cls(String8(cls16).string()); | |
| // List the reports | |
| vector<sp<ReportFile>> all; | |
| err = mWorkDirectory->getReports(&all, 0); | |
| if (err != NO_ERROR) { | |
| return Status::fromStatusT(err); | |
| } | |
| // Find the ones that match pkg and cls. | |
| for (sp<ReportFile>& file: all) { | |
| err = file->loadEnvelope(); | |
| if (err != NO_ERROR) { | |
| continue; | |
| } | |
| const ReportFileProto& envelope = file->getEnvelope(); | |
| size_t reportCount = envelope.report_size(); | |
| for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) { | |
| const ReportFileProto_Report& report = envelope.report(reportIndex); | |
| if (pkg == report.pkg() && cls == report.cls()) { | |
| result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str())); | |
| break; | |
| } | |
| } | |
| } | |
| return Status::ok(); | |
| } | |
| Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16, | |
| const String16& id16, IncidentManager::IncidentReport* result) { | |
| status_t err; | |
| const string pkg(String8(pkg16).string()); | |
| const string cls(String8(cls16).string()); | |
| const string id(String8(id16).string()); | |
| IncidentReportArgs args; | |
| sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args); | |
| if (file != nullptr) { | |
| // Create pipe | |
| int fds[2]; | |
| if (pipe(fds) != 0) { | |
| ALOGW("Error opening pipe to filter incident report: %s", | |
| file->getDataFileName().c_str()); | |
| return Status::ok(); | |
| } | |
| result->setTimestampNs(file->getTimestampNs()); | |
| result->setPrivacyPolicy(file->getEnvelope().privacy_policy()); | |
| result->takeFileDescriptor(fds[0]); | |
| int writeFd = fds[1]; | |
| // spawn a thread to write the data. Release the writeFd ownership to the thread. | |
| thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); }); | |
| th.detach(); | |
| } | |
| return Status::ok(); | |
| } | |
| Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16, | |
| const String16& id16) { | |
| const string pkg(String8(pkg16).string()); | |
| const string cls(String8(cls16).string()); | |
| const string id(String8(id16).string()); | |
| sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr); | |
| if (file != nullptr) { | |
| mWorkDirectory->commit(file, pkg, cls); | |
| } | |
| mBroadcaster->clearBroadcasts(pkg, cls, id); | |
| return Status::ok(); | |
| } | |
| Status IncidentService::deleteAllIncidentReports(const String16& pkg16) { | |
| const string pkg(String8(pkg16).string()); | |
| mWorkDirectory->commitAll(pkg); | |
| mBroadcaster->clearPackageBroadcasts(pkg); | |
| return Status::ok(); | |
| } | |
| /** | |
| * Implement our own because the default binder implementation isn't | |
| * properly handling SHELL_COMMAND_TRANSACTION. | |
| */ | |
| status_t IncidentService::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; | |
| } break; | |
| default: { return BnIncidentManager::onTransact(code, data, reply, flags); } | |
| } | |
| } | |
| status_t IncidentService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args) { | |
| const int argCount = args.size(); | |
| if (argCount >= 1) { | |
| if (!args[0].compare(String8("privacy"))) { | |
| return cmd_privacy(in, out, err, args); | |
| } | |
| if (!args[0].compare(String8("throttler"))) { | |
| mThrottler->dump(out); | |
| return NO_ERROR; | |
| } | |
| if (!args[0].compare(String8("section"))) { | |
| if (argCount == 1) { | |
| fprintf(out, "Not enough arguments for section\n"); | |
| return NO_ERROR; | |
| } | |
| int id = atoi(args[1]); | |
| int idx = 0; | |
| while (SECTION_LIST[idx] != NULL) { | |
| const Section* section = SECTION_LIST[idx]; | |
| if (section->id == id) { | |
| fprintf(out, "Section[%d] %s\n", id, section->name.string()); | |
| break; | |
| } | |
| idx++; | |
| } | |
| return NO_ERROR; | |
| } | |
| } | |
| return cmd_help(out); | |
| } | |
| status_t IncidentService::cmd_help(FILE* out) { | |
| fprintf(out, "usage: adb shell cmd incident privacy print <section_id>\n"); | |
| fprintf(out, "usage: adb shell cmd incident privacy parse <section_id> < proto.txt\n"); | |
| fprintf(out, " Prints/parses for the section id.\n\n"); | |
| fprintf(out, "usage: adb shell cmd incident section <section_id>\n"); | |
| fprintf(out, " Prints section id and its name.\n\n"); | |
| fprintf(out, "usage: adb shell cmd incident throttler\n"); | |
| fprintf(out, " Prints the current throttler state\n"); | |
| return NO_ERROR; | |
| } | |
| static void printPrivacy(const Privacy* p, FILE* out, String8 indent) { | |
| if (p == NULL) return; | |
| fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy); | |
| if (p->children == NULL) return; | |
| for (int i = 0; p->children[i] != NULL; i++) { // NULL-terminated. | |
| printPrivacy(p->children[i], out, indent + " "); | |
| } | |
| } | |
| status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) { | |
| (void)in; | |
| const int argCount = args.size(); | |
| if (argCount >= 3) { | |
| String8 opt = args[1]; | |
| int sectionId = atoi(args[2].string()); | |
| const Privacy* p = get_privacy_of_section(sectionId); | |
| if (p == NULL) { | |
| fprintf(err, "Can't find section id %d\n", sectionId); | |
| return NO_ERROR; | |
| } | |
| fprintf(err, "Get privacy for %d\n", sectionId); | |
| if (opt == "print") { | |
| printPrivacy(p, out, String8("")); | |
| } else if (opt == "parse") { | |
| /* | |
| FdBuffer buf; | |
| status_t error = buf.read(fileno(in), 60000); | |
| if (error != NO_ERROR) { | |
| fprintf(err, "Error reading from stdin\n"); | |
| return error; | |
| } | |
| fprintf(err, "Read %zu bytes\n", buf.size()); | |
| PrivacyFilter pBuf(p, buf.data()); | |
| PrivacySpec spec = PrivacySpec::new_spec(argCount > 3 ? atoi(args[3]) : -1); | |
| error = pBuf.strip(spec); | |
| if (error != NO_ERROR) { | |
| fprintf(err, "Error strip pii fields with spec %d\n", spec.policy); | |
| return error; | |
| } | |
| return pBuf.flush(fileno(out)); | |
| */ | |
| return -1; | |
| } | |
| } else { | |
| return cmd_help(out); | |
| } | |
| return NO_ERROR; | |
| } | |
| } // namespace incidentd | |
| } // namespace os | |
| } // namespace android |