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