/**************************************************************************
* Copyright  (c) 2001 by Acunia N.V. All rights reserved.                 *
*                                                                         *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other       *
* interests in the software remain the property of Acunia N.V. and its    *
* licensors, if any.                                                      *
*                                                                         *
* This software may only be used in accordance with the corresponding     *
* license agreement. Any unauthorized use, duplication, transmission,     *
*  distribution or disclosure of this software is expressly forbidden.    *
*                                                                         *
* This Copyright notice may not be removed or modified without prior      *
* written consent of Acunia N.V.                                          *
*                                                                         *
* Acunia N.V. reserves the right to modify this software without notice.  *
*                                                                         *
*   Acunia N.V.                                                           *
*   Vanden Tymplestraat 35      info@acunia.com                           *
*   3000 Leuven                 http://www.acunia.com                     *
*   Belgium - EUROPE                                                      *
**************************************************************************/

/*
** $Id: jamjar.c,v 1.9 2003/01/21 08:39:00 gray Exp $
*/

#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <errno.h>
#include <dirent.h>

#include "jamjar.h"
#include "class.h"
#include "zip.h"
#include "resource.h"
#include "argument.h"
#include "meta.h"

j_Options Options;
j_options options = &Options;

typedef struct Block * block;

typedef struct Block {
  block next;
  block previous;
  char * function;
  size_t line;
  size_t size;
  unsigned int * back;
  unsigned int front;
  unsigned char space[0];
} Block;

static Block List = {
  NULL,               // next
  NULL,               // previous
  "(first)",          // function name
  0,                  // line
  0,                  // size of block
  NULL,               // back pointer
  0x00000000,         // front, no space in this block
};

static block list = &List;

#ifdef DEBUG
void jj_check(void) {

  block c;
  int stop = 0;
  int i;
  unsigned char *string;

  for (c = list->next; c != list; c = c->next) {
    if (c->front != 0xcafebabe) {
      printf("A front = 0x%08x\n", c->front);
      stop = 1;
    }

    if (*c->back != 0xcafebabe) {
      printf("A back = 0x%08x\n", *c->back);
      string = (char *)c->back;
      for (i = 0; i < 4; i++) {
        printf("--> '%c'\n", string[i]);
      }
      printf("previous block allocated at %s:%d\n", c->previous->function, c->previous->line);
      printf("    this block allocated at %s:%d\n", c->function, c->line);
      printf("    next block allocated at %d\n", c->next->line);
      printf("    next block allocated at %s\n", c->next->function);
      stop = 1;
    }
  }

  if (stop) {
    exit(1);
  }
  
}

void * _jj_calloc(const char *f, size_t l, size_t num, size_t size) {

  block b;
  size_t s = num * size;

  b = calloc(1, s + sizeof(Block) + sizeof(unsigned int));
  
  list_insert(list, b);
  b->function = (char *)f;
  b->line = l;
  b->size = s;
  b->front = 0xcafebabe;
  b->back = (unsigned int *)(b->space + s);
  *b->back = 0xcafebabe;

//  printf("%s %d: allocated %d bytes\n", f, l, s);

  jj_check();
  
  return b->space;
  
}

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 

void _jj_free(const char *f, size_t l, void * u) {

  block b;
  int stop = 0;
  char * string;
  int i;
  
  b = u - offsetof(Block, space);

//  printf("%s %d: freeing block allocated at %s %d.\n", f, l, b->function, b->line);

  if (b->front != 0xcafebabe) {
    printf("F front = 0x%08x\n", b->front);
    stop = 1;
  }

  if (*b->back != 0xcafebabe) {
    printf("F back = 0x%08x\n", *b->back);
    string = (char *)b->back;
    for (i = 0; i < 4; i++) {
      printf("--> '%c'\n", string[i]);
    }
    stop = 1;
  }

  if (stop) {
    exit(0);
  }

  list_remove(b);

  jj_check();
  
  free(b);
  
}

void * _jj_realloc(const char *f, size_t l, void * b, size_t size) {

  unsigned char * new;

  jj_check();
  new = _jj_calloc(f, l, 1, size);
  memcpy(new, b, size);
  _jj_free(f, l, b);
  
  return new;
    
}
#endif

void * z_calloc(void * x, size_t num, size_t size) {
  return jj_calloc(num, size);
}

void z_free(void * x, void * b) {
  jj_free(b);
}

static char * logbuffer = NULL;

#define LOGSIZE (1024)

void logmsg(int level, const char *fmt, ...) {

  va_list ap;

  if (logbuffer == NULL) {
    logbuffer = jj_calloc(1, LOGSIZE);
  }

  if (options->verbosity >= level || (level == 0) || (level == 1)) {  
    memset(logbuffer, 0x00, LOGSIZE);
    va_start(ap, fmt);
    vsprintf(logbuffer, fmt, ap);
    va_end(ap);
//  write(1, logbuffer, strlen(logbuffer));
    printf("%s", logbuffer);
  }
  
  if (level == 0) {
    printf("jamjar stopped.\n");
    exit(1);
  }
  
}

void it_check(j_res res, void * arg) {

  int * count = (int *)arg;
  
  *count += 1;

}

/*
** Work around jikes bug... jikes version 1.14 and lower don't generate an InnerClass attribute on
** anonymous inner classes. We painfully (and slowly) scan directories for classes that have a name
** that shows up like "<path>/<basename>$<numeral>.class" on existing classes.
*/

#define ACBUFSIZE (1024)

static void fix_jikes_anonymous(j_res res, void * arg) {

  int * count = (int *)arg;
  char * location;
  char * start;
  j_res new;
  j_res found;
  char path[ACBUFSIZE];
  char z_name[ACBUFSIZE];

  char an_base[ACBUFSIZE];
  int an_base_len;
  DIR * dirp;
  struct dirent *de;

  /*
  ** When a class has no r_name yet, it's an inner class we find during reading of
  ** the class files. No need to go further...
  */
  
  if (res->r_name == NULL) {
    return;
  }
  
  location = strrchr(res->r_name, '.');
  if (location && isSet(res->flags, RES_CLASS)) {
    memset(an_base, 0x00, ACBUFSIZE);
    memset(path, 0x00, ACBUFSIZE);
    start = strrchr(res->r_name, '/');
    if (start) {
      start += 1;
    }
    else {
      start = res->r_name;
    }
    memcpy(an_base, start, location - start);
    strncat(an_base, "$", ACBUFSIZE-strlen(an_base));
    an_base_len = strlen(an_base);

    location = strrchr(res->r_name, '/');
    if (location) {
      memcpy(path, res->r_name, location - res->r_name + 1);
    }
    else {
      strncat(path, "./", ACBUFSIZE-strlen(path));
    }
    dirp = opendir( path );
    while ( (de = readdir(dirp) ) != NULL ) {
      if ( strncmp(de->d_name, an_base, an_base_len) != 0) {
         continue;
      }
      memset(z_name, 0x00, ACBUFSIZE);
      location = strrchr(res->z_name, '/');
      if (location) {
        memcpy(z_name, res->z_name, location - res->z_name + 1);
      }
      strncat(z_name, de->d_name, ACBUFSIZE-strlen(z_name));
      new = jj_calloc(1, sizeof(j_Res));
      new->z_name = z_name;
      res_hash(new);
      found = res_exists(new);
      if (found) {
        jj_free(new);
      }
      else {
        *count += 1;
        new->outer = res->class;
        new->r_name = jj_calloc(strlen(path) + strlen(de->d_name) + 1, sizeof(char));
        strcat(new->r_name, path);    /* no need for strncat, see jj_calloc */
        strcat(new->r_name, de->d_name); /* no need for strncat*/
        logmsg(3, "Recycling anonymous inner class lost by jikes: zname = '%s' file = '%s'\n", z_name, new->r_name);
        new->z_name = jj_calloc(strlen(z_name) + 1, sizeof(char));
        setFlag(new->flags, RES_CLASS);
        strcpy(new->z_name, z_name); /* no need for strncpy */
        res_add(new);
      }
    }
    closedir(dirp);
  }

}

static void it_inner(j_res res, void * arg) {

  int * count = (int *)arg;
  char * name;
  char * path;
  int alloc_path = 0;
  char * base;
  int alloc_base = 0;
  char * location;
  struct stat fs;
  int status;
  j_class class;

  /*
  ** If the resource refers to an unresolved inner class, find the path from the parent and
  ** the base from the inner class and construct a filename for the inner class resource.
  ** Then get some information about size and time of this class and read it in.
  */

  if (isSet(res->flags, RES_CLASS) && res->class == NULL) {  

    name = res->outer->res->r_name;
    location = strrchr(name, '/');
    if (location) {
      path = jj_calloc(location - name + 1, sizeof(char));
      memcpy(path, name, location - name);
      alloc_path = 1;
    }
    else {
      path = "";
    }

    name = res->z_name;
    location = strrchr(name, '/');
    if (location) {
      base = jj_calloc(strlen(name) - (location - name) + 1, sizeof(char));
      memcpy(base, location, strlen(name) - (location - name));
      alloc_base = 1;
    }
    else {
      base = res->z_name;
    }
    
    res->r_name = jj_calloc(strlen(path) + strlen(base) + 1, sizeof(char));
    strcpy(res->r_name, path); /* no need for strncpy, see jj_calloc above */
    strcat(res->r_name, base); /* ditto */
    if (alloc_path) {
      jj_free(path);
    }
    if (alloc_base) {
      jj_free(base);
    }

    status = stat(res->r_name, &fs);
    if (status == 0) {
      res->u_size = fs.st_size;
      res->time = fs.st_mtime;
      class = class_read(res->r_name, res->u_size);
      if (class) {
        res->class = class;
        class->res = res;
        res->u_data = class->data;
        setFlag(res->flags, RES_INNER);
      }
    }
    else {
      logmsg(1, "warning: did not find inner class file '%s'.\n", res->r_name);
    }

    *count += 1;

  }

}

static void it_compress(j_res res, void * arg) {

  if (res->u_data && res->u_size) {
    if (res->arg && isSet(res->arg->flags, ARG_STORE)) {
      res->c_data = res->u_data;
      res->c_size = res->u_size;
    }
    else {
      logmsg(2, "Compressing '%s' %d bytes ...\n", res->r_name, res->u_size);
      res_compress(res);
      logmsg(2, "... done, compressed to %d bytes.\n", res->c_size);
    }
    if (res->c_data) {
      setFlag(res->flags, RES_WRITE);
    }
  }
  
}

static void it_store(j_res res, void * arg) {

  if (res->u_data && res->u_size) {
    res->c_data = res->u_data;
    res->c_size = res->u_size;
    setFlag(res->flags, RES_WRITE);
  }
  
}

static void write_local(j_res res, void * arg) {

  j_out out = (j_out)arg;
  int pc = 0;

  if (isSet(res->flags, RES_WRITE)) {
    if (res->u_size > res->c_size) {
      pc = 100 - ((res->u_size * 100 - res->c_size * 100) / res->u_size);
    }
    logmsg(1, "adding: %4d in = %6d  out = %6d  (%s %2d%%) %s\n", out->count + 1, res->u_size, res->c_size, (res->u_size == res->c_size) ? "stored  " : "deflated", pc, res->z_name);
    res->offset = out->offset;
    out->offset += res_write_local(res);
    out->offset += write(options->out, res->c_data, res->c_size);
    out->count += 1;
  }

}

static void write_central(j_res res, void * arg) {

  j_out out = (j_out)arg;

  if (isSet(res->flags, RES_WRITE)) {
    out->dir_size += res_write_central(res);
  }

}

int res_jar_sort(const j_res * res1r, const j_res * res2r) {

  j_res res1 = *res1r;
  j_res res2 = *res2r;
  char * d1;
  char * d2;

  /*
  ** META-INF entries have highest precedence .. 
  */
  
  d1 = strstr(res1->z_name, "META-INF");
  d2 = strstr(res2->z_name, "META-INF");

  if (d1) {
    if (d2) {
      return strcmp(res1->z_name, res2->z_name);
    }
    else {
      return -1;
    }
  }
  
  if (d2) {
    return 1;
  }

  /*
  ** ... then come the directories that take precedence ...
  */
  
  d1 = strchr(res1->z_name, '/');
  d2 = strchr(res2->z_name, '/');

  if (d1) {
    if (d2) {
      return strcmp(res1->z_name, res2->z_name);
    }
    else {
      return -1;
    }
  }
  
  if (d2) {
    return 1;
  }

  /*
  ** ... otherwise, normal comparison will do ...
  */
    
  return strcmp(res1->z_name, res2->z_name);
  
}

void jar_write(int count) {

  j_Entry Entries;
  j_entry entries = &Entries;
  j_res res;
  j_res res_write;
  j_zip zip = NULL;
  j_Out Out;
  j_out out = &Out;

  logmsg(1, "Writing %d entries.\n", count);

  /*
  ** When the OPT_STORE flag is not set, compress all entries in the hashtable.
  */

  list_init(entries);
  if (isNotSet(options->flags, OPT_STORE)) {
    logmsg(2, "compressing all resources...\n");
    res_iterate(it_compress, entries);
    logmsg(2, "done.\n");
  }
  else {
    logmsg(2, "preparing to store all resources...\n");
    res_iterate(it_store, entries);
    logmsg(2, "done.\n");
  }

  if (isSet(options->flags, OPT_STDOUT)) {
    logmsg(2, "output is standard output.\n");
  }
  else {
    logmsg(2, "output is '%s'\n", options->jar->file);
  }

  /*
  ** Read in the existing jarfile, if it exists and if we don't have to create one.
  */
  
  if (options->jar) {
    if (isSet(options->jar->flags, ARG_EXISTS)) {
      if (isSet(options->flags, OPT_UPDATE)) {
        zip = zip_read(options->jar, OUTPUT_JAR);
      }
    }
  }

  /* 
  ** Write back the resources to the output jar. First write the local
  ** directory entries and then write the central directory entries and
  ** the final trailing information.
  */

  if (options->jar) {
    options->out = open(options->jar->file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
  }
  else {
    options->out = 1;
  }

  res_write = res_sort(res_jar_sort);
  if (isNotSet(options->flags, OPT_NO_MAN)) {
    res = manifest_create(zip);
    res->sorted = res_write;
    res_write = res;
  }

  memset(out, 0x00, sizeof(j_Out));
  logmsg(2, "writing local headers...\n");
  for (res = res_write; res; res = res->sorted) {
    write_local(res, out);
  }
  logmsg(2, "done;\nwriting central directory headers...\n");
  for (res = res_write; res; res = res->sorted) {
    write_central(res, out);
  }
  logmsg(2, "done;\nwriting zip trailer...");
  zip_write_trailer(out);
  logmsg(2, "done;\n");

  if (options->jar) {
    close(options->out);
  }

  logmsg(1, "written %d files to '%s'\n", out->count, options->jar ? options->jar->file : "standard out");

  compress_cleanup();

}

int main(int argc, char * argv[]) {

  j_arg arg;
  j_res res;
  j_class class;
  int loops;
  int fd;
  int pc = 0;
  int count;
  int list_some = 0;
  j_zip listzip;
  j_entry entry;

  list_init(list);

  args_read(argc, argv);
  
  /*
  ** Loop over argument list, when the file exists and needs to be processed:
  **
  ** a) when the argument is a class file and it exists, it is parsed and its class structure
  **    is passed to the res_arg call.
  **
  ** b) for non class files, we make an 'other' entry.
  **
  ** In any case, we record the resource in the hashtable.
  */

  logmsg(2, "reading argument files...\n");
  for (arg = options->args; arg; arg = arg->next) {
    if (isSet(arg->flags, ARG_EXISTS)) {
      if (isSet(arg->flags, ARG_CLASS)) {
        class = class_read(arg->file, arg->size);
        if (class) {
          res = res_class(class->name);
          res = res_find(res);
          res_attach_class(res, arg, class);
        }
      }
      else {
        res = res_arg(arg);
        res = res_find(res);
        res_attach_other(res, arg);
      }
    }
  }

  /*
  ** Workaround for a jikes 1.14 and lower versions bug. In jikes, the anonymous inner
  ** classes don't get an InnerClasses attribute. We scan the directory for any names
  ** that have a $<numeral>.class component. We add these to.
  */

  count = 0;
  logmsg(2, "recycling jikes lost anonymous inner classes...\n");
  res_iterate(fix_jikes_anonymous, &count);
  logmsg(2, "done; recycled %d anonymous inner classes that jikes forget about....\n", count);

  /*
  ** Iterate over the hashtable and attach a correct filename to the inner class
  ** resources. In this iterator, the inner classes will be read in. We do this for
  ** as long as there are more inner classes found. The 'count' argument is set to
  ** the number of unattached inner classes found.
  */

  logmsg(2, "resolving and reading inner classes...\n");
  loops = -1;
  do {
    count = 0;
    res_iterate(it_inner, &count);
    loops += 1;
  } while (count != 0);

  logmsg(2, "done; looped %d times to read inner classes...\n", loops);

  /*
  ** Read in the entries data that are not class files (other jars, au, png, gif, ...)
  */

  logmsg(2, "reading in other passed non class files...\n");
  for (arg = options->args; arg; arg = arg->next) {
    res = arg->res;
    if (res) {
      res->u_data = jj_calloc(res->u_size, sizeof(unsigned char));
      fd = open(arg->file, O_RDONLY);
      if (fd == -1) {
        logmsg(11, "could not open '%s' (%s)\n", arg->file, strerror(errno));
      }
      else {
        read(fd, res->u_data, res->u_size);
        close(fd);
        logmsg(2, "read %d bytes of data from '%s'\n", res->u_size, arg->file);
        if (isSet(res->arg->flags, ARG_IN_JAR)) {
          if (isSet(options->flags, OPT_STOREJARS) && zip_is_compressed(res->u_data)) {
            setFlag(res->arg->flags, ARG_STORE);
          }
        }
      }
    }
  }
  
  /*
  ** Do a final check before proceeding. If the number of files to be written is not 0, call jar_write.
  */

  count = 0;
  res_iterate(it_check, &count);
  
  if (count != 0) {
    jar_write(count);
  }

  for (arg = options->args; arg; arg = arg->next) {
    if (isSet(arg->flags, ARG_LIST)) {
      list_some = 1;
      break;
    }
  }

  if (list_some || isSet(options->flags, OPT_LIST)) {
    listzip = zip_read(options->jar, LIST_JAR);
    if (isSet(options->flags, OPT_LIST)) {
      for (entry = listzip->entries; entry; entry = entry->next) {
        res = entry->res;
        if (options->verbosity == 0) {
          logmsg(1, "%s\n", res->z_name);
        }
        else if (options->verbosity == 1) {
          logmsg(1, "%6d %s %s\n", res->u_size, res_timefmt(res), res->z_name);
        }
        else {
          if (res->u_size > res->c_size) {
            pc = 100 - ((res->u_size * 100 - res->c_size * 100) / res->u_size);
          }
          logmsg(1, "%6d %6d %2d%% %s %s\n", res->u_size, res->c_size, pc, res_timefmt(res), res->z_name);
        }
      }
    }
    else {
      for (arg = options->args; arg; arg = arg->next) {
        if (isSet(arg->flags, ARG_LIST)) {
          // Only list some files (not yet)
        }
      }
    }
  }

  /*
  ** Print out any warnings of files that could not be found back.
  */
  
  for (arg = options->args; arg; arg = arg->next) {
    if (isNotSet(arg->flags, ARG_EXISTS)) {
      logmsg(1, "warning: file '%s' doesn't exist.\n", arg->file);
    }
  }

  return 0;
   
}
