/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Helge Deller <deller@gmx.de>
 */
#ifdef __hpux
#   define _LINUX_TYPES_H
#endif
#include "common.h"
#include "load.h"

#include <stdio.h>


#ifndef NULL
#define NULL (void *)0
#endif

/*
 * gzip declarations
 */
#ifdef IPL_LOADER
# include "bootloader.h"
#else
# include <setjmp.h>
# include <string.h>
# include <stdlib.h>
# define pa_memcpy	memcpy
# define mark		gzip_mark
# define release	gzip_release
#endif

# define gzip_mark(ptr)  do { *ptr = 0UL; } while (0)
# define gzip_release(x) do { } while (0)

static void pa_free(void *ptr) { }
#define pa_malloc malloc

#define OF(args)  args
#define STATIC static

#define memzero(s, n)     memset ((s), 0, (n))

typedef unsigned char uch;
typedef unsigned short ush;
typedef unsigned long ulg;

#define WSIZE 0x8000		/* Window size must be at least 32k, */
				/* and a power of two */
static uch window[WSIZE];	/* Sliding window buffer */

static unsigned outcnt = 0;	/* bytes in output buffer */

/* gzip flag byte */
#define ASCII_FLAG   0x01	/* bit 0 set: file probably ascii text */
#define CONTINUATION 0x02	/* bit 1 set: continuation of multi-part gzip file */
#define EXTRA_FIELD  0x04	/* bit 2 set: extra field present */
#define ORIG_NAME    0x08	/* bit 3 set: original file name present */
#define COMMENT      0x10	/* bit 4 set: file comment present */
#define ENCRYPTED    0x20	/* bit 5 set: file is encrypted */
#define RESERVED     0xC0	/* bit 6,7:   reserved */

#define Assert(cond,msg)
#define Trace(x)
#define Tracev(x)
#define Tracevv(x)
#define Tracec(c,x)
#define Tracecv(c,x)

static void flush_window (void);
static void error (char *);
static long bytes_out;
static uch *output_data, *output_limit;

/* gzbuffer is the buffer in which we keep the uncompressed gzip data */
#define GZBUFFER_SIZE (4096*2)
static unsigned char gzbuffer[GZBUFFER_SIZE];
static int orig_fd; /* file descriptor for seekread */
static int blocknr;
static int gzminp;

static unsigned char get_byte(void)
{
    if (gzminp == GZBUFFER_SIZE) {
	blocknr++;
	STRUCTREAD(orig_fd, gzbuffer, blocknr * GZBUFFER_SIZE);
	gzminp = 0;
    }
    return gzbuffer[gzminp++];
}

static void unget_byte(void)
{
    if (gzminp == 0) {
	blocknr--;
	STRUCTREAD(orig_fd, gzbuffer, blocknr * GZBUFFER_SIZE);
	gzminp = GZBUFFER_SIZE;
    }
    gzminp--;
}

static jmp_buf gunzip_env;

#include "../lib/inflate.c"

static void error (char *m)
{
    printf ("\nDecompression error: %s\n", m);
    longjmp (gunzip_env, 1);
}

static void flush_window ()
{
    ulg c = crc;
    unsigned n;
    uch *in, ch;
    in = window;
    if (output_data + outcnt > output_limit)
	error ("\nuncompressed image too long - wouldn't fit into destination");
    for (n = 0; n < outcnt; n++) {
	ch = *output_data++ = *in++;
	c = crc_32_tab[((int) c ^ ch) & 0xff] ^ (c >> 8);
    }
    crc = c;

    /* show progress indicator */
    printf(".");

    bytes_out += (ulg) outcnt;
    outcnt = 0;
}

static long decompress(char *outptr, char *outptrlim)
{
    void *save_ptr;
    static int first = 1;

    gzip_mark (&save_ptr);
    if (setjmp (gunzip_env)) {
	gzip_release (&save_ptr);
	return -1;
    }
    output_data = (uch *)outptr;
    output_limit = (uch *)outptrlim;
    bytes_out = 0;
    outcnt = 0;
    crc = 0xffffffffL;
    if (first) {
        makecrc ();
        first = 0;
    }
    gunzip ();
    gzip_release (&save_ptr);
    return bytes_out;
}


#define GZIP_MAGIC0 0x1f
#define GZIP_MAGIC1 0x8b
#define GZIP_MAGIC2 0x08  /* DEFLATED */

int prepare_GZIP_loadable(int fd, struct loadable *loadable, int *wide)
{
    unsigned char flags;
    long bytes_len;
    char *orig_filename = "unknown";
    char *outptr, *outptrlim;
    int i;
    void *release_all_mallocs, *keep_unzipped;

    blocknr = 0;
    gzminp = 0;
    orig_fd = fd;
    STRUCTREAD(orig_fd, gzbuffer, 0);

    if (gzbuffer[0] != GZIP_MAGIC0 ||
	gzbuffer[1] != GZIP_MAGIC1 ||
	gzbuffer[2] != GZIP_MAGIC2)
    {
	return PREPARE_CONTINUE;
    }

    flags = gzbuffer[3];	/* general flags */
    if (flags & ORIG_NAME)
	orig_filename = (char *) &gzbuffer[10];

    mark(&release_all_mallocs);
#define MAX_GZ_LEN	(100*1024*1024)
    outptr = (char *) pa_malloc(MAX_GZ_LEN);
    mark(&keep_unzipped);

    outptrlim = outptr + MAX_GZ_LEN;
    if (0)
	printf("uncompressing %s to 0x%p", orig_filename, outptr);
    else
	printf("uncompressing Linux kernel");

    bytes_len = decompress(outptr, outptrlim);
    if (bytes_len > MAX_GZ_LEN)
	printf("\nERROR: Internal buffer MAX_GZ_LEN too small!");
    if (bytes_len < 0) {
	release(release_all_mallocs);
	return PREPARE_CONTINUE;
    }
    release(keep_unzipped);

    loadable->uncompressed_size = bytes_len;
    loadable->uncompressed_data = outptr;

    if (0)
	printf("\nGZIP compressed file size = %lu\n", bytes_len);
    else
	printf("\n");

    /* try to detect real 32 or 64bit kernel now */
    i = prepare_ELF32_loadable(fd, loadable, wide);
    if (i != PREPARE_OK)
	i = prepare_ELF64_loadable(fd, loadable, wide);

    if (i != PREPARE_OK)
	release(release_all_mallocs);

    return i;
}
