1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16#define DEBUG false 17#include "Log.h" 18 19#include "Reporter.h" 20 21#include "Privacy.h" 22#include "report_directory.h" 23#include "section_list.h" 24 25#include <android-base/properties.h> 26#include <android/os/DropBoxManager.h> 27#include <private/android_filesystem_config.h> 28#include <utils/SystemClock.h> 29 30#include <dirent.h> 31#include <errno.h> 32#include <fcntl.h> 33#include <sys/stat.h> 34#include <sys/types.h> 35#include <string> 36 37/** 38 * The directory where the incident reports are stored. 39 */ 40static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/"; 41 42namespace android { 43namespace os { 44namespace incidentd { 45 46// ================================================================================ 47ReportRequest::ReportRequest(const IncidentReportArgs& a, 48 const sp<IIncidentReportStatusListener>& l, int f) 49 : args(a), listener(l), fd(f), err(NO_ERROR) {} 50 51ReportRequest::~ReportRequest() { 52 if (fd >= 0) { 53 // clean up the opened file descriptor 54 close(fd); 55 } 56} 57 58bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; } 59 60// ================================================================================ 61ReportRequestSet::ReportRequestSet() 62 : mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {} 63 64ReportRequestSet::~ReportRequestSet() {} 65 66// TODO: dedup on exact same args and fd, report the status back to listener! 67void ReportRequestSet::add(const sp<ReportRequest>& request) { 68 mRequests.push_back(request); 69 mSections.merge(request->args); 70 mMetadata.set_request_size(mMetadata.request_size() + 1); 71} 72 73void ReportRequestSet::setMainFd(int fd) { 74 mMainFd = fd; 75 mMetadata.set_use_dropbox(fd > 0); 76} 77 78void ReportRequestSet::setMainDest(int dest) { 79 mMainDest = dest; 80 PrivacySpec spec = PrivacySpec::new_spec(dest); 81 switch (spec.dest) { 82 case android::os::DEST_AUTOMATIC: 83 mMetadata.set_dest(IncidentMetadata_Destination_AUTOMATIC); 84 break; 85 case android::os::DEST_EXPLICIT: 86 mMetadata.set_dest(IncidentMetadata_Destination_EXPLICIT); 87 break; 88 case android::os::DEST_LOCAL: 89 mMetadata.set_dest(IncidentMetadata_Destination_LOCAL); 90 break; 91 } 92} 93 94bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); } 95 96IncidentMetadata::SectionStats* ReportRequestSet::sectionStats(int id) { 97 if (mSectionStats.find(id) == mSectionStats.end()) { 98 IncidentMetadata::SectionStats stats; 99 stats.set_id(id); 100 mSectionStats[id] = stats; 101 } 102 return &mSectionStats[id]; 103} 104 105// ================================================================================ 106Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; }; 107 108Reporter::Reporter(const char* directory) : batch() { 109 char buf[100]; 110 111 mMaxSize = 30 * 1024 * 1024; // incident reports can take up to 30MB on disk 112 mMaxCount = 100; 113 114 // string ends up with '/' is a directory 115 String8 dir = String8(directory); 116 if (directory[dir.size() - 1] != '/') dir += "/"; 117 mIncidentDirectory = dir.string(); 118 119 // There can't be two at the same time because it's on one thread. 120 mStartTime = time(NULL); 121 strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); 122 mFilename = mIncidentDirectory + buf; 123} 124 125Reporter::~Reporter() {} 126 127Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) { 128 status_t err = NO_ERROR; 129 bool needMainFd = false; 130 int mainFd = -1; 131 int mainDest = -1; 132 HeaderSection headers; 133 MetadataSection metadataSection; 134 std::string buildType = android::base::GetProperty("ro.build.type", ""); 135 const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; 136 137 // See if we need the main file 138 for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { 139 if ((*it)->fd < 0 && mainFd < 0) { 140 needMainFd = true; 141 mainDest = (*it)->args.dest(); 142 break; 143 } 144 } 145 if (needMainFd) { 146 // Create the directory 147 if (!isTest) err = create_directory(mIncidentDirectory); 148 if (err != NO_ERROR) { 149 goto DONE; 150 } 151 152 // If there are too many files in the directory (for whatever reason), 153 // delete the oldest ones until it's under the limit. Doing this first 154 // does mean that we can go over, so the max size is not a hard limit. 155 if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount); 156 157 // Open the file. 158 err = create_file(&mainFd); 159 if (err != NO_ERROR) { 160 goto DONE; 161 } 162 163 // Add to the set 164 batch.setMainFd(mainFd); 165 batch.setMainDest(mainDest); 166 } 167 168 // Tell everyone that we're starting. 169 for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { 170 if ((*it)->listener != NULL) { 171 (*it)->listener->onReportStarted(); 172 } 173 } 174 175 // Write the incident headers 176 headers.Execute(&batch); 177 178 // For each of the report fields, see if we need it, and if so, execute the command 179 // and report to those that care that we're doing it. 180 for (const Section** section = SECTION_LIST; *section; section++) { 181 const int id = (*section)->id; 182 if ((*section)->userdebugAndEngOnly && !isUserdebugOrEng) { 183 ALOGD("Skipping incident report section %d '%s' because it's limited to userdebug/eng", 184 id, (*section)->name.string()); 185 continue; 186 } 187 if (this->batch.containsSection(id)) { 188 ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); 189 for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { 190 if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { 191 (*it)->listener->onReportSectionStatus( 192 id, IIncidentReportStatusListener::STATUS_STARTING); 193 } 194 } 195 196 // Execute - go get the data and write it into the file descriptors. 197 IncidentMetadata::SectionStats* stats = batch.sectionStats(id); 198 int64_t startTime = uptimeMillis(); 199 err = (*section)->Execute(&batch); 200 int64_t endTime = uptimeMillis(); 201 stats->set_success(err == NO_ERROR); 202 stats->set_exec_duration_ms(endTime - startTime); 203 if (err != NO_ERROR) { 204 ALOGW("Incident section %s (%d) failed: %s. Stopping report.", 205 (*section)->name.string(), id, strerror(-err)); 206 goto DONE; 207 } 208 (*reportByteSize) += stats->report_size_bytes(); 209 210 // Notify listener of starting 211 for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { 212 if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { 213 (*it)->listener->onReportSectionStatus( 214 id, IIncidentReportStatusListener::STATUS_FINISHED); 215 } 216 } 217 ALOGD("Finish incident report section %d '%s'", id, (*section)->name.string()); 218 } 219 } 220 221DONE: 222 // Reports the metdadata when taking the incident report. 223 if (!isTest) metadataSection.Execute(&batch); 224 225 // Close the file. 226 if (mainFd >= 0) { 227 close(mainFd); 228 } 229 230 // Tell everyone that we're done. 231 for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { 232 if ((*it)->listener != NULL) { 233 if (err == NO_ERROR) { 234 (*it)->listener->onReportFinished(); 235 } else { 236 (*it)->listener->onReportFailed(); 237 } 238 } 239 } 240 241 // Put the report into dropbox. 242 if (needMainFd && err == NO_ERROR) { 243 sp<DropBoxManager> dropbox = new DropBoxManager(); 244 Status status = dropbox->addFile(String16("incident"), mFilename, 0); 245 ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); 246 if (!status.isOk()) { 247 return REPORT_NEEDS_DROPBOX; 248 } 249 250 // If the status was ok, delete the file. If not, leave it around until the next 251 // boot or the next checkin. If the directory gets too big older files will 252 // be rotated out. 253 if (!isTest) unlink(mFilename.c_str()); 254 } 255 256 return REPORT_FINISHED; 257} 258 259/** 260 * Create our output file and set the access permissions to -rw-rw---- 261 */ 262status_t Reporter::create_file(int* fd) { 263 const char* filename = mFilename.c_str(); 264 265 *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); 266 if (*fd < 0) { 267 ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); 268 return -errno; 269 } 270 271 // Override umask. Not super critical. If it fails go on with life. 272 chmod(filename, 0660); 273 274 if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) { 275 ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); 276 status_t err = -errno; 277 unlink(mFilename.c_str()); 278 return err; 279 } 280 281 return NO_ERROR; 282} 283 284Reporter::run_report_status_t Reporter::upload_backlog() { 285 DIR* dir; 286 struct dirent* entry; 287 struct stat st; 288 status_t err; 289 290 ALOGD("Start uploading backlogs in %s", INCIDENT_DIRECTORY); 291 if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) { 292 ALOGE("directory doesn't exist: %s", strerror(-err)); 293 return REPORT_FINISHED; 294 } 295 296 if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) { 297 ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY); 298 return REPORT_NEEDS_DROPBOX; 299 } 300 301 sp<DropBoxManager> dropbox = new DropBoxManager(); 302 303 // Enumerate, count and add up size 304 int count = 0; 305 while ((entry = readdir(dir)) != NULL) { 306 if (entry->d_name[0] == '.') { 307 continue; 308 } 309 String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name; 310 if (stat(filename.string(), &st) != 0) { 311 ALOGE("Unable to stat file %s", filename.string()); 312 continue; 313 } 314 if (!S_ISREG(st.st_mode)) { 315 continue; 316 } 317 318 Status status = dropbox->addFile(String16("incident"), filename.string(), 0); 319 ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); 320 if (!status.isOk()) { 321 return REPORT_NEEDS_DROPBOX; 322 } 323 324 // If the status was ok, delete the file. If not, leave it around until the next 325 // boot or the next checkin. If the directory gets too big older files will 326 // be rotated out. 327 unlink(filename.string()); 328 count++; 329 } 330 ALOGD("Successfully uploaded %d files to Dropbox.", count); 331 closedir(dir); 332 333 return REPORT_FINISHED; 334} 335 336} // namespace incidentd 337} // namespace os 338} // namespace android 339