blob: 1ecb291c84a1926f9403d07634a96df4f90ceac0 [file] [log] [blame]
Joe Onorato1754d742016-11-21 17:51:35 -08001/*
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 */
38static const String8 INCIDENT_DIRECTORY("/data/incidents");
39
40static status_t
41write_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// ================================================================================
55ReportRequest::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
64ReportRequest::~ReportRequest()
65{
66}
67
68// ================================================================================
69ReportRequestSet::ReportRequestSet()
70 :mRequests(),
71 mWritableCount(0),
72 mMainFd(-1)
73{
74}
75
76ReportRequestSet::~ReportRequestSet()
77{
78}
79
80void
81ReportRequestSet::add(const sp<ReportRequest>& request)
82{
83 mRequests.push_back(request);
84 mWritableCount++;
85}
86
87void
88ReportRequestSet::setMainFd(int fd)
89{
90 mMainFd = fd;
91 mWritableCount++;
92}
93
94status_t
95ReportRequestSet::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// ================================================================================
127Reporter::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
143Reporter::~Reporter()
144{
145}
146
147Reporter::run_report_status_t
148Reporter::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
244done:
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 */
282status_t
283Reporter::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// ================================================================================
307Reporter::run_report_status_t
308Reporter::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