Home | History | Annotate | Download | only in src
      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