#include "gskdebuglog.h"
#include "../gskerror.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

static gboolean
read_word (GskDebugLog *log,
           guint64     *out)
{
  if (log->is_64bit)
    {
      guint64 val;
      if (fread (&val, 8, 1, log->fp) != 1)
        return FALSE;
      *out = log->little_endian ? GUINT64_FROM_LE (val) : GUINT64_FROM_BE (val);
    }
  else
    {
      guint32 val;
      if (fread (&val, 4, 1, log->fp) != 1)
        return FALSE;
      *out = log->little_endian ? GUINT32_FROM_LE (val) : GUINT32_FROM_BE (val);
    }
  return TRUE;
}

GskDebugLog       *
gsk_debug_log_open (const char *filename,
                    GError    **error)
{
  guint8 data[8];
  FILE *fp;
  GskDebugLog *log;
  guint32 magic_le = GUINT32_TO_LE (GSK_DEBUG_LOG_PACKET_INIT);
  guint32 magic_be = GUINT32_TO_BE (GSK_DEBUG_LOG_PACKET_INIT);
  guint32 zeroes = 0;
  guint64 v;
  fp = fopen (filename, "rb");
  if (fp == NULL)
    {
      g_set_error (error,
                   GSK_G_ERROR_DOMAIN,
                   gsk_error_code_from_errno (errno),
                   "error opening %s: %s", filename, g_strerror (errno));
      return NULL;
    }
  log = g_new (GskDebugLog, 1);
  log->fp = fp;

  if (fread (data, 8, 1, fp) != 1)
    {
      g_set_error (error,
                   GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_END_OF_FILE,
                   "error reading %s: too short", filename);
      return NULL;
    }
  if (memcmp (data, &magic_le, 4) == 0
   && memcmp (data + 4, "\4\3\2\1", 4) == 0)
    {
      log->little_endian = TRUE;
      log->is_64bit = FALSE;
    }
  else if (memcmp (data, &magic_be, 4) == 0
   && memcmp (data + 4, "\1\2\3\4", 4) == 0)
    {
      log->little_endian = FALSE;
      log->is_64bit = FALSE;
    }
  else if (memcmp (data, &zeroes, 4) == 0
   && memcmp (data + 4, &magic_be, 4) == 0)
    {
      log->little_endian = FALSE;
      log->is_64bit = TRUE;
    }
  else if (memcmp (data + 4, &zeroes, 4) == 0
        && memcmp (data, &magic_le, 4) == 0)
    {
      log->little_endian = TRUE;
      log->is_64bit = TRUE;
    }
  else
    {
      g_set_error (error,
                   GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_BAD_FORMAT,
                   "magic invalid: cannot determine big/little 32/64 headers");
      g_free (log);
      fclose (fp);
      return NULL;
    }

  if (log->is_64bit)
    {
      /* read the \1\2\3\4 number */
      if (fread (data, 8, 1, fp) != 1)
        g_error ("debug-log-magic: too short");
      if (log->little_endian)
        g_assert (memcmp (data, "\4\3\2\1\0\0\0\0", 8) == 0);
      else
        g_assert (memcmp (data, "\0\0\0\0\4\3\2\1", 8) == 0);
    }
  if (!read_word (log, &v))
    g_error ("error reading version");
  g_assert (v == 0); /* version 0 only */
  if (!read_word (log, &v))
    g_error ("error reading timestamp");
  log->timestamp = v;
  return log;
}

GskDebugLogPacket *gsk_debug_log_read (GskDebugLog *log)
{
  guint64 magic, data;
  guint i;
  GskDebugLogPacket *rv = g_new (GskDebugLogPacket, 1);

restart:
  if (!read_word (log, &magic))
    return NULL;
  rv->type = magic;
  switch (magic)
    {
    case GSK_DEBUG_LOG_PACKET_MAP:
      {
        GskDebugLogMap map;
        if (!read_word (log, &data))
          g_error ("read-debug-alloc-log: error reading MAP message");
        map.start = data;
        if (!read_word (log, &data))
          g_error ("read-debug-alloc-log: error reading MAP message");
        map.length = data;
        if (!read_word (log, &data))
          g_error ("read-debug-alloc-log: error reading MAP message");
        map.path = g_malloc (data + 1);
        map.path[data] = 0;
        if (fread (map.path, data, 1, log->fp) != 1)
          g_error ("read-debug-alloc-log: error reading MAP message");
        log->n_maps++;
        log->maps = g_renew (GskDebugLogMap, log->maps, log->n_maps);
        log->maps[log->n_maps-1] = map;
      }
      goto restart;
    case GSK_DEBUG_LOG_PACKET_MALLOC:
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading MALLOC message");
      rv->info.malloc.n_bytes = data;
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading MALLOC message");
      rv->info.malloc.n_contexts = data;
      rv->info.malloc.contexts = g_new (guint64, rv->info.malloc.n_contexts);
      for (i = 0; i < data; i++)
        if (!read_word (log, rv->info.malloc.contexts + i))
          g_error ("read-debug-alloc-log: error reading MALLOC message");
      if (!read_word (log, &rv->info.malloc.allocation))
        g_error ("read-debug-alloc-log: error reading MALLOC message");
      break;
    case GSK_DEBUG_LOG_PACKET_FREE:
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading FREE message");
      rv->info.free.n_bytes = data;
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading FREE message");
      rv->info.free.n_contexts = data;
      rv->info.free.contexts = g_new (guint64, rv->info.free.n_contexts);
      for (i = 0; i < data; i++)
        if (!read_word (log, rv->info.free.contexts + i))
          g_error ("read-debug-alloc-log: error reading FREE message");
      if (!read_word (log, &rv->info.free.allocation))
        g_error ("read-debug-alloc-log: error reading FREE message");
      break;
    case GSK_DEBUG_LOG_PACKET_REALLOC:
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading REALLOC message");
      rv->info.realloc.mem = data;
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading REALLOC message");
      rv->info.realloc.size = data;
      break;
    case GSK_DEBUG_LOG_PACKET_TIME:
      if (!read_word (log, &data))
        g_error ("read-debug-alloc-log: error reading TIME message");
      log->timestamp = data;
      goto restart;
    default:
      g_error ("invalid magic in debug-log");
    }
  return rv;
}

void               gsk_debug_log_packet_free (GskDebugLogPacket *packet)
{
  switch (packet->type)
    {
    case GSK_DEBUG_LOG_PACKET_MALLOC:
      g_free (packet->info.malloc.contexts);
      break;
    case GSK_DEBUG_LOG_PACKET_FREE:
      g_free (packet->info.free.contexts);
      break;
    default:
      break;
    }
  g_free (packet);
}

void
gsk_debug_log_close(GskDebugLog *log)
{
  guint i;
  for (i = 0; i < log->n_maps; i++)
    g_free (log->maps[i].path);
  g_free (log->maps);
  fclose (log->fp);
  if (log->context_cache)
    g_hash_table_destroy (log->context_cache);
  g_free (log);
}


syntax highlighted by Code2HTML, v. 0.9.1