// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.crash;
import android.content.Context;
import android.content.Intent;
import org.chromium.base.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Callable helper for {@link MinidumpPreparationService}.
*
* This class will append a logcat file to a minidump file for upload.
*/
public class MinidumpPreparationCallable implements Callable<Boolean> {
private static final String TAG = "DumpPrepCallable";
private static final String LOGCAT_CONTENT_DISPOSITION =
"Content-Disposition: form-data; name=\"logcat\"; filename=\"logcat\"";
private static final String LOGCAT_CONTENT_TYPE =
"Content-Type: text/plain";
private final File mLogcatFile;
private final File mMinidumpFile;
private final Intent mRedirectIntent;
private final Context mContext;
private final CrashFileManager mFileManager;
public MinidumpPreparationCallable(
Context context,
File miniDumpFile,
File logcatFile,
Intent redirectIntent) {
mContext = context;
mLogcatFile = logcatFile;
mMinidumpFile = miniDumpFile;
mRedirectIntent = redirectIntent;
mFileManager = new CrashFileManager(context.getCacheDir());
}
/**
* Read the boundary from the first lien of the file.
*/
private static String getBoundary(File processMinidumpFile) throws IOException {
BufferedReader bReader = null;
try {
bReader = new BufferedReader(new FileReader(processMinidumpFile));
return bReader.readLine();
} finally {
if (bReader != null) {
bReader.close();
}
}
}
/**
* Write the invoking {@link MinidumpPreparationCallable}s logcat data to
* the specified target {@link File}.
*
* Target file is overwritten, not appended to the end.
*
* @param targetFile File to which logcat data should be written.
* @param boundary String MIME boundary to prepend.
* @throws IOException if something goes wrong.
*/
private static void writeLogcat(File targetFile, List<String> logcat, String boundary)
throws IOException {
BufferedWriter bWriter = null;
try {
bWriter = new BufferedWriter(new FileWriter(targetFile, false));
bWriter.write(boundary);
bWriter.newLine();
// Next we write the logcat data in a MIME block.
bWriter.write(LOGCAT_CONTENT_DISPOSITION);
bWriter.newLine();
bWriter.write(LOGCAT_CONTENT_TYPE);
bWriter.newLine();
bWriter.newLine();
// Emits the contents of the buffer into the output file.
for (String ln : logcat) {
bWriter.write(ln);
bWriter.newLine();
}
} finally {
if (bWriter != null) {
bWriter.close();
}
}
}
/**
* Append the minidump file data to the specified target {@link File}.
*
* @param processMinidumpFile File containing data to append.
* @param targetFile File to which data should be appended.
* @throws IOException when standard IO errors occur.
*/
private static void appendMinidump(
File processMinidumpFile, File targetFile) throws IOException {
BufferedInputStream bIn = null;
BufferedOutputStream bOut = null;
try {
byte[] buf = new byte[256];
bIn = new BufferedInputStream(new FileInputStream(processMinidumpFile));
bOut = new BufferedOutputStream(new FileOutputStream(targetFile, true));
int count;
while ((count = bIn.read(buf)) != -1) {
bOut.write(buf, 0, count);
}
} finally {
if (bIn != null) bIn.close();
if (bOut != null) bOut.close();
}
}
private boolean augmentTargetFile(List<String> logcat) {
File targetFile = null;
try {
targetFile = mFileManager.createNewTempFile(mMinidumpFile.getName() + ".try0");
String boundary = getBoundary(mMinidumpFile);
if (boundary == null) {
return false;
}
writeLogcat(targetFile, logcat, boundary);
// Finally Reopen and append the original minidump MIME sections
// including the leading boundary.
appendMinidump(mMinidumpFile, targetFile);
if (!mMinidumpFile.delete()) {
Log.w(TAG, "Fail to delete minidump file: " + mMinidumpFile.getName());
}
return true;
} catch (IOException e) {
String msg = String.format(
"Error while tyring to annotate minidump file %s with logcat data",
mMinidumpFile.getAbsoluteFile());
Log.w(TAG, msg, e);
if (targetFile != null) {
CrashFileManager.deleteFile(targetFile);
}
return false;
}
}
private List<String> getLogcatAsList() throws IOException {
BufferedReader r = null;
try {
List<String> logcat = new LinkedList<String>();
if (mLogcatFile != null) {
r = new BufferedReader(new FileReader(mLogcatFile));
String ln;
while ((ln = r.readLine()) != null) {
logcat.add(ln);
}
}
return Collections.unmodifiableList(logcat);
} finally {
if (r != null) {
r.close();
}
}
}
@Override
public Boolean call() throws IOException {
// By default set the basic minidump to be uploaded. That way, even if
// there are errors augmenting the minidump with logcat data, the service
// can still upload the unaugmented minidump.
List<String> logcat = getLogcatAsList();
boolean success = true;
if (!logcat.isEmpty()) {
success = augmentTargetFile(logcat);
if (success && !mLogcatFile.delete()) {
Log.w(TAG, "Failed to delete logcat file: " + mLogcatFile.getName());
}
}
if (mRedirectIntent != null) {
mContext.startService(mRedirectIntent);
}
return success;
}
}