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 17 #define LOG_TAG "incidentd" 18 19 #include "Reporter.h" 20 #include "protobuf.h" 21 22 #include "report_directory.h" 23 #include "section_list.h" 24 25 #include <private/android_filesystem_config.h> 26 #include <android/os/DropBoxManager.h> 27 #include <utils/SystemClock.h> 28 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <dirent.h> 32 #include <fcntl.h> 33 #include <errno.h> 34 35 /** 36 * The directory where the incident reports are stored. 37 */ 38 static const String8 INCIDENT_DIRECTORY("/data/incidents"); 39 40 static status_t 41 write_all(int fd, uint8_t const* buf, size_t size) 42 { 43 while (size > 0) { 44 ssize_t amt = ::write(fd, buf, size); 45 if (amt < 0) { 46 return -errno; 47 } 48 size -= amt; 49 buf += amt; 50 } 51 return NO_ERROR; 52 } 53 54 // ================================================================================ 55 ReportRequest::ReportRequest(const IncidentReportArgs& a, 56 const sp<IIncidentReportStatusListener> &l, int f) 57 :args(a), 58 listener(l), 59 fd(f), 60 err(NO_ERROR) 61 { 62 } 63 64 ReportRequest::~ReportRequest() 65 { 66 } 67 68 // ================================================================================ 69 ReportRequestSet::ReportRequestSet() 70 :mRequests(), 71 mWritableCount(0), 72 mMainFd(-1) 73 { 74 } 75 76 ReportRequestSet::~ReportRequestSet() 77 { 78 } 79 80 void 81 ReportRequestSet::add(const sp<ReportRequest>& request) 82 { 83 mRequests.push_back(request); 84 mWritableCount++; 85 } 86 87 void 88 ReportRequestSet::setMainFd(int fd) 89 { 90 mMainFd = fd; 91 mWritableCount++; 92 } 93 94 status_t 95 ReportRequestSet::write(uint8_t const* buf, size_t size) 96 { 97 status_t err = EBADF; 98 99 // The streaming ones 100 int const N = mRequests.size(); 101 for (int i=N-1; i>=0; i--) { 102 sp<ReportRequest> request = mRequests[i]; 103 if (request->fd >= 0 && request->err == NO_ERROR) { 104 err = write_all(request->fd, buf, size); 105 if (err != NO_ERROR) { 106 request->err = err; 107 mWritableCount--; 108 } 109 } 110 } 111 112 // The dropbox file 113 if (mMainFd >= 0) { 114 err = write_all(mMainFd, buf, size); 115 if (err != NO_ERROR) { 116 mMainFd = -1; 117 mWritableCount--; 118 } 119 } 120 121 // Return an error only when there are no FDs to write. 122 return mWritableCount > 0 ? NO_ERROR : err; 123 } 124 125 126 // ================================================================================ 127 Reporter::Reporter() 128 :args(), 129 batch() 130 { 131 char buf[100]; 132 133 // TODO: Make the max size smaller for user builds. 134 mMaxSize = 100 * 1024 * 1024; 135 mMaxCount = 100; 136 137 // There can't be two at the same time because it's on one thread. 138 mStartTime = time(NULL); 139 strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); 140 mFilename = INCIDENT_DIRECTORY + buf; 141 } 142 143 Reporter::~Reporter() 144 { 145 } 146 147 Reporter::run_report_status_t 148 Reporter::runReport() 149 { 150 151 status_t err = NO_ERROR; 152 bool needMainFd = false; 153 int mainFd = -1; 154 155 // See if we need the main file 156 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 157 if ((*it)->fd < 0 && mainFd < 0) { 158 needMainFd = true; 159 break; 160 } 161 } 162 if (needMainFd) { 163 // Create the directory 164 err = create_directory(INCIDENT_DIRECTORY); 165 if (err != NO_ERROR) { 166 goto done; 167 } 168 169 // If there are too many files in the directory (for whatever reason), 170 // delete the oldest ones until it's under the limit. Doing this first 171 // does mean that we can go over, so the max size is not a hard limit. 172 clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); 173 174 // Open the file. 175 err = create_file(&mainFd); 176 if (err != NO_ERROR) { 177 goto done; 178 } 179 180 // Add to the set 181 batch.setMainFd(mainFd); 182 } 183 184 // Tell everyone that we're starting. 185 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 186 if ((*it)->listener != NULL) { 187 (*it)->listener->onReportStarted(); 188 } 189 } 190 191 // Write the incident headers 192 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 193 const sp<ReportRequest> request = (*it); 194 const vector<vector<int8_t>>& headers = request->args.headers(); 195 196 for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); 197 buf++) { 198 int fd = request->fd >= 0 ? request->fd : mainFd; 199 200 uint8_t buffer[20]; 201 uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, 202 buf->size()); 203 write_all(fd, buffer, p-buffer); 204 205 write_all(fd, (uint8_t const*)buf->data(), buf->size()); 206 // If there was an error now, there will be an error later and we will remove 207 // it from the list then. 208 } 209 } 210 211 // For each of the report fields, see if we need it, and if so, execute the command 212 // and report to those that care that we're doing it. 213 for (const Section** section=SECTION_LIST; *section; section++) { 214 const int id = (*section)->id; 215 ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); 216 217 if (this->args.containsSection(id)) { 218 // Notify listener of starting 219 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 220 if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { 221 (*it)->listener->onReportSectionStatus(id, 222 IIncidentReportStatusListener::STATUS_STARTING); 223 } 224 } 225 226 // Execute - go get the data and write it into the file descriptors. 227 err = (*section)->Execute(&batch); 228 if (err != NO_ERROR) { 229 ALOGW("Incident section %s (%d) failed. Stopping report.", 230 (*section)->name.string(), id); 231 goto done; 232 } 233 234 // Notify listener of starting 235 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 236 if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { 237 (*it)->listener->onReportSectionStatus(id, 238 IIncidentReportStatusListener::STATUS_FINISHED); 239 } 240 } 241 } 242 } 243 244 done: 245 // Close the file. 246 if (mainFd >= 0) { 247 close(mainFd); 248 } 249 250 // Tell everyone that we're done. 251 for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { 252 if ((*it)->listener != NULL) { 253 if (err == NO_ERROR) { 254 (*it)->listener->onReportFinished(); 255 } else { 256 (*it)->listener->onReportFailed(); 257 } 258 } 259 } 260 261 // Put the report into dropbox. 262 if (needMainFd && err == NO_ERROR) { 263 sp<DropBoxManager> dropbox = new DropBoxManager(); 264 Status status = dropbox->addFile(String16("incident"), mFilename, 0); 265 ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); 266 if (!status.isOk()) { 267 return REPORT_NEEDS_DROPBOX; 268 } 269 270 // If the status was ok, delete the file. If not, leave it around until the next 271 // boot or the next checkin. If the directory gets too big older files will 272 // be rotated out. 273 unlink(mFilename.c_str()); 274 } 275 276 return REPORT_FINISHED; 277 } 278 279 /** 280 * Create our output file and set the access permissions to -rw-rw---- 281 */ 282 status_t 283 Reporter::create_file(int* fd) 284 { 285 const char* filename = mFilename.c_str(); 286 287 *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); 288 if (*fd < 0) { 289 ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); 290 return -errno; 291 } 292 293 // Override umask. Not super critical. If it fails go on with life. 294 chmod(filename, 0660); 295 296 if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { 297 ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); 298 status_t err = -errno; 299 unlink(mFilename.c_str()); 300 return err; 301 } 302 303 return NO_ERROR; 304 } 305 306 // ================================================================================ 307 Reporter::run_report_status_t 308 Reporter::upload_backlog() 309 { 310 DIR* dir; 311 struct dirent* entry; 312 struct stat st; 313 314 if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { 315 ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); 316 return REPORT_NEEDS_DROPBOX; 317 } 318 319 String8 dirbase(INCIDENT_DIRECTORY + "/"); 320 sp<DropBoxManager> dropbox = new DropBoxManager(); 321 322 // Enumerate, count and add up size 323 while ((entry = readdir(dir)) != NULL) { 324 if (entry->d_name[0] == '.') { 325 continue; 326 } 327 String8 filename = dirbase + entry->d_name; 328 if (stat(filename.string(), &st) != 0) { 329 ALOGE("Unable to stat file %s", filename.string()); 330 continue; 331 } 332 if (!S_ISREG(st.st_mode)) { 333 continue; 334 } 335 336 Status status = dropbox->addFile(String16("incident"), filename.string(), 0); 337 ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); 338 if (!status.isOk()) { 339 return REPORT_NEEDS_DROPBOX; 340 } 341 342 // If the status was ok, delete the file. If not, leave it around until the next 343 // boot or the next checkin. If the directory gets too big older files will 344 // be rotated out. 345 unlink(filename.string()); 346 } 347 348 closedir(dir); 349 350 return REPORT_FINISHED; 351 } 352 353