/*
 * package.c
 *
 * Copyright (C) 2003 Bastian Blank <waldi@debian.org>
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * $LastChangedBy$
 * $LastChangedDate$
 * $LastChangedRevision$
 */

#include <config.h>

#include <ar.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <bzlib.h>
#include <lzma.h>
#include <zlib.h>

#include "download.h"
#include "execute.h"
#include "log.h"
#include "package.h"
#include "target.h"

const char *package_get_local_filename (di_package *package)
{
  const char *tmp = strrchr (package->filename, '/');
  if (tmp)
    return tmp + 1;
  return package->filename;
}

static long package_extract_self_parse_header_length (const char *inh, size_t len)
{
  char lintbuf[15];
  long r;
  char *endp;

  if (memchr (inh, 0, len))
    return -1;
  memcpy (lintbuf, inh, len);
  lintbuf[len] = ' ';
  *strchr(lintbuf,' ') = 0;
  r = strtoul(lintbuf, &endp, 10);
  if (*endp)
    return -1;
  return r;
}

static int package_extract_self_bz2 (FILE *source, size_t len, const char *command)
{
  int ret = 0;
  bz_stream stream;
  char in[1024*1024], out[1024*1024];
  FILE *dest = NULL;

  stream.bzalloc = NULL;
  stream.bzfree = NULL;
  stream.opaque = NULL;
  ret = BZ2_bzDecompressInit (&stream, 0, 0);
  if (ret != BZ_OK)
    goto out;

  log_text (DI_LOG_LEVEL_DEBUG, "Execute \"%s\" with bzip2 input", command);

  dest = popen (command, "w");
  if (!dest)
  { ret = -1; goto out; }

  do
  {
    size_t toread = len;
    if (toread > sizeof in) { toread = sizeof in; }

    stream.avail_in = fread(in, 1, toread, source);
    stream.next_in = in;
    len -= stream.avail_in;
    if (ferror(source))
    { ret = -1; goto out; }
    if (stream.avail_in == 0)
      goto out;

    do
    {
      stream.avail_out = sizeof out;
      stream.next_out = out;

      ret = BZ2_bzDecompress (&stream);
      if (ret != BZ_OK && ret != BZ_STREAM_END)
        goto out;

      size_t towrite = sizeof out - stream.avail_out;
      if (fwrite (out, 1, towrite, dest) != towrite || ferror(dest))
      { ret = -1; goto out; }
    }
    while (stream.avail_out == 0);
  }
  while (ret != BZ_STREAM_END);

out:
  BZ2_bzDecompressEnd (&stream);
  if (fclose (dest))
    ret = -1;
  return ret != BZ_STREAM_END;
}

static int package_extract_self_gz (FILE *source, size_t len, const char *command)
{
  int ret = 0;
  z_stream stream;
  unsigned char in[1024*1024], out[1024*1024];
  FILE *dest = NULL;

  stream.zalloc = NULL;
  stream.zfree = NULL;
  stream.opaque = NULL;
  stream.avail_in = 0;
  stream.next_in = NULL;
  ret = inflateInit2 (&stream, MAX_WBITS + 32);
  if (ret != Z_OK)
    goto out;

  log_text (DI_LOG_LEVEL_DEBUG, "Execute \"%s\" with gzip input", command);

  dest = popen (command, "w");
  if (!dest)
  { ret = -1; goto out; }

  do
  {
    size_t toread = len - stream.total_in;
    if (toread > sizeof in) { toread = sizeof in; }

    stream.avail_in = fread(in, 1, toread, source);
    stream.next_in = in;
    if (ferror(source))
    { ret = -1; goto out; }
    if (stream.avail_in == 0)
      goto out;

    do
    {
      stream.avail_out = sizeof out;
      stream.next_out = out;

      ret = inflate(&stream, Z_NO_FLUSH);
      if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
        goto out;

      size_t towrite = sizeof out - stream.avail_out;
      if (fwrite (out, 1, towrite, dest) != towrite || ferror(dest))
      { ret = -1; goto out; }
    }
    while (stream.avail_out == 0);
  }
  while (ret != Z_STREAM_END);

out:
  inflateEnd(&stream);
  if (dest && fclose (dest))
    ret = -1;
  return ret != Z_STREAM_END;
}

static int package_extract_self_xz (FILE *source, size_t len, const char *command)
{
  int ret = 0;
  lzma_stream stream = LZMA_STREAM_INIT;
  unsigned char in[1024*1024], out[1024*1024];
  FILE *dest = NULL;

  ret = lzma_stream_decoder(&stream, 128*1024*1024, 0);
  if (ret != LZMA_OK)
    goto out;

  log_text (DI_LOG_LEVEL_DEBUG, "Execute \"%s\" with xz input", command);

  dest = popen (command, "w");
  if (!dest)
  { ret = -1; goto out; }

  do
  {
    size_t toread = len;
    if (toread > sizeof in) { toread = sizeof in; }

    stream.avail_in = fread(in, 1, toread, source);
    stream.next_in = in;
    len -= stream.avail_in;
    if (ferror(source))
    { ret = -1; goto out; }
    if (stream.avail_in == 0)
      goto out;

    do
    {
      stream.avail_out = sizeof out;
      stream.next_out = out;

      ret = lzma_code (&stream, LZMA_RUN);
      if (ret != LZMA_OK && ret != LZMA_STREAM_END)
        goto out;

      size_t towrite = sizeof out - stream.avail_out;
      if (fwrite (out, 1, towrite, dest) != towrite || ferror(dest))
      { ret = -1; goto out; }
    }
    while (stream.avail_out == 0);
  }
  while (ret != LZMA_STREAM_END);

out:
  lzma_end (&stream);
  if (fclose (dest))
    ret = -1;
  return ret != LZMA_STREAM_END;
}

static int package_extract_self (const char *file, const char *command)
{
  char *infobuf = NULL, versionbuf[40];
  ssize_t memberlen;
  FILE *ar = NULL;
  char *cur;
  struct ar_hdr arh;
  int header_done = 0;
  int ret = -1;

  ar = fopen (file, "rb");
  if (ar &&
      fgets (versionbuf, sizeof (versionbuf), ar) &&
      !strcmp (versionbuf, ARMAG))
  {
    while (1)
    {
      if (fread (&arh, 1, sizeof(arh), ar) != sizeof (arh))
        break;
      if (memcmp (arh.ar_fmag, ARFMAG, sizeof (arh.ar_fmag)))
        break;
      memberlen = package_extract_self_parse_header_length (arh.ar_size, sizeof (arh.ar_size));
      if (memberlen < 0)
        break;

      if (!header_done)
      {
        if (memcmp (arh.ar_name,"debian-binary   ",sizeof (arh.ar_name)) &&
            memcmp (arh.ar_name,"debian-binary/  ",sizeof (arh.ar_name)))
          break;
        infobuf = di_new (char, memberlen + 1);
        if (fread (infobuf, 1, memberlen + (memberlen & 1), ar) == (size_t) (memberlen + (memberlen & 1)))
        infobuf[memberlen]= 0;
        cur = strchr (infobuf, '\n');
        if (!cur)
          break;
        *cur = 0;
        cur = strchr (infobuf, '.');
        if (!cur)
          break;
        *cur = 0;
        if (strcmp (infobuf, "2"))
          break;
        *cur = '.';
        strncpy (versionbuf, infobuf, sizeof(versionbuf));
        versionbuf[sizeof (versionbuf) - 1]= 0;
        header_done = 1;
      }
      else if (!memcmp (arh.ar_name,"data.tar.bz2    ",sizeof (arh.ar_name)) ||
               !memcmp (arh.ar_name,"data.tar.bz2/   ",sizeof (arh.ar_name)))
      {
        ret = package_extract_self_bz2 (ar, memberlen, command);
        break;
      }
      else if (!memcmp (arh.ar_name,"data.tar.gz     ",sizeof (arh.ar_name)) ||
               !memcmp (arh.ar_name,"data.tar.gz/    ",sizeof (arh.ar_name)))
      {
        ret = package_extract_self_gz (ar, memberlen, command);
        break;
      }
      else if (!memcmp (arh.ar_name,"data.tar.xz     ",sizeof (arh.ar_name)) ||
               !memcmp (arh.ar_name,"data.tar.xz/    ",sizeof (arh.ar_name)))
      {
        ret = package_extract_self_xz (ar, memberlen, command);
        break;
      }
      else
        fseek (ar, memberlen + (memberlen & 1), SEEK_CUR);
    }
  }

  di_free (infobuf);
  if (ar)
    fclose (ar);

  log_text (DI_LOG_LEVEL_DEBUG, "Return code: %d", ret);

  return ret;
}

int package_extract (di_package *package)
{
  char buf_file[PATH_MAX];
  char buf[PATH_MAX];

  build_target_deb (buf_file, PATH_MAX, package_get_local_filename (package));
  snprintf (buf, PATH_MAX, "tar -x -C %s -f -", target_root);
  return package_extract_self (buf_file, buf);
}

