1 /*******************************************************************************
2 * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others
4 * All rights reserved. This program and the accompanying materials are made
5 * available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Matthew Khouzam - Initial API and implementation
11 * Simon Marchi - Initial API and implementation
12 * Matthew Khouzam - Update for live trace reading support
13 * Bernd Hufmann - Add method to copy metadata file
14 *******************************************************************************/
16 package org
.eclipse
.tracecompass
.ctf
.core
.trace
;
19 import java
.io
.FileInputStream
;
20 import java
.io
.FileNotFoundException
;
21 import java
.io
.FileReader
;
22 import java
.io
.IOException
;
23 import java
.io
.Reader
;
24 import java
.io
.StringReader
;
25 import java
.nio
.ByteBuffer
;
26 import java
.nio
.ByteOrder
;
27 import java
.nio
.channels
.FileChannel
;
28 import java
.nio
.charset
.Charset
;
29 import java
.nio
.file
.FileSystems
;
30 import java
.nio
.file
.Files
;
31 import java
.nio
.file
.Path
;
32 import java
.nio
.file
.StandardOpenOption
;
33 import java
.util
.UUID
;
35 import org
.antlr
.runtime
.ANTLRReaderStream
;
36 import org
.antlr
.runtime
.CommonTokenStream
;
37 import org
.antlr
.runtime
.RecognitionException
;
38 import org
.antlr
.runtime
.tree
.CommonTree
;
39 import org
.antlr
.runtime
.tree
.RewriteCardinalityException
;
40 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
41 import org
.eclipse
.tracecompass
.ctf
.core
.CTFException
;
42 import org
.eclipse
.tracecompass
.ctf
.parser
.CTFLexer
;
43 import org
.eclipse
.tracecompass
.ctf
.parser
.CTFParser
;
44 import org
.eclipse
.tracecompass
.ctf
.parser
.CTFParser
.parse_return
;
45 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.CtfAntlrException
;
46 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.IOStructGen
;
47 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.event
.metadata
.ParseException
;
48 import org
.eclipse
.tracecompass
.internal
.ctf
.core
.trace
.Utils
;
51 * The CTF trace metadata TSDL file
54 * @author Matthew Khouzam
55 * @author Simon Marchi
57 public class Metadata
{
59 // ------------------------------------------------------------------------
61 // ------------------------------------------------------------------------
63 private static final Charset ASCII_CHARSET
= Charset
.forName("ASCII"); //$NON-NLS-1$
65 private static final String TEXT_ONLY_METADATA_HEADER_PREFIX
= "/* CTF"; //$NON-NLS-1$
67 private static final int PREVALIDATION_SIZE
= 8;
69 private static final int BITS_PER_BYTE
= Byte
.SIZE
;
72 * Name of the metadata file in the trace directory
74 private static final String METADATA_FILENAME
= "metadata"; //$NON-NLS-1$
77 * Size of the metadata packet header, in bytes, computed by hand.
79 private static final int METADATA_PACKET_HEADER_SIZE
= 37;
81 // ------------------------------------------------------------------------
83 // ------------------------------------------------------------------------
86 * Byte order as detected when reading the TSDL magic number.
88 private ByteOrder fDetectedByteOrder
= null;
91 * The trace file to which belongs this metadata file.
93 private final CTFTrace fTrace
;
95 private IOStructGen fTreeParser
;
97 // ------------------------------------------------------------------------
99 // ------------------------------------------------------------------------
102 * Constructs a Metadata object.
105 * The trace to which belongs this metadata file.
107 public Metadata(CTFTrace trace
) {
112 * For network streaming
115 fTrace
= new CTFTrace();
118 // ------------------------------------------------------------------------
119 // Getters/Setters/Predicates
120 // ------------------------------------------------------------------------
123 * Returns the ByteOrder that was detected while parsing the metadata.
125 * @return The byte order.
127 public ByteOrder
getDetectedByteOrder() {
128 return fDetectedByteOrder
;
132 * Gets the parent trace
134 * @return the parent trace
136 public CTFTrace
getTrace() {
140 // ------------------------------------------------------------------------
142 // ------------------------------------------------------------------------
145 * Parse the metadata file.
147 * @throws CTFException
148 * If there was a problem parsing the metadata
150 public void parseFile() throws CTFException
{
153 * Reader. It will contain a StringReader if we are using packet-based
154 * metadata and it will contain a FileReader if we have text-based
158 try (FileInputStream fis
= new FileInputStream(getMetadataPath());
159 FileChannel metadataFileChannel
= fis
.getChannel();
160 /* Check if metadata is packet-based, if not it is text based */
161 Reader metadataTextInput
= (isPacketBased(metadataFileChannel
) ?
readBinaryMetaData(metadataFileChannel
) : new FileReader(fis
.getFD()));) {
163 readMetaDataText(metadataTextInput
);
165 } catch (FileNotFoundException e
) {
166 throw new CTFException("Cannot find metadata file!", e
); //$NON-NLS-1$
167 } catch (IOException
| ParseException e
) {
168 throw new CTFException(e
);
169 } catch (RecognitionException
| RewriteCardinalityException e
) {
170 throw new CtfAntlrException(e
);
174 private Reader
readBinaryMetaData(FileChannel metadataFileChannel
) throws CTFException
{
175 /* Create StringBuffer to receive metadata text */
176 StringBuffer metadataText
= new StringBuffer();
179 * Read metadata packet one by one, appending the text to the
182 MetadataPacketHeader packetHeader
= readMetadataPacket(
183 metadataFileChannel
, metadataText
);
184 while (packetHeader
!= null) {
185 packetHeader
= readMetadataPacket(metadataFileChannel
,
189 /* Wrap the metadata string with a StringReader */
190 return new StringReader(metadataText
.toString());
194 * Executes a weak validation of the metadata. It checks if a file with name
195 * metadata exists and if one of the following conditions are met:
197 * <li>For text-only metadata, the file starts with "/* CTF" (without the
199 * <li>For packet-based metadata, the file starts with correct magic number
204 * path to CTF trace directory
205 * @return <code>true</code> if pre-validation is ok else <code>false</code>
206 * @throws CTFException
207 * file channel cannot be created
210 public static boolean preValidate(String path
) throws CTFException
{
211 String metadataPath
= path
+ Utils
.SEPARATOR
+ METADATA_FILENAME
;
212 File metadataFile
= new File(metadataPath
);
213 if (metadataFile
.exists() && metadataFile
.length() > PREVALIDATION_SIZE
) {
214 try (FileChannel fc
= FileChannel
.open(metadataFile
.toPath(), StandardOpenOption
.READ
)) {
215 ByteBuffer bb
= ByteBuffer
.allocate(PREVALIDATION_SIZE
);
219 if (bb
.getInt(0) == Utils
.TSDL_MAGIC
) {
222 bb
.order(ByteOrder
.LITTLE_ENDIAN
);
223 if (bb
.getInt(0) == Utils
.TSDL_MAGIC
) {
227 byte bytes
[] = new byte[PREVALIDATION_SIZE
];
229 String text
= new String(bytes
, ASCII_CHARSET
);
230 return text
.startsWith(TEXT_ONLY_METADATA_HEADER_PREFIX
);
231 } catch (IOException e
) {
232 throw new CTFException(e
.getMessage(), e
);
239 * Read the metadata from a formatted TSDL string
243 * @throws CTFException
244 * this exception wraps a ParseException, IOException or
245 * CtfAntlrException, three exceptions that can be obtained from
246 * parsing a TSDL file
248 public void parseText(String data
) throws CTFException
{
249 Reader metadataTextInput
= new StringReader(data
);
251 readMetaDataText(metadataTextInput
);
252 } catch (IOException
| ParseException e
) {
253 throw new CTFException(e
);
254 } catch (RecognitionException
| RewriteCardinalityException e
) {
255 throw new CtfAntlrException(e
);
260 private void readMetaDataText(Reader metadataTextInput
) throws IOException
, RecognitionException
, ParseException
{
261 CommonTree tree
= createAST(metadataTextInput
);
263 /* Generate IO structures (declarations) */
264 fTreeParser
= new IOStructGen(tree
, NonNullUtils
.checkNotNull(fTrace
));
265 fTreeParser
.generate();
266 /* store locally in case of concurrent modification */
267 ByteOrder detectedByteOrder
= getDetectedByteOrder();
268 if (detectedByteOrder
!= null && fTrace
.getByteOrder() != detectedByteOrder
) {
269 throw new ParseException("Metadata byte order and trace byte order inconsistent."); //$NON-NLS-1$
274 * Read a metadata fragment from a formatted TSDL string
276 * @param dataFragment
278 * @throws CTFException
279 * this exception wraps a ParseException, IOException or
280 * CtfAntlrException, three exceptions that can be obtained from
281 * parsing a TSDL file
283 public void parseTextFragment(String dataFragment
) throws CTFException
{
284 Reader metadataTextInput
= new StringReader(dataFragment
);
286 readMetaDataTextFragment(metadataTextInput
);
287 } catch (IOException
| ParseException e
) {
288 throw new CTFException(e
);
289 } catch (RecognitionException
| RewriteCardinalityException e
) {
290 throw new CtfAntlrException(e
);
294 private void readMetaDataTextFragment(Reader metadataTextInput
) throws IOException
, RecognitionException
, ParseException
{
295 CommonTree tree
= createAST(metadataTextInput
);
296 fTreeParser
.setTree(tree
);
297 fTreeParser
.generateFragment();
300 private static CommonTree
createAST(Reader metadataTextInput
) throws IOException
,
301 RecognitionException
{
302 /* Create an ANTLR reader */
303 ANTLRReaderStream antlrStream
;
304 antlrStream
= new ANTLRReaderStream(metadataTextInput
);
306 /* Parse the metadata text and get the AST */
307 CTFLexer ctfLexer
= new CTFLexer(antlrStream
);
308 CommonTokenStream tokens
= new CommonTokenStream(ctfLexer
);
309 CTFParser ctfParser
= new CTFParser(tokens
, false);
311 parse_return pr
= ctfParser
.parse();
316 * Determines whether the metadata file is packet-based by looking at the
317 * TSDL magic number. If it is packet-based, it also gives information about
318 * the endianness of the trace using the detectedByteOrder attribute.
320 * @param metadataFileChannel
321 * FileChannel of the metadata file.
322 * @return True if the metadata is packet-based.
323 * @throws CTFException
325 private boolean isPacketBased(FileChannel metadataFileChannel
)
326 throws CTFException
{
328 * Create a ByteBuffer to read the TSDL magic number (default is
331 ByteBuffer magicByteBuffer
= ByteBuffer
.allocate(Utils
.TSDL_MAGIC_LEN
);
333 /* Read without changing file position */
335 metadataFileChannel
.read(magicByteBuffer
, 0);
336 } catch (IOException e
) {
337 throw new CTFException("Unable to read metadata file channel.", e
); //$NON-NLS-1$
340 /* Get the first int from the file */
341 int magic
= magicByteBuffer
.getInt(0);
343 /* Check if it matches */
344 if (Utils
.TSDL_MAGIC
== magic
) {
345 fDetectedByteOrder
= ByteOrder
.BIG_ENDIAN
;
349 /* Try the same thing, but with little-endian */
350 magicByteBuffer
.order(ByteOrder
.LITTLE_ENDIAN
);
351 magic
= magicByteBuffer
.getInt(0);
353 if (Utils
.TSDL_MAGIC
== magic
) {
354 fDetectedByteOrder
= ByteOrder
.LITTLE_ENDIAN
;
361 private String
getMetadataPath() {
362 /* Path of metadata file = trace directory path + metadata filename */
363 if (fTrace
.getTraceDirectory() == null) {
364 return ""; //$NON-NLS-1$
366 return fTrace
.getTraceDirectory().getPath()
367 + Utils
.SEPARATOR
+ METADATA_FILENAME
;
371 * Reads a metadata packet from the given metadata FileChannel, do some
372 * basic validation and append the text to the StringBuffer.
374 * @param metadataFileChannel
375 * Metadata FileChannel
376 * @param metadataText
377 * StringBuffer to which the metadata text will be appended.
378 * @return A structure describing the header of the metadata packet, or null
379 * if the end of the file is reached.
380 * @throws CTFException
382 private MetadataPacketHeader
readMetadataPacket(
383 FileChannel metadataFileChannel
, StringBuffer metadataText
)
384 throws CTFException
{
385 /* Allocate a ByteBuffer for the header */
386 ByteBuffer headerByteBuffer
= ByteBuffer
.allocate(METADATA_PACKET_HEADER_SIZE
);
388 /* Read the header */
390 int nbBytesRead
= metadataFileChannel
.read(headerByteBuffer
);
392 /* Return null if EOF */
393 if (nbBytesRead
< 0) {
397 if (nbBytesRead
!= METADATA_PACKET_HEADER_SIZE
) {
398 throw new CTFException("Error reading the metadata header."); //$NON-NLS-1$
401 } catch (IOException e
) {
402 throw new CTFException("Error reading the metadata header.", e
); //$NON-NLS-1$
405 /* Set ByteBuffer's position to 0 */
406 headerByteBuffer
.position(0);
408 /* Use byte order that was detected with the magic number */
409 headerByteBuffer
.order(fDetectedByteOrder
);
411 MetadataPacketHeader header
= new MetadataPacketHeader(headerByteBuffer
);
413 /* Check TSDL magic number */
414 if (!header
.isMagicValid()) {
415 throw new CTFException("TSDL magic number does not match"); //$NON-NLS-1$
419 if (!fTrace
.uuidIsSet()) {
420 fTrace
.setUUID(header
.getUuid());
421 } else if (!fTrace
.getUUID().equals(header
.getUuid())) {
422 throw new CTFException("UUID mismatch"); //$NON-NLS-1$
425 /* Extract the text from the packet */
426 int payloadSize
= ((header
.getContentSize() / BITS_PER_BYTE
) - METADATA_PACKET_HEADER_SIZE
);
427 if (payloadSize
< 0) {
428 throw new CTFException("Invalid metadata packet payload size."); //$NON-NLS-1$
430 int skipSize
= (header
.getPacketSize() - header
.getContentSize()) / BITS_PER_BYTE
;
432 /* Read the payload + the padding in a ByteBuffer */
433 ByteBuffer payloadByteBuffer
= ByteBuffer
.allocateDirect(payloadSize
436 metadataFileChannel
.read(payloadByteBuffer
);
437 } catch (IOException e
) {
438 throw new CTFException("Error reading metadata packet payload.", e
); //$NON-NLS-1$
440 payloadByteBuffer
.rewind();
442 /* Read only the payload from the ByteBuffer into a byte array */
443 byte payloadByteArray
[] = new byte[payloadByteBuffer
.remaining()];
444 payloadByteBuffer
.get(payloadByteArray
, 0, payloadSize
);
446 /* Convert the byte array to a String */
447 String str
= new String(payloadByteArray
, 0, payloadSize
, ASCII_CHARSET
);
449 /* Append it to the existing metadata */
450 metadataText
.append(str
);
455 private static class MetadataPacketHeader
{
457 private static final int UUID_SIZE
= 16;
458 private final int fMagic
;
459 private final UUID fUuid
;
460 private final int fChecksum
;
461 private final int fContentSize
;
462 private final int fPacketSize
;
463 private final byte fCompressionScheme
;
464 private final byte fEncryptionScheme
;
465 private final byte fChecksumScheme
;
466 private final byte fCtfMajorVersion
;
467 private final byte fCtfMinorVersion
;
469 public MetadataPacketHeader(ByteBuffer headerByteBuffer
) {
470 /* Read from the ByteBuffer */
471 fMagic
= headerByteBuffer
.getInt();
472 byte[] uuidBytes
= new byte[UUID_SIZE
];
473 headerByteBuffer
.get(uuidBytes
);
474 fUuid
= Utils
.makeUUID(uuidBytes
);
475 fChecksum
= headerByteBuffer
.getInt();
476 fContentSize
= headerByteBuffer
.getInt();
477 fPacketSize
= headerByteBuffer
.getInt();
478 fCompressionScheme
= headerByteBuffer
.get();
479 fEncryptionScheme
= headerByteBuffer
.get();
480 fChecksumScheme
= headerByteBuffer
.get();
481 fCtfMajorVersion
= headerByteBuffer
.get();
482 fCtfMinorVersion
= headerByteBuffer
.get();
485 public boolean isMagicValid() {
486 return fMagic
== Utils
.TSDL_MAGIC
;
489 public UUID
getUuid() {
493 public int getContentSize() {
497 public int getPacketSize() {
502 public String
toString() {
503 /* Only for debugging, shouldn't be externalized */
504 /* Therefore it cannot be covered by test cases */
505 return "MetadataPacketHeader [magic=0x" //$NON-NLS-1$
506 + Integer
.toHexString(fMagic
) + ", uuid=" //$NON-NLS-1$
507 + fUuid
.toString() + ", checksum=" + fChecksum
//$NON-NLS-1$
508 + ", contentSize=" + fContentSize
+ ", packetSize=" //$NON-NLS-1$ //$NON-NLS-2$
509 + fPacketSize
+ ", compressionScheme=" + fCompressionScheme
//$NON-NLS-1$
510 + ", encryptionScheme=" + fEncryptionScheme
//$NON-NLS-1$
511 + ", checksumScheme=" + fChecksumScheme
//$NON-NLS-1$
512 + ", ctfMajorVersion=" + fCtfMajorVersion
//$NON-NLS-1$
513 + ", ctfMinorVersion=" + fCtfMinorVersion
+ ']'; //$NON-NLS-1$
519 * Copies the metadata file to a destination directory.
522 * the destination directory
523 * @return the path to the target file
524 * @throws IOException
525 * if an error occurred
529 public Path
copyTo(final File path
) throws IOException
{
530 Path source
= FileSystems
.getDefault().getPath(fTrace
.getTraceDirectory().getAbsolutePath(), METADATA_FILENAME
);
531 Path destPath
= FileSystems
.getDefault().getPath(path
.getAbsolutePath());
532 return Files
.copy(source
, destPath
.resolve(source
.getFileName()));