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 // ------------------------------------------------------------------------
62 private static final String TEXT_ONLY_METADATA_HEADER_PREFIX
= "/* CTF"; //$NON-NLS-1$
64 private static final int PREVALIDATION_SIZE
= 8;
66 private static final int BITS_PER_BYTE
= Byte
.SIZE
;
69 * Name of the metadata file in the trace directory
71 private static final String METADATA_FILENAME
= "metadata"; //$NON-NLS-1$
74 * Size of the metadata packet header, in bytes, computed by hand.
76 private static final int METADATA_PACKET_HEADER_SIZE
= 37;
78 // ------------------------------------------------------------------------
80 // ------------------------------------------------------------------------
83 * Byte order as detected when reading the TSDL magic number.
85 private ByteOrder fDetectedByteOrder
= null;
88 * The trace file to which belongs this metadata file.
90 private final CTFTrace fTrace
;
92 private IOStructGen fTreeParser
;
94 // ------------------------------------------------------------------------
96 // ------------------------------------------------------------------------
99 * Constructs a Metadata object.
102 * The trace to which belongs this metadata file.
104 public Metadata(CTFTrace trace
) {
109 * For network streaming
112 fTrace
= new CTFTrace();
115 // ------------------------------------------------------------------------
116 // Getters/Setters/Predicates
117 // ------------------------------------------------------------------------
120 * Returns the ByteOrder that was detected while parsing the metadata.
122 * @return The byte order.
124 public ByteOrder
getDetectedByteOrder() {
125 return fDetectedByteOrder
;
129 * Gets the parent trace
131 * @return the parent trace
133 public CTFTrace
getTrace() {
137 // ------------------------------------------------------------------------
139 // ------------------------------------------------------------------------
142 * Parse the metadata file.
144 * @throws CTFException
145 * If there was a problem parsing the metadata
147 public void parseFile() throws CTFException
{
150 * Reader. It will contain a StringReader if we are using packet-based
151 * metadata and it will contain a FileReader if we have text-based
155 try (FileInputStream fis
= new FileInputStream(getMetadataPath());
156 FileChannel metadataFileChannel
= fis
.getChannel();
157 /* Check if metadata is packet-based, if not it is text based */
158 Reader metadataTextInput
= (isPacketBased(metadataFileChannel
) ?
readBinaryMetaData(metadataFileChannel
) : new FileReader(getMetadataPath()));) {
160 readMetaDataText(metadataTextInput
);
162 } catch (FileNotFoundException e
) {
163 throw new CTFException("Cannot find metadata file!", e
); //$NON-NLS-1$
164 } catch (IOException
| ParseException e
) {
165 throw new CTFException(e
);
166 } catch (RecognitionException
| RewriteCardinalityException e
) {
167 throw new CtfAntlrException(e
);
171 private Reader
readBinaryMetaData(FileChannel metadataFileChannel
) throws CTFException
{
172 /* Create StringBuffer to receive metadata text */
173 StringBuffer metadataText
= new StringBuffer();
176 * Read metadata packet one by one, appending the text to the
179 MetadataPacketHeader packetHeader
= readMetadataPacket(
180 metadataFileChannel
, metadataText
);
181 while (packetHeader
!= null) {
182 packetHeader
= readMetadataPacket(metadataFileChannel
,
186 /* Wrap the metadata string with a StringReader */
187 return new StringReader(metadataText
.toString());
191 * Executes a weak validation of the metadata. It checks if a file with name
192 * metadata exists and if one of the following conditions are met:
194 * <li>For text-only metadata, the file starts with "/* CTF" (without the
196 * <li>For packet-based metadata, the file starts with correct magic number
201 * path to CTF trace directory
202 * @return <code>true</code> if pre-validation is ok else <code>false</code>
203 * @throws CTFException
204 * file channel cannot be created
207 public static boolean preValidate(String path
) throws CTFException
{
208 String metadataPath
= path
+ Utils
.SEPARATOR
+ METADATA_FILENAME
;
209 File metadataFile
= new File(metadataPath
);
210 if (metadataFile
.exists() && metadataFile
.length() > PREVALIDATION_SIZE
) {
211 try (FileChannel fc
= FileChannel
.open(metadataFile
.toPath(), StandardOpenOption
.READ
)) {
212 ByteBuffer bb
= ByteBuffer
.allocate(PREVALIDATION_SIZE
);
216 if (bb
.getInt(0) == Utils
.TSDL_MAGIC
) {
219 bb
.order(ByteOrder
.LITTLE_ENDIAN
);
220 if (bb
.getInt(0) == Utils
.TSDL_MAGIC
) {
224 Charset forName
= Charset
.forName("ASCII"); //$NON-NLS-1$
225 byte bytes
[] = new byte[PREVALIDATION_SIZE
];
227 String text
= new String(bytes
, forName
);
228 return text
.startsWith(TEXT_ONLY_METADATA_HEADER_PREFIX
);
229 } catch (IOException e
) {
230 throw new CTFException(e
.getMessage(), e
);
237 * Read the metadata from a formatted TSDL string
241 * @throws CTFException
242 * this exception wraps a ParseException, IOException or
243 * CtfAntlrException, three exceptions that can be obtained from
244 * parsing a TSDL file
246 public void parseText(String data
) throws CTFException
{
247 Reader metadataTextInput
= new StringReader(data
);
249 readMetaDataText(metadataTextInput
);
250 } catch (IOException
| ParseException e
) {
251 throw new CTFException(e
);
252 } catch (RecognitionException
| RewriteCardinalityException e
) {
253 throw new CtfAntlrException(e
);
258 private void readMetaDataText(Reader metadataTextInput
) throws IOException
, RecognitionException
, ParseException
{
259 CommonTree tree
= createAST(metadataTextInput
);
261 /* Generate IO structures (declarations) */
262 fTreeParser
= new IOStructGen(tree
, NonNullUtils
.checkNotNull(fTrace
));
263 fTreeParser
.generate();
264 /* store locally in case of concurrent modification */
265 ByteOrder detectedByteOrder
= getDetectedByteOrder();
266 if (detectedByteOrder
!= null && fTrace
.getByteOrder() != detectedByteOrder
) {
267 throw new ParseException("Metadata byte order and trace byte order inconsistent."); //$NON-NLS-1$
272 * Read a metadata fragment from a formatted TSDL string
274 * @param dataFragment
276 * @throws CTFException
277 * this exception wraps a ParseException, IOException or
278 * CtfAntlrException, three exceptions that can be obtained from
279 * parsing a TSDL file
281 public void parseTextFragment(String dataFragment
) throws CTFException
{
282 Reader metadataTextInput
= new StringReader(dataFragment
);
284 readMetaDataTextFragment(metadataTextInput
);
285 } catch (IOException
| ParseException e
) {
286 throw new CTFException(e
);
287 } catch (RecognitionException
| RewriteCardinalityException e
) {
288 throw new CtfAntlrException(e
);
292 private void readMetaDataTextFragment(Reader metadataTextInput
) throws IOException
, RecognitionException
, ParseException
{
293 CommonTree tree
= createAST(metadataTextInput
);
294 fTreeParser
.setTree(tree
);
295 fTreeParser
.generateFragment();
298 private static CommonTree
createAST(Reader metadataTextInput
) throws IOException
,
299 RecognitionException
{
300 /* Create an ANTLR reader */
301 ANTLRReaderStream antlrStream
;
302 antlrStream
= new ANTLRReaderStream(metadataTextInput
);
304 /* Parse the metadata text and get the AST */
305 CTFLexer ctfLexer
= new CTFLexer(antlrStream
);
306 CommonTokenStream tokens
= new CommonTokenStream(ctfLexer
);
307 CTFParser ctfParser
= new CTFParser(tokens
, false);
309 parse_return pr
= ctfParser
.parse();
314 * Determines whether the metadata file is packet-based by looking at the
315 * TSDL magic number. If it is packet-based, it also gives information about
316 * the endianness of the trace using the detectedByteOrder attribute.
318 * @param metadataFileChannel
319 * FileChannel of the metadata file.
320 * @return True if the metadata is packet-based.
321 * @throws CTFException
323 private boolean isPacketBased(FileChannel metadataFileChannel
)
324 throws CTFException
{
326 * Create a ByteBuffer to read the TSDL magic number (default is
329 ByteBuffer magicByteBuffer
= ByteBuffer
.allocate(Utils
.TSDL_MAGIC_LEN
);
331 /* Read without changing file position */
333 metadataFileChannel
.read(magicByteBuffer
, 0);
334 } catch (IOException e
) {
335 throw new CTFException("Unable to read metadata file channel.", e
); //$NON-NLS-1$
338 /* Get the first int from the file */
339 int magic
= magicByteBuffer
.getInt(0);
341 /* Check if it matches */
342 if (Utils
.TSDL_MAGIC
== magic
) {
343 fDetectedByteOrder
= ByteOrder
.BIG_ENDIAN
;
347 /* Try the same thing, but with little-endian */
348 magicByteBuffer
.order(ByteOrder
.LITTLE_ENDIAN
);
349 magic
= magicByteBuffer
.getInt(0);
351 if (Utils
.TSDL_MAGIC
== magic
) {
352 fDetectedByteOrder
= ByteOrder
.LITTLE_ENDIAN
;
359 private String
getMetadataPath() {
360 /* Path of metadata file = trace directory path + metadata filename */
361 if (fTrace
.getTraceDirectory() == null) {
364 return fTrace
.getTraceDirectory().getPath()
365 + Utils
.SEPARATOR
+ METADATA_FILENAME
;
369 * Reads a metadata packet from the given metadata FileChannel, do some
370 * basic validation and append the text to the StringBuffer.
372 * @param metadataFileChannel
373 * Metadata FileChannel
374 * @param metadataText
375 * StringBuffer to which the metadata text will be appended.
376 * @return A structure describing the header of the metadata packet, or null
377 * if the end of the file is reached.
378 * @throws CTFException
380 private MetadataPacketHeader
readMetadataPacket(
381 FileChannel metadataFileChannel
, StringBuffer metadataText
)
382 throws CTFException
{
383 /* Allocate a ByteBuffer for the header */
384 ByteBuffer headerByteBuffer
= ByteBuffer
.allocate(METADATA_PACKET_HEADER_SIZE
);
386 /* Read the header */
388 int nbBytesRead
= metadataFileChannel
.read(headerByteBuffer
);
390 /* Return null if EOF */
391 if (nbBytesRead
< 0) {
395 if (nbBytesRead
!= METADATA_PACKET_HEADER_SIZE
) {
396 throw new CTFException("Error reading the metadata header."); //$NON-NLS-1$
399 } catch (IOException e
) {
400 throw new CTFException("Error reading the metadata header.", e
); //$NON-NLS-1$
403 /* Set ByteBuffer's position to 0 */
404 headerByteBuffer
.position(0);
406 /* Use byte order that was detected with the magic number */
407 headerByteBuffer
.order(fDetectedByteOrder
);
409 MetadataPacketHeader header
= new MetadataPacketHeader(headerByteBuffer
);
411 /* Check TSDL magic number */
412 if (!header
.isMagicValid()) {
413 throw new CTFException("TSDL magic number does not match"); //$NON-NLS-1$
417 if (!fTrace
.uuidIsSet()) {
418 fTrace
.setUUID(header
.getUuid());
419 } else if (!fTrace
.getUUID().equals(header
.getUuid())) {
420 throw new CTFException("UUID mismatch"); //$NON-NLS-1$
423 /* Extract the text from the packet */
424 int payloadSize
= ((header
.getContentSize() / BITS_PER_BYTE
) - METADATA_PACKET_HEADER_SIZE
);
425 if (payloadSize
< 0) {
426 throw new CTFException("Invalid metadata packet payload size."); //$NON-NLS-1$
428 int skipSize
= (header
.getPacketSize() - header
.getContentSize()) / BITS_PER_BYTE
;
430 /* Read the payload + the padding in a ByteBuffer */
431 ByteBuffer payloadByteBuffer
= ByteBuffer
.allocateDirect(payloadSize
434 metadataFileChannel
.read(payloadByteBuffer
);
435 } catch (IOException e
) {
436 throw new CTFException("Error reading metadata packet payload.", e
); //$NON-NLS-1$
438 payloadByteBuffer
.rewind();
440 /* Read only the payload from the ByteBuffer into a byte array */
441 byte payloadByteArray
[] = new byte[payloadByteBuffer
.remaining()];
442 payloadByteBuffer
.get(payloadByteArray
, 0, payloadSize
);
444 /* Convert the byte array to a String */
445 String str
= new String(payloadByteArray
, 0, payloadSize
);
447 /* Append it to the existing metadata */
448 metadataText
.append(str
);
453 private static class MetadataPacketHeader
{
455 private static final int UUID_SIZE
= 16;
456 private final int fMagic
;
457 private final UUID fUuid
;
458 private final int fChecksum
;
459 private final int fContentSize
;
460 private final int fPacketSize
;
461 private final byte fCompressionScheme
;
462 private final byte fEncryptionScheme
;
463 private final byte fChecksumScheme
;
464 private final byte fCtfMajorVersion
;
465 private final byte fCtfMinorVersion
;
467 public MetadataPacketHeader(ByteBuffer headerByteBuffer
) {
468 /* Read from the ByteBuffer */
469 fMagic
= headerByteBuffer
.getInt();
470 byte[] uuidBytes
= new byte[UUID_SIZE
];
471 headerByteBuffer
.get(uuidBytes
);
472 fUuid
= Utils
.makeUUID(uuidBytes
);
473 fChecksum
= headerByteBuffer
.getInt();
474 fContentSize
= headerByteBuffer
.getInt();
475 fPacketSize
= headerByteBuffer
.getInt();
476 fCompressionScheme
= headerByteBuffer
.get();
477 fEncryptionScheme
= headerByteBuffer
.get();
478 fChecksumScheme
= headerByteBuffer
.get();
479 fCtfMajorVersion
= headerByteBuffer
.get();
480 fCtfMinorVersion
= headerByteBuffer
.get();
483 public boolean isMagicValid() {
484 return fMagic
== Utils
.TSDL_MAGIC
;
487 public UUID
getUuid() {
491 public int getContentSize() {
495 public int getPacketSize() {
500 public String
toString() {
501 /* Only for debugging, shouldn't be externalized */
502 /* Therefore it cannot be covered by test cases */
503 return "MetadataPacketHeader [magic=0x" //$NON-NLS-1$
504 + Integer
.toHexString(fMagic
) + ", uuid=" //$NON-NLS-1$
505 + fUuid
.toString() + ", checksum=" + fChecksum
//$NON-NLS-1$
506 + ", contentSize=" + fContentSize
+ ", packetSize=" //$NON-NLS-1$ //$NON-NLS-2$
507 + fPacketSize
+ ", compressionScheme=" + fCompressionScheme
//$NON-NLS-1$
508 + ", encryptionScheme=" + fEncryptionScheme
//$NON-NLS-1$
509 + ", checksumScheme=" + fChecksumScheme
//$NON-NLS-1$
510 + ", ctfMajorVersion=" + fCtfMajorVersion
//$NON-NLS-1$
511 + ", ctfMinorVersion=" + fCtfMinorVersion
+ ']'; //$NON-NLS-1$
517 * Copies the metadata file to a destination directory.
520 * the destination directory
521 * @return the path to the target file
522 * @throws IOException
523 * if an error occurred
527 public Path
copyTo(final File path
) throws IOException
{
528 Path source
= FileSystems
.getDefault().getPath(fTrace
.getTraceDirectory().getAbsolutePath(), METADATA_FILENAME
);
529 Path destPath
= FileSystems
.getDefault().getPath(path
.getAbsolutePath());
530 return Files
.copy(source
, destPath
.resolve(source
.getFileName()));