#!/usr/bin/perl

# xencov_split - split coverage information from Xen
#
# Copyright (C) 2013  - Citrix Systems
# -----
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <http://www.gnu.org/licenses/>.

use strict;
use File::Path qw(mkpath);

# some magic constants
my $magic = 0x67636461;
my $ctrBase = 0x01a10000;

my $xenMagic = 0x58544346;	# file header
my $xenTagFunc = 0x58544366;	# functions tag
my $xenTagCount0 = 0x58544330;	# counter 0 tag
my $xenTagEnd = 0x5854432e;	# end file

# open input file
if ($ARGV[0]) {
	my $fn = $ARGV[0];
	open(IN, '<', $fn) or die "opening file \"$fn\"";
} else {
	open(IN, '<&STDIN') or die "redirecting input";
}

my $pos = 0;

sub getRaw($)
{
    my $l = shift;
    die 'got no data to read' if $l < 0;
    my $res = '';
    do {
        my $data;
        my $r = read(IN, $data, $l);
        die "error $! reading data from input at position $pos" if !defined($r);
        die "unexpected end of file at position $pos" if !$r;
        $l -= $r;
        $pos += $r;
        $res .= $data;
    } while ($l > 0);
    return $res;
}

sub get32()
{
    return unpack('V', getRaw(4));
}

sub get64()
{
    # This is returned as raw data as some Perl version could not
    # support 64 bit integer
    # This is ok for little endian machines
    return getRaw(8);
}

sub align()
{
    my $l = $pos & 7;
    getRaw(8-$l) if $l;
}

# read a string prefixed by length
sub getS()
{
    my $l = get32();
    my $res = getRaw($l);
    align();
    return $res;
}

sub parseFunctions($)
{
    my $numCounters = shift;
    my $num = get32();

    my @funcs;
    for my $n (1..$num) {
        my @data;
        my $ident = get32();
        my $checksum = get32();
        for my $n (1..$numCounters) {
            push @data, get32(); # number of counters for a type
        }
        push @funcs, [$ident, $checksum, \@data];
    }
    align();
    return @funcs;
}

sub parseCounters($)
{
    my $tag = shift;
    die sprintf("wrong tag 0x%08x pos $pos (0x%08x)", $tag, $pos) if $tag < $xenTagCount0;
    $tag -= $xenTagCount0;
    die sprintf('wrong tag 0x%08x', $tag) if $tag > 5;
    my $data = '';

    my $num = get32();
    for my $n (1..$num) {
        $data .= get64();
    }
    align();
    return [$tag, $data];
}


sub parseFile()
{
    my $ver = get32();
    my $stamp = get32();
    my $fn = getS();
    align();

    my $numCounters;

    print "got file $fn\n";
    die if $fn !~ m,^(/.*?)[^/]+\.gcda$,;
    mkpath(".$1");
    open(OUT, '>', ".$fn") or die;

    print OUT pack('VVV', $magic, $ver, $stamp);

    # read counters of file
    my @ctrs;
    my @funcs;
    my $tag;
    for (;;) {
        $tag = get32();
        last if ($tag == $xenMagic || $tag == $xenTagEnd);
        if ($tag == $xenTagFunc) {
            die if scalar(@funcs);
            @funcs = parseFunctions(scalar(@ctrs));
            next;
        }

        # must be a counter
        push @ctrs, parseCounters($tag);
        ++$numCounters;
    }

    # print all functions
    for my $f (@funcs) {
        # tag tag_len ident checksum
        print OUT pack('VVVV', 0x01000000, 2, $f->[0], $f->[1]);
        # all counts
        my $n = 0;
        for my $c (@{$f->[2]}) {
            my ($type, $data) = @{$ctrs[$n]};
            print OUT pack('VV', $ctrBase + 0x20000 * $type, $c*2);
            die "--$c--$type--$data--" if length($data) < $c * 8;
            print OUT substr($data, 0, $c * 8);
            $ctrs[$n] = [$type, substr($data, $c * 8)];
            ++$n;
        }
    }
    close(OUT);

    return $tag;
}

my $tag = get32();
die 'no coverage or wrong file format' if $tag != $xenMagic;
for (;;) {
    if ($tag == $xenMagic) {
        $tag = parseFile();
    } elsif ($tag == $xenTagEnd) {
        last;
    } else {
        die "wrong tag $tag";
    }
}
