Common files for reporting and symbol server
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@5 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
cb91a2f879
commit
68b748fc58
47
java/common/build.xml
Normal file
47
java/common/build.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<project name="AirbagShared" default="compile" basedir=".">
|
||||
<property name="src.home" value="${basedir}/src"/>
|
||||
<property name="lib.home" value="${basedir}/../third-party"/>
|
||||
<property name="dist.home" value="${basedir}/dist"/>
|
||||
<property name="app.name" value="airbag-common"/>
|
||||
<property name="doc.home" value="${basedir}/docs"/>
|
||||
<property name="build.home" value="${basedir}/build"/>
|
||||
|
||||
<property name="compile.debug" value="true"/>
|
||||
<property name="compile.deprecation" value="true"/>
|
||||
<property name="compile.optimize" value="false"/>
|
||||
|
||||
<path id="compile.classpath">
|
||||
<pathelement location="${lib.home}/servlet-api.jar"/>
|
||||
</path>
|
||||
|
||||
<target name="compile"
|
||||
description="Compile Java sources">
|
||||
<mkdir dir="${build.home}"/>
|
||||
<javac srcdir="${src.home}"
|
||||
destdir="${build.home}"
|
||||
debug="${compile.debug}"
|
||||
deprecation="${compile.deprecation}"
|
||||
optimize="${compile.optimize}">
|
||||
<classpath refid="compile.classpath"/>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="javadoc" depends="compile"
|
||||
description="Create Javadoc API documentation">
|
||||
<mkdir dir="${dist.home}/docs"/>
|
||||
<javadoc sourcepath="${src.home}"
|
||||
destdir="${dist.home}/docs"
|
||||
packagenames="*">
|
||||
<classpath refid="compile.classpath"/>
|
||||
</javadoc>
|
||||
</target>
|
||||
|
||||
<target name="dist" depends="compile,javadoc"
|
||||
description="Create binary distribution">
|
||||
<mkdir dir="${dist.home}/docs"/>
|
||||
|
||||
<!-- Create JAR file -->
|
||||
<jar jarfile="${dist.home}/${app.name}.jar"
|
||||
basedir="${build.home}"/>
|
||||
</target>
|
||||
</project>
|
@ -0,0 +1,135 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Random;
|
||||
import java.util.SortedMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Common part of uploading a file with parameters. A subclass needs to
|
||||
* provide a ReportStorage implementation.
|
||||
*
|
||||
* An upload file is saved in the file storage with parsed parameters from
|
||||
* the URL.
|
||||
*
|
||||
* A separate processor will access the file storage and process these
|
||||
* reports.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AbstractMinidumpServlet extends HttpServlet {
|
||||
// Minidump storage to be instantiated by subclasses in init() method.
|
||||
protected ReportStorage minidumpStorage;
|
||||
|
||||
// Random number generator
|
||||
private final Random random = new Random();
|
||||
|
||||
/**
|
||||
* Always return success to a GET, to keep out nosy people who go
|
||||
* directly to the URL.
|
||||
*/
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse res) {
|
||||
res.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the file POSTed to the server and writes it to a file storage.
|
||||
* Parameters in URL are saved as attributes of the file.
|
||||
*
|
||||
* @param req a wrapped HttpServletRequest that represents a multipart
|
||||
* request
|
||||
* @return unique ID for this report, can be used to get parameters and
|
||||
* uploaded file contents from a file storage. If these is a
|
||||
* collation, returns null.
|
||||
*
|
||||
* @throws ServletException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected String saveFile(MultipartRequest req)
|
||||
throws ServletException, IOException {
|
||||
// parse mutilpart request
|
||||
SortedMap<String, String> params = req.getParameters();
|
||||
|
||||
//TODO(fqian): verify required fields of a report
|
||||
InputStream inputs = req.getInputStream();
|
||||
|
||||
/* It is possible that two or more clients report crashes at the same
|
||||
* time with same parameters. To reduce the chance of collation, we
|
||||
* add two internal parameters:
|
||||
* 1. reporting time, a time string in the form of YYMMDD-HHMMSS;
|
||||
* 2. a random number;
|
||||
*
|
||||
* In theory, there is still a chance to collate, but it is very low.
|
||||
* When collation happens, the one coming later is dropped.
|
||||
*/
|
||||
// 1. add a timestamp to parameters
|
||||
params.put(NameConstants.REPORTTIME_PNAME, currentTimeString());
|
||||
|
||||
// 2. get a random number to make the change of collation very small
|
||||
int r;
|
||||
synchronized (this.random) {
|
||||
r = this.random.nextInt();
|
||||
}
|
||||
params.put(NameConstants.RANDOMNUM_PNAME, Integer.toString(r));
|
||||
|
||||
String fid = this.minidumpStorage.getUniqueId(params);
|
||||
|
||||
assert fid != null;
|
||||
|
||||
if (this.minidumpStorage.reportExists(fid)) {
|
||||
// collation happens
|
||||
return null;
|
||||
}
|
||||
|
||||
this.minidumpStorage.saveAttributes(fid, params);
|
||||
// save uploaded contents to the storage
|
||||
this.minidumpStorage.writeStreamToReport(fid, inputs, 0);
|
||||
|
||||
return fid;
|
||||
}
|
||||
|
||||
/* Gets a string representing the current time using the format:
|
||||
* YYMMDD-HHMMSS.
|
||||
*/
|
||||
private String currentTimeString() {
|
||||
NumberFormat formatter = NumberFormat.getInstance();
|
||||
formatter.setGroupingUsed(false);
|
||||
formatter.setMinimumIntegerDigits(2); // 2 digits per date component
|
||||
|
||||
// All servers are in Pacific time
|
||||
Calendar cal = Calendar.getInstance();
|
||||
|
||||
StringBuffer tstring = new StringBuffer();
|
||||
tstring.append(formatter.format(cal.get(Calendar.YEAR)));
|
||||
// We want January = 1.
|
||||
tstring.append(formatter.format((cal.get(Calendar.MONTH) + 1)));
|
||||
tstring.append(formatter.format(cal.get(Calendar.DAY_OF_MONTH)));
|
||||
tstring.append("-");
|
||||
tstring.append(formatter.format(cal.get(Calendar.HOUR_OF_DAY)));
|
||||
tstring.append(formatter.format(cal.get(Calendar.MINUTE)));
|
||||
tstring.append(formatter.format(cal.get(Calendar.SECOND)));
|
||||
|
||||
return new String(tstring);
|
||||
}
|
||||
}
|
188
java/common/src/com/google/airbag/common/CrashUtils.java
Normal file
188
java/common/src/com/google/airbag/common/CrashUtils.java
Normal file
@ -0,0 +1,188 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** A utility class used by crash reporting server. */
|
||||
|
||||
public class CrashUtils {
|
||||
// A map from numbers to hexadecimal characters.
|
||||
private static final char[] maps = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F',
|
||||
};
|
||||
|
||||
private static final String MD_ALGORITHM = "MD5";
|
||||
|
||||
// delimiter between record
|
||||
private static final String RECORD_DELIMITER = ";";
|
||||
private static final String FIELD_DELIMITER = " ";
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* Given a byte array, returns a string of its hex representation.
|
||||
* Each byte is represented by two characters for its high and low
|
||||
* parts. For example, if the input is [0x3F, 0x01], this method
|
||||
* returns 3F01.
|
||||
*
|
||||
* @param bs a byte array
|
||||
* @return a string of hexadecimal characters
|
||||
*/
|
||||
public static String bytesToHexString(byte[] bs) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (byte b : bs) {
|
||||
int high = (b >> 4) & 0x0F;
|
||||
int low = b & 0x0F;
|
||||
sb.append(maps[high]);
|
||||
sb.append(maps[low]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a byte array, computes its message digest using one-way hash
|
||||
* functions.
|
||||
*
|
||||
* @param data a byte array
|
||||
* @return a string as its signature, or null if no message digest
|
||||
* algorithm
|
||||
* supported by the system.
|
||||
*/
|
||||
public static String dataSignature(byte[] data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD_ALGORITHM);
|
||||
return bytesToHexString(md.digest(data));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the signature of a file by calling dataSignature on file
|
||||
* contents.
|
||||
*
|
||||
* @param file a file name which signature to be computed
|
||||
* @return the method signature of the file, or null if failed to
|
||||
* read file contents, or message digest algorithm is not supported
|
||||
*/
|
||||
public static String fileSignature(File file) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] buf = new byte[BUFFER_SIZE];
|
||||
MessageDigest md = MessageDigest.getInstance(MD_ALGORITHM);
|
||||
while (true) {
|
||||
int bytesRead = fis.read(buf, 0, BUFFER_SIZE);
|
||||
if (bytesRead == -1)
|
||||
break;
|
||||
md.update(buf, 0, bytesRead);
|
||||
}
|
||||
return bytesToHexString(md.digest());
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an attribute map to a string. Encoded string format:
|
||||
* name value1[ value2];name value1[ value2]
|
||||
* Names and values should be escaped so that there are no
|
||||
* RECORD_DELIMITER and VALUE_DELIMITER in strings.
|
||||
*
|
||||
* @param attributes a maps of attributes name and value to be encoded
|
||||
* @return a string of encoded attributes
|
||||
*/
|
||||
public static
|
||||
String attributesToString(SortedMap<String, String> attributes) {
|
||||
StringBuffer res = new StringBuffer();
|
||||
for (Map.Entry<String, String> e : attributes.entrySet()) {
|
||||
String name = e.getKey();
|
||||
String value = e.getValue();
|
||||
|
||||
assert name.indexOf(RECORD_DELIMITER) == -1;
|
||||
assert name.indexOf(FIELD_DELIMITER) == -1;
|
||||
res.append(name).append(FIELD_DELIMITER);
|
||||
|
||||
assert value.indexOf(RECORD_DELIMITER) == -1;
|
||||
assert value.indexOf(FIELD_DELIMITER) == -1;
|
||||
res.append(value).append(RECORD_DELIMITER);
|
||||
}
|
||||
return new String(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a string to a map of attributes.
|
||||
*/
|
||||
public static SortedMap<String, String> stringToAttributes(String s) {
|
||||
SortedMap<String, String> map =
|
||||
new TreeMap<String, String>();
|
||||
String[] records = s.split(RECORD_DELIMITER);
|
||||
for (String r : records) {
|
||||
String[] fields = r.trim().split(FIELD_DELIMITER);
|
||||
if (fields.length != 2) // discard records that has no values
|
||||
continue;
|
||||
String name = fields[0].trim();
|
||||
String value = fields[1].trim();
|
||||
map.put(name, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies bytes from an input stream to an output stream, with max bytes.
|
||||
*
|
||||
* @param ins an input stream to read
|
||||
* @param outs an output stream to write
|
||||
* @param max the maximum number of bytes to copy. If max <= 0, copy bytes
|
||||
* until the end of input stream
|
||||
* @return the number of bytes copied
|
||||
* @throws IOException
|
||||
*/
|
||||
public static int copyStream(InputStream ins, OutputStream outs, int max)
|
||||
throws IOException {
|
||||
byte[] buf = new byte[BUFFER_SIZE];
|
||||
int bytesWritten = 0;
|
||||
|
||||
while (true) {
|
||||
int bytesToRead = BUFFER_SIZE;
|
||||
if (max > 0)
|
||||
bytesToRead = Math.min(BUFFER_SIZE, max - bytesWritten);
|
||||
|
||||
if (bytesToRead <= 0)
|
||||
break;
|
||||
|
||||
int bytesRead = ins.read(buf, 0, bytesToRead);
|
||||
if (bytesRead == -1) // end of input stream
|
||||
break;
|
||||
outs.write(buf, 0, bytesRead);
|
||||
bytesWritten += bytesRead;
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.util.SortedMap;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A common interface for different multipart HttpServletRequest
|
||||
* implementations. The interface is simple enough to be used by the
|
||||
* upload server.
|
||||
*/
|
||||
|
||||
public interface MultipartRequest {
|
||||
/**
|
||||
* Returns a sorted map of name to values of an HTTP request.
|
||||
*/
|
||||
public SortedMap<String, String> getParameters();
|
||||
|
||||
/**
|
||||
* Returns an input stream of uploading file.
|
||||
*/
|
||||
public InputStream getInputStream();
|
||||
}
|
42
java/common/src/com/google/airbag/common/NameConstants.java
Normal file
42
java/common/src/com/google/airbag/common/NameConstants.java
Normal file
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
/** A class defines URL parameter names. */
|
||||
|
||||
public class NameConstants {
|
||||
// URL parameter names
|
||||
// product name
|
||||
public static final String PRODUCT_PNAME = "prod";
|
||||
// version
|
||||
public static final String VERSION_PNAME = "ver";
|
||||
// application or module
|
||||
public static final String APPLICATION_PNAME = "app";
|
||||
// platform, e.g., win32, linux, mac
|
||||
public static final String PLATFORM_PNAME = "plat";
|
||||
// report format, e.g., dump, xml
|
||||
public static final String FORMAT_PNAME = "fmt";
|
||||
// process uptime
|
||||
public static final String PROCESSUPTIME_PNAME = "procup";
|
||||
// cumulative process uptime
|
||||
public static final String CUMULATIVEUPTIME_PNAME = "cumup";
|
||||
// time when report is created
|
||||
public static final String REPORTTIME_PNAME = "rept";
|
||||
// a random number
|
||||
public static final String RANDOMNUM_PNAME = "rand";
|
||||
// report checksum
|
||||
public static final String CHECKSUM_PNAME = "sum";
|
||||
}
|
60
java/common/src/com/google/airbag/common/ReportQueue.java
Normal file
60
java/common/src/com/google/airbag/common/ReportQueue.java
Normal file
@ -0,0 +1,60 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A queue interface for unprocessed report ids. The interface is intended
|
||||
* for inter-process usage. A report uploading server enqueues new report
|
||||
* ids, and a processor dequeues ids.
|
||||
*
|
||||
* The interface is much simpler than <b>java.util.Queue</b>. An implementation
|
||||
* should provide a persistent storage of queued ids even when a process
|
||||
* is killed.
|
||||
*/
|
||||
|
||||
public interface ReportQueue {
|
||||
/**
|
||||
* Enqueue a record id.
|
||||
*
|
||||
* @param rid
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean enqueue(String rid);
|
||||
|
||||
/**
|
||||
* Enqueue a list of ids
|
||||
*
|
||||
* @param ids
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean enqueue(LinkedList<String> ids);
|
||||
|
||||
/**
|
||||
* Checks if a queue is empty
|
||||
* @return true if the queue is empty
|
||||
*/
|
||||
public boolean empty();
|
||||
|
||||
/**
|
||||
* Removes several ids from the queue. An implementation decides how
|
||||
* many ids to be removed.
|
||||
*
|
||||
* @return a list of queue
|
||||
*/
|
||||
public LinkedList<String> dequeue();
|
||||
}
|
115
java/common/src/com/google/airbag/common/ReportQueueDirImpl.java
Normal file
115
java/common/src/com/google/airbag/common/ReportQueueDirImpl.java
Normal file
@ -0,0 +1,115 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements ReportQueue using directories. Some restrictions:
|
||||
* <ul>
|
||||
* <li>Ids must be valid file names;</li>
|
||||
* <li>Ids cannot be duplicated, a duplicated id is ignored;</li>
|
||||
* <li>No guarantees on ordering, in other words, this is not really
|
||||
* a queue (with FIFO order);</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
public class ReportQueueDirImpl implements ReportQueue {
|
||||
// maximum number of ids returned by dequque method.
|
||||
private static final int MAX_DEQUEUED_IDS = 100;
|
||||
|
||||
// the directory name for storing files
|
||||
private String queueDir;
|
||||
|
||||
/**
|
||||
* Creates an instance of ReportQueueDirImpl with a directory name.
|
||||
* @param dirname
|
||||
*/
|
||||
public ReportQueueDirImpl(String dirname)
|
||||
throws IOException
|
||||
{
|
||||
this.queueDir = dirname;
|
||||
File q = new File(dirname);
|
||||
if (!q.exists())
|
||||
q.mkdirs();
|
||||
|
||||
if (!q.isDirectory())
|
||||
throw new IOException("name "+dirname
|
||||
+" exits already, but not a directory.");
|
||||
}
|
||||
|
||||
/** Enqueue a report id. */
|
||||
public boolean enqueue(String rid) {
|
||||
//lock on the directory
|
||||
// add a file named by id
|
||||
File f = new File(this.queueDir, rid);
|
||||
try {
|
||||
return f.createNewFile();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Enqueue a list of ids. */
|
||||
public boolean enqueue(LinkedList<String> ids) {
|
||||
//lock on the directory
|
||||
// add a file named by id
|
||||
for (String rid : ids) {
|
||||
File f = new File(this.queueDir, rid);
|
||||
try {
|
||||
if (!f.createNewFile())
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Checks if the queue is empty. */
|
||||
public boolean empty() {
|
||||
File f = new File(this.queueDir);
|
||||
String[] ids = f.list();
|
||||
if (ids == null)
|
||||
return true;
|
||||
else
|
||||
return ids.length == 0;
|
||||
}
|
||||
|
||||
/** Remove ids from the queue. */
|
||||
public LinkedList<String> dequeue() {
|
||||
// lock on the directory
|
||||
LinkedList<String> rids = new LinkedList<String>();
|
||||
File d = new File(this.queueDir);
|
||||
String[] ids = d.list();
|
||||
if (ids == null)
|
||||
return rids;
|
||||
|
||||
for (int i =0; i < Math.min(ids.length, MAX_DEQUEUED_IDS); i++) {
|
||||
File f = new File(this.queueDir, ids[i]);
|
||||
f.delete();
|
||||
rids.add(ids[i]);
|
||||
}
|
||||
|
||||
return rids;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.util.LinkedList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Implementation of ReportQueue using a shared file. File accesses are
|
||||
* protected by a file lock. When dequeue, all reports in the file were
|
||||
* returned and the file is truncated to length zero.
|
||||
*/
|
||||
|
||||
public class ReportQueueFileImpl implements ReportQueue {
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(ReportQueueFileImpl.class.getName());
|
||||
private String queueFile;
|
||||
|
||||
/** Given a file name, creates an instance of ReportQueueFileImpl. */
|
||||
public ReportQueueFileImpl(String fname) {
|
||||
this.queueFile = fname;
|
||||
}
|
||||
|
||||
/** Enqueues a report id. */
|
||||
public boolean enqueue(String rid) {
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
|
||||
FileChannel fc = raf.getChannel();
|
||||
// block thread until lock is obtained
|
||||
FileLock lock = fc.lock();
|
||||
raf.seek(raf.length());
|
||||
raf.writeBytes(rid);
|
||||
raf.writeByte('\n');
|
||||
lock.release();
|
||||
fc.close();
|
||||
raf.close();
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.severe("Cannot open file "+this.queueFile+" for write.");
|
||||
} catch (IOException e) {
|
||||
logger.severe("Cannot write to file "+this.queueFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Enqueues a list of report ids. */
|
||||
public boolean enqueue(LinkedList<String> ids) {
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
|
||||
FileChannel fc = raf.getChannel();
|
||||
// block thread until lock is obtained
|
||||
FileLock lock = fc.lock();
|
||||
raf.seek(raf.length());
|
||||
for (String rid : ids) {
|
||||
raf.writeBytes(rid);
|
||||
raf.writeByte('\n');
|
||||
}
|
||||
lock.release();
|
||||
fc.close();
|
||||
raf.close();
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.severe("Cannot open file "+this.queueFile+" for write.");
|
||||
} catch (IOException e) {
|
||||
logger.severe("Cannot write to file "+this.queueFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean empty() {
|
||||
// check the length of the file
|
||||
File f = new File(this.queueFile);
|
||||
return f.length() == 0L;
|
||||
}
|
||||
|
||||
public LinkedList<String> dequeue() {
|
||||
LinkedList<String> ids = new LinkedList<String>();
|
||||
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
|
||||
FileChannel fc = raf.getChannel();
|
||||
FileLock flock = fc.lock();
|
||||
|
||||
while (true) {
|
||||
String s = raf.readLine();
|
||||
if (s == null)
|
||||
break;
|
||||
s = s.trim();
|
||||
if (s.equals(""))
|
||||
continue;
|
||||
|
||||
ids.add(s);
|
||||
}
|
||||
|
||||
fc.truncate(0L);
|
||||
|
||||
// release the lock
|
||||
flock.release();
|
||||
fc.close();
|
||||
raf.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.severe("Cannot open file "+this.queueFile+" for write.");
|
||||
} catch (IOException e) {
|
||||
logger.severe("Cannot write to file "+this.queueFile);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A test class for ReportQueue implementations, currently tests
|
||||
* ReportQueueDirImpl and ReportQueueFileImpl.
|
||||
*/
|
||||
public class ReportQueueTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ReportQueue rq = new ReportQueueFileImpl("/tmp/rqtest");
|
||||
runTest(rq);
|
||||
|
||||
try {
|
||||
rq = new ReportQueueDirImpl("/tmp/rqdir");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
runTest(rq);
|
||||
|
||||
System.out.println("OK");
|
||||
}
|
||||
|
||||
private static void runTest(ReportQueue rq) {
|
||||
rq.enqueue("hello");
|
||||
rq.enqueue("world");
|
||||
|
||||
LinkedList<String> v = rq.dequeue();
|
||||
assert v.size() == 2;
|
||||
|
||||
assert v.get(0).equals("hello");
|
||||
assert v.get(1).equals("world");
|
||||
assert rq.empty();
|
||||
|
||||
v.remove();
|
||||
|
||||
rq.enqueue(v);
|
||||
assert !rq.empty();
|
||||
|
||||
v = rq.dequeue();
|
||||
assert v.size() == 1;
|
||||
assert v.get(0).equals("world");
|
||||
}
|
||||
}
|
134
java/common/src/com/google/airbag/common/ReportStorage.java
Normal file
134
java/common/src/com/google/airbag/common/ReportStorage.java
Normal file
@ -0,0 +1,134 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import java.util.SortedMap;
|
||||
|
||||
/**
|
||||
* ReportStorage.java
|
||||
*
|
||||
* <p>
|
||||
* Provide an abstract layer for storing crash reports and associated meta
|
||||
* data.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The interface is intended to be used by a client in the following way:
|
||||
*
|
||||
* <pre>
|
||||
* ReportStorage rs = new ReportStorageImpl(...);
|
||||
* // Write an uploading file to a storage
|
||||
* try {
|
||||
* String rid = rs.getUniqueId(attributes); // params from URL
|
||||
* rs.saveAttributes(id, attributes);
|
||||
* if (!rs.reportExists(rid) || allowOverwrite)
|
||||
* rs.writeStreamToReport(rid, input, 0);
|
||||
* else
|
||||
* rs.appendStreamToReport(rid, input, 0);
|
||||
* } catch (...)
|
||||
*
|
||||
* // Read a file from the storage
|
||||
* try {
|
||||
* OutputStream os = fs.openReportForRead(fid);
|
||||
* os.read(...);
|
||||
* os.close();
|
||||
* } catch (...)
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
public interface ReportStorage {
|
||||
/**
|
||||
* Given a sorted map of attributes (name and value), returns a unique id
|
||||
* of the crash report.
|
||||
*
|
||||
* @param params a sorted map from name to value
|
||||
* @return a string as the file id
|
||||
*/
|
||||
public String getUniqueId(SortedMap<String, String> params);
|
||||
|
||||
/**
|
||||
* Given a report id, checks if the report data AND attributes identified
|
||||
* by this id exists on the storage.
|
||||
*
|
||||
* @param id a report id
|
||||
* @return true if the id represents an existing file
|
||||
*/
|
||||
public boolean reportExists(String id);
|
||||
|
||||
/**
|
||||
* Given a report id and a sorted map of attributes, saves attributes on
|
||||
* the storage.
|
||||
*
|
||||
* @param id a report id
|
||||
* @param attrs attributes associated with this id
|
||||
* @return true if attributes are saved successfully
|
||||
*/
|
||||
public boolean saveAttributes(String id, SortedMap<String, String> attrs);
|
||||
|
||||
/**
|
||||
* Given a report id, returns attributes associated with this report.
|
||||
*
|
||||
* @param id a report id
|
||||
* @return a sorted map from name to value
|
||||
* @throws FileNotFoundException if fileExists(id) returns false
|
||||
*/
|
||||
public SortedMap<String, String> getAttributes(String id)
|
||||
throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Writes <i>max</i> bytes from an input stream to a report identified by
|
||||
* an <i>id</i>.
|
||||
*
|
||||
* @param id a report id
|
||||
* @param input an input stream
|
||||
* @param max
|
||||
* maximum bytes to be written, if max is less or equal than 0, it will
|
||||
* write all bytes from input stream to the file
|
||||
* @return the number of bytes written
|
||||
* @throws FileNotFoundException
|
||||
* if reportExists(id) returns false
|
||||
* @throws IOException
|
||||
*/
|
||||
public int writeStreamToReport(String id, InputStream input, int max)
|
||||
throws FileNotFoundException, IOException;
|
||||
|
||||
/**
|
||||
* Opens a report for read.
|
||||
*
|
||||
* @param id a report id
|
||||
* @return an output stream for read
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public InputStream openReportForRead(String id) throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* @param id a report id
|
||||
* @return the checksum of a report.
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public String getChecksum(String id) throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Removes a report.
|
||||
*
|
||||
* @param id a report id
|
||||
* @return true if the report is removed successfully
|
||||
*/
|
||||
public boolean removeReport(String id) throws FileNotFoundException;
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.util.SortedMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* <p>Implement FileStorage using a local file system.</p>
|
||||
*
|
||||
* <p>Given a sorted map of attributes, create a checksum as unique file
|
||||
* id.</p>
|
||||
*
|
||||
* <p>Each file id is associated with two files in the storage:
|
||||
* <ol>
|
||||
* <li>an attribute file named as <id>.attr;</li>
|
||||
* <li>a data file named as <id>.data;</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*/
|
||||
|
||||
public class ReportStorageLocalFileImpl implements ReportStorage {
|
||||
|
||||
// Name extension of attribute file
|
||||
private static final String ATTR_FILE_EXT = ".attr";
|
||||
|
||||
// Name extension of data file
|
||||
private static final String DATA_FILE_EXT = ".data";
|
||||
|
||||
// Set the maximum file length at 1M
|
||||
private static final long MAX_ATTR_FILE_LENGTH = 1 << 10;
|
||||
|
||||
// Logging
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(ReportStorageLocalFileImpl.class.getName());
|
||||
|
||||
// Directory name for storing files.
|
||||
private String directoryName;
|
||||
|
||||
/**
|
||||
* Creates an instance of ReportStorageLocalFileImpl by providing a
|
||||
* directory name.
|
||||
*
|
||||
* @param dirname a directory for storing files
|
||||
* @throws IOException
|
||||
* @throws NullPointerException if dirname is null
|
||||
*/
|
||||
public ReportStorageLocalFileImpl(String dirname) throws IOException {
|
||||
this.directoryName = dirname;
|
||||
// new File can throw NullPointerException if dirname is null
|
||||
File dir = new File(dirname);
|
||||
if (!dir.exists() && !dir.mkdirs())
|
||||
throw new IOException("Cannot make dirs for "+dirname);
|
||||
|
||||
if (!dir.canWrite())
|
||||
throw new IOException("Cannot write to "+dirname
|
||||
+", check your permissions.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hashed string of attributes. Attributes are saved
|
||||
* in the file storage if not exists.
|
||||
*/
|
||||
public String getUniqueId(SortedMap<String, String> attributes)
|
||||
{
|
||||
String attr = CrashUtils.attributesToString(attributes);
|
||||
return CrashUtils.dataSignature(attr.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves attributes associated with a given id. if attributes does not
|
||||
* match the id (comparing results of getUniqueId(attributes)
|
||||
* with id), it returns false. Otherwise, attributes are saved.
|
||||
*/
|
||||
public boolean saveAttributes(String id, SortedMap<String, String> attributes)
|
||||
{
|
||||
String attr = CrashUtils.attributesToString(attributes);
|
||||
String digest = CrashUtils.dataSignature(attr.getBytes());
|
||||
|
||||
if (!digest.equals(id))
|
||||
return false;
|
||||
|
||||
try {
|
||||
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
|
||||
|
||||
// check if attr file exists
|
||||
if (!attrFile.exists()) {
|
||||
FileOutputStream fos = new FileOutputStream(attrFile);
|
||||
fos.write(attr.getBytes());
|
||||
fos.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.warning(e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if a report id exists. */
|
||||
public boolean reportExists(String id) {
|
||||
File datafile = new File(this.directoryName, id+DATA_FILE_EXT);
|
||||
File attrfile = new File(this.directoryName, id+ATTR_FILE_EXT);
|
||||
return datafile.isFile() && attrfile.isFile();
|
||||
}
|
||||
|
||||
/** Returns attributes in a map associated with an id. */
|
||||
public SortedMap<String, String> getAttributes(String id)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
if (!this.reportExists(id))
|
||||
throw new FileNotFoundException("no file is identified by "+id);
|
||||
|
||||
File attrfile = new File(this.directoryName, id+ATTR_FILE_EXT);
|
||||
if (!attrfile.isFile())
|
||||
throw new FileNotFoundException("no file is identified by "+id);
|
||||
|
||||
int length = (int) attrfile.length();
|
||||
if (length >= MAX_ATTR_FILE_LENGTH)
|
||||
throw new FileNotFoundException("no file is identified by "+id);
|
||||
|
||||
byte[] content = new byte[length];
|
||||
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(attrfile);
|
||||
fis.read(content);
|
||||
fis.close();
|
||||
|
||||
// verify checksum
|
||||
String sig = CrashUtils.dataSignature(content);
|
||||
if (!sig.equals(id)) {
|
||||
logger.warning("illegal access to "+attrfile);
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse contents
|
||||
return CrashUtils.stringToAttributes(new String(content));
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warning(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int writeStreamToReport(String id, InputStream input, int max)
|
||||
throws FileNotFoundException, IOException {
|
||||
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
|
||||
FileOutputStream fos = new FileOutputStream(datafile);
|
||||
|
||||
int bytesCopied = CrashUtils.copyStream(input, fos, max);
|
||||
|
||||
fos.close();
|
||||
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
public InputStream openReportForRead(String id) throws FileNotFoundException {
|
||||
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
|
||||
return new FileInputStream(datafile);
|
||||
}
|
||||
|
||||
public String getChecksum(String id) throws FileNotFoundException {
|
||||
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
|
||||
return CrashUtils.fileSignature(datafile);
|
||||
}
|
||||
|
||||
|
||||
public boolean removeReport(String id) {
|
||||
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
|
||||
File attrfile = new File(this.directoryName, id + ATTR_FILE_EXT);
|
||||
|
||||
datafile.delete();
|
||||
attrfile.delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** A simple regression test of ReportStorage.java and implementations.
|
||||
*/
|
||||
|
||||
public class ReportStorageTest {
|
||||
public static void main(String[] args) {
|
||||
// use /tmp/test as testing directory
|
||||
ReportStorage rs = null;
|
||||
try {
|
||||
rs = new ReportStorageLocalFileImpl("/tmp/test");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
runTest(rs);
|
||||
|
||||
// test passed
|
||||
System.out.println("OK.");
|
||||
}
|
||||
|
||||
private static void runTest(ReportStorage rs) {
|
||||
// test CrashUtil.bytesToHexString
|
||||
byte[] ba = new byte[4];
|
||||
ba[0] = (byte)0xFF;
|
||||
ba[1] = (byte)0x00;
|
||||
ba[2] = (byte)0x0F;
|
||||
ba[3] = (byte)0xF0;
|
||||
String s = CrashUtils.bytesToHexString(ba);
|
||||
assert s.equals("FF000FF0");
|
||||
|
||||
// construct a simple map of attributes
|
||||
TreeMap<String, String> params =
|
||||
new TreeMap<String, String>();
|
||||
params.put("Hello", "World");
|
||||
String rid = rs.getUniqueId(params);
|
||||
assert rid != null;
|
||||
|
||||
boolean b = rs.saveAttributes(rid, params);
|
||||
assert b;
|
||||
ba = "hellow, world!".getBytes();
|
||||
InputStream in = new ByteArrayInputStream(ba);
|
||||
|
||||
assert rs.reportExists(rid);
|
||||
|
||||
// save contents to storage
|
||||
try {
|
||||
rs.writeStreamToReport(rid, in, 0);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// read contents out
|
||||
try {
|
||||
InputStream in1 = rs.openReportForRead(rid);
|
||||
assert in1.available() == ba.length;
|
||||
in1.read(ba);
|
||||
assert(new String(ba).equals("hellow, world!"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
86
java/common/src/com/google/airbag/common/SymbolStorage.java
Normal file
86
java/common/src/com/google/airbag/common/SymbolStorage.java
Normal file
@ -0,0 +1,86 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.SortedMap;
|
||||
|
||||
/**
|
||||
* <p>SymbolStorage provides a simple interface for storing and retrieving
|
||||
* symbol files. Symbol files are indexed by a set of attributes:
|
||||
* <ul>
|
||||
* <li>product name</li>
|
||||
* <li>version (build id)</li>
|
||||
* <li>platform</li>
|
||||
* <li>application/module name</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The interface is designed for a symbol server supports upload, getid,
|
||||
* download operations.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
public interface SymbolStorage {
|
||||
/**
|
||||
* Saves a symbol file indexed by a set of attributes. Attributes include
|
||||
* product, version, platform, application/module, plus a checksum of
|
||||
* the symbol file.
|
||||
*
|
||||
* If a symbol file whose checksum matches the attribute, the input stream
|
||||
* can be NULL. No contents will be written. This can save some workloads
|
||||
* of uploading symbols.
|
||||
*
|
||||
* @param attrs a map of attributes, it must have 'prod', 'ver', 'plat',
|
||||
* 'app', and 'sum', values of the first four attributes are used
|
||||
* as index, and the value of the last attribute is the MD5 checksum
|
||||
* of the symbol file for verification.
|
||||
* @param contents symbol file contents.
|
||||
* @return true if checksum matches
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean saveSymbolFile(SortedMap<String, String> attrs,
|
||||
InputStream contents) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the checksum of a symbol file indexed by a set of attributes.
|
||||
* @param attrs a map of attributes, must include 'prod', 'ver', 'plat',
|
||||
* and 'app'.
|
||||
* @return MD5 checksum as a string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getFileChecksum(SortedMap<String, String> attrs)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Checks if a file exists already (identified by its checksum).
|
||||
* @param checksum the file checksum
|
||||
* @return true if a file with the same checksum exists
|
||||
*/
|
||||
public boolean fileExists(String checksum);
|
||||
|
||||
/**
|
||||
* Gets an input stream of a symbol server indexed by a set of attributes.
|
||||
* @param attrs a map of attributes, must include 'prod', 'ver', 'plat',
|
||||
* and 'app'.
|
||||
* @return an input stream of the symbol file
|
||||
* @throws IOException
|
||||
*/
|
||||
public InputStream openFileForRead(SortedMap<String, String> attrs)
|
||||
throws IOException;
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Implementation of SymbolStorage interface using a local file system.
|
||||
*
|
||||
* Given a set of parameters, prod, ver, app, plat, computes its MD5 digest.
|
||||
* Digest + .attr contains attributes and checksum of the symbol file.
|
||||
* Symbol file content is stored in a file named by its checksum + .data.
|
||||
*
|
||||
* To upload a symbol file, a client can send a query:
|
||||
* <pre>
|
||||
* /symexists?sum=<checksum>, the server checks if a symbol file identified by checksum exists;
|
||||
* /upload?prod=<product>&ver=<version>&plat=<platform>&app=<module>&sum=<checksum> use POST
|
||||
* method with or without uploading a file.
|
||||
*
|
||||
* A client can always call /upload to upload a symbol file.
|
||||
* However, a more efficient way is to check whether a file is on the server by calling /symexists.
|
||||
* If so, the client can just POST the request without actually upload the file content,
|
||||
* checksum is sufficient.
|
||||
*
|
||||
* /getchecksum?prod=<product>&ver=<version>&plat=<platform>&app=<module>, returns the checksum
|
||||
* of the symbol file on the server.
|
||||
* /download?prod=<product>&ver=<version>&plat=<platform>&app=<module>, downloads the symbol file
|
||||
* on the server.
|
||||
*
|
||||
* A client can always use /download to download a symbol file.
|
||||
* However, if the client maintins its own cache of symbol files, it can call /getchecksum,
|
||||
* and look up the cache using the checksum. If the cache does not have the file, then it
|
||||
* calls /download.
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
public class SymbolStorageLocalFileImpl implements SymbolStorage {
|
||||
|
||||
// Name extension of attribute file
|
||||
private static final String ATTR_FILE_EXT = ".attr";
|
||||
|
||||
// Name extension of data file
|
||||
private static final String DATA_FILE_EXT = ".data";
|
||||
|
||||
private static final int MAX_ATTR_FILE_LENGTH = 1 << 10;
|
||||
|
||||
// Directory name for storing files.
|
||||
private String directoryName;
|
||||
|
||||
/**
|
||||
* Creates an instance of ReportStorageLocalFileImpl by providing a
|
||||
* directory name.
|
||||
*
|
||||
* @param dirname
|
||||
* @throws IOException
|
||||
*/
|
||||
public SymbolStorageLocalFileImpl(String dirname) throws IOException {
|
||||
this.directoryName = dirname;
|
||||
// new File can throw NullPointerException if dirname is null
|
||||
File dir = new File(dirname);
|
||||
if (!dir.exists() && !dir.mkdirs())
|
||||
throw new IOException("Cannot make dirs for "+dirname);
|
||||
|
||||
if (!dir.canWrite())
|
||||
throw new IOException("Cannot write to "+dirname
|
||||
+", check your permissions.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a symbol file.
|
||||
*/
|
||||
public boolean saveSymbolFile(SortedMap<String, String> attrs, InputStream contents)
|
||||
throws IOException {
|
||||
String digest = getAttributesSignature(attrs);
|
||||
|
||||
// get 'sum' value
|
||||
String checksum = attrs.get(NameConstants.CHECKSUM_PNAME);
|
||||
if (checksum == null)
|
||||
return false;
|
||||
|
||||
// attribute file name and data file name
|
||||
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
|
||||
|
||||
// use passed in checksum as file name
|
||||
File dataFile = new File(this.directoryName, checksum+DATA_FILE_EXT);
|
||||
|
||||
// write data to file
|
||||
FileOutputStream outs = new FileOutputStream(dataFile);
|
||||
CrashUtils.copyStream(contents, outs, 0);
|
||||
outs.close();
|
||||
|
||||
// get signature of input stream
|
||||
String filesig = CrashUtils.fileSignature(dataFile);
|
||||
|
||||
if (!checksum.equals(filesig)) {
|
||||
dataFile.delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
// save all attributes with checksum
|
||||
String fullAttrs = CrashUtils.attributesToString(attrs);
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(attrFile);
|
||||
fos.write(fullAttrs.getBytes());
|
||||
fos.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public String getFileChecksum(SortedMap<String, String> attrs) throws IOException {
|
||||
String digest = getAttributesSignature(attrs);
|
||||
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
|
||||
|
||||
if (!attrFile.isFile())
|
||||
throw new FileNotFoundException();
|
||||
|
||||
int length = (int) attrFile.length();
|
||||
if (length >= MAX_ATTR_FILE_LENGTH)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
byte[] content = new byte[length];
|
||||
|
||||
FileInputStream fis = new FileInputStream(attrFile);
|
||||
fis.read(content);
|
||||
fis.close();
|
||||
|
||||
// parse contents
|
||||
SortedMap<String, String> savedAttrs =
|
||||
CrashUtils.stringToAttributes(new String(content));
|
||||
|
||||
return savedAttrs.get(NameConstants.CHECKSUM_PNAME);
|
||||
}
|
||||
|
||||
public boolean fileExists(String checksum) {
|
||||
File dataFile = new File(this.directoryName, checksum+DATA_FILE_EXT);
|
||||
return dataFile.isFile();
|
||||
}
|
||||
|
||||
public InputStream openFileForRead(SortedMap<String, String> attrs)
|
||||
throws IOException {
|
||||
String checksum = getFileChecksum(attrs);
|
||||
if (checksum == null)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
File dataFile = new File(this.directoryName, checksum + DATA_FILE_EXT);
|
||||
if (!dataFile.isFile())
|
||||
throw new FileNotFoundException();
|
||||
|
||||
return new FileInputStream(dataFile);
|
||||
}
|
||||
|
||||
private static final String[] requiredParameters = {
|
||||
NameConstants.PRODUCT_PNAME,
|
||||
NameConstants.APPLICATION_PNAME,
|
||||
NameConstants.PLATFORM_PNAME,
|
||||
NameConstants.VERSION_PNAME
|
||||
};
|
||||
|
||||
private String getAttributesSignature(SortedMap<String, String> attrs) {
|
||||
// canonize parameters
|
||||
SortedMap<String, String> params = canonizeAttributes(attrs);
|
||||
String attrString = CrashUtils.attributesToString(params);
|
||||
return CrashUtils.dataSignature(attrString.getBytes());
|
||||
}
|
||||
/* Canonize attributes, get 'prod', 'ver', 'plat', and 'app' values,
|
||||
* and put them in a new sorted map. If one of value is missing,
|
||||
* returns null.
|
||||
*/
|
||||
private SortedMap<String, String>
|
||||
canonizeAttributes(SortedMap<String, String> attrs) {
|
||||
SortedMap<String, String> params = new TreeMap<String, String>();
|
||||
for (String s : requiredParameters) {
|
||||
String v = attrs.get(s);
|
||||
if (v == null)
|
||||
return null;
|
||||
else
|
||||
params.put(s, v);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Test ReportStorage and its implementation by simulating two pocesses
|
||||
* as producer and consuer.
|
||||
*
|
||||
* To use this test with TestReportStorageProducer:
|
||||
* > java TestReportStorageProducer /tmp/testdir
|
||||
*
|
||||
* In another console,
|
||||
* > java TestReportStorageConsumer /tmp/testdir
|
||||
*
|
||||
* Then watch output on both console.
|
||||
*/
|
||||
public class TestReportStorageConsumer {
|
||||
|
||||
/**
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String testdir = args[0];
|
||||
ReportStorage rs = null;
|
||||
try {
|
||||
rs = new ReportStorageLocalFileImpl(testdir);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
consume(rs);
|
||||
}
|
||||
|
||||
private static void consume(ReportStorage rs) {
|
||||
int i = 0;
|
||||
TreeMap<String, String> params =
|
||||
new TreeMap<String, String>();
|
||||
String v = "hello";
|
||||
byte[] buf = new byte[1024];
|
||||
while (true) {
|
||||
params.put(Integer.toString(i), v);
|
||||
String id = rs.getUniqueId(params);
|
||||
rs.saveAttributes(id, params);
|
||||
if (rs.reportExists(id)) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = rs.openReportForRead(id);
|
||||
while (is.read(buf) != -1) {
|
||||
System.out.print(new String(buf));
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/* Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.airbag.common;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Test ReportStorage and its implementation by simulating two pocesses
|
||||
* as producer and consuer.
|
||||
*
|
||||
* To use this test with TestReportStorageConsumer:
|
||||
* > java TestReportStorageProducer /tmp/testdir
|
||||
*
|
||||
* In another console,
|
||||
* > java TestReportStorageConsumer /tmp/testdir
|
||||
*
|
||||
* Then watch output on both console.
|
||||
*/
|
||||
|
||||
public class TestReportStorageProducer {
|
||||
|
||||
/**
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String testdir = args[0];
|
||||
ReportStorage rs = null;
|
||||
try {
|
||||
rs = new ReportStorageLocalFileImpl(testdir);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
produce(rs);
|
||||
}
|
||||
|
||||
private static void produce(ReportStorage rs) {
|
||||
int i = 0;
|
||||
TreeMap<String, String> params =
|
||||
new TreeMap<String, String>();
|
||||
String v = "hello";
|
||||
ByteArrayInputStream ba = new ByteArrayInputStream("hello world!".getBytes());
|
||||
while (true) {
|
||||
ba.reset();
|
||||
params.put(Integer.toString(i), v);
|
||||
String id = rs.getUniqueId(params);
|
||||
rs.saveAttributes(id, params);
|
||||
if (id == null) {
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
rs.writeStreamToReport(id, ba, 0);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user