• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
write_all(int fd,uint8_t const * buf,size_t size)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 // ================================================================================
ReportRequest(const IncidentReportArgs & a,const sp<IIncidentReportStatusListener> & l,int f)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 
~ReportRequest()64 ReportRequest::~ReportRequest()
65 {
66 }
67 
68 // ================================================================================
ReportRequestSet()69 ReportRequestSet::ReportRequestSet()
70     :mRequests(),
71      mWritableCount(0),
72      mMainFd(-1)
73 {
74 }
75 
~ReportRequestSet()76 ReportRequestSet::~ReportRequestSet()
77 {
78 }
79 
80 void
add(const sp<ReportRequest> & request)81 ReportRequestSet::add(const sp<ReportRequest>& request)
82 {
83     mRequests.push_back(request);
84     mWritableCount++;
85 }
86 
87 void
setMainFd(int fd)88 ReportRequestSet::setMainFd(int fd)
89 {
90     mMainFd = fd;
91     mWritableCount++;
92 }
93 
94 status_t
write(uint8_t const * buf,size_t size)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 // ================================================================================
Reporter()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 
~Reporter()143 Reporter::~Reporter()
144 {
145 }
146 
147 Reporter::run_report_status_t
runReport()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
create_file(int * fd)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
upload_backlog()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