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  */
40 static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
41 
42 namespace android {
43 namespace os {
44 namespace incidentd {
45 
46 // ================================================================================
ReportRequest(const IncidentReportArgs & a,const sp<IIncidentReportStatusListener> & l,int f)47 ReportRequest::ReportRequest(const IncidentReportArgs& a,
48                              const sp<IIncidentReportStatusListener>& l, int f)
49     : args(a), listener(l), fd(f), err(NO_ERROR) {}
50 
~ReportRequest()51 ReportRequest::~ReportRequest() {
52     if (fd >= 0) {
53         // clean up the opened file descriptor
54         close(fd);
55     }
56 }
57 
ok()58 bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; }
59 
60 // ================================================================================
ReportRequestSet()61 ReportRequestSet::ReportRequestSet()
62     : mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {}
63 
~ReportRequestSet()64 ReportRequestSet::~ReportRequestSet() {}
65 
66 // TODO: dedup on exact same args and fd, report the status back to listener!
add(const sp<ReportRequest> & request)67 void 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 
setMainFd(int fd)73 void ReportRequestSet::setMainFd(int fd) {
74     mMainFd = fd;
75     mMetadata.set_use_dropbox(fd > 0);
76 }
77 
setMainDest(int dest)78 void 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 
containsSection(int id)94 bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); }
95 
sectionStats(int id)96 IncidentMetadata::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 // ================================================================================
Reporter()106 Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
107 
Reporter(const char * directory)108 Reporter::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 
~Reporter()125 Reporter::~Reporter() {}
126 
runReport(size_t * reportByteSize)127 Reporter::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 
221 DONE:
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  */
create_file(int * fd)262 status_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 
upload_backlog()284 Reporter::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