/* Copyright (C) 2001-2006 artofcode LLC.
   All Rights Reserved.
  
  This file is part of GNU ghostscript

  GNU ghostscript is free software; you can redistribute it and/or
  modify it under the terms of the version 2 of the GNU General Public
  License as published by the Free Software Foundation.

  GNU ghostscript 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
  ghostscript; see the file COPYING. If not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

*/

/* $Id: mkromfs.c,v 1.3 2007/08/01 14:26:40 jemarch Exp $ */
/* Generate source data for the %rom% IODevice */
/*
 * For reasons of convenience and footprint reduction, the various postscript
 * source files, resources and fonts required by Ghostscript can be compiled
 * into the executable.
 *
 * This file takes a set of directories, and creates a compressed filesystem
 * image that can be compiled into the executable as static data and accessed
 * through the %rom% iodevice prefix
 */
/*
 *	Usage: mkromfs [-o outputfile] [options ...] paths
 *	    options and paths can be interspersed and are processed in order
 *	    options:
 *		-o outputfile	default: obj/gsromfs.c if this option present, must be first.
 *		-P prefix	use prefix to find path. prefix not included in %rom%
 *		-X path		exclude the path from further processing.
 *              -d string       directory in %rom file system (really just a prefix string on filename)
 *		-c		compression on
 *		-b		compression off (binary).
 *
 *	    Note: The tail of any path encountered will be tested so .svn on the -X
 *		  list will exclude that path in all subsequent paths enumerated.
 */ 

#include "stdpre.h"
#include "stdint_.h"
#include "gsiorom.h"
#include "gsmemret.h" /* for gs_memory_type_ptr_t */
#include "gsmalloc.h"
#include "gsstype.h"
#include "gp.h" 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "time_.h"

#include <zlib.h>

/*
 * The rom file system is an array of pointers to nodes, terminated by a NULL
 */
/*
 * in memory structure of each node is:
 *
 *	length_of_uncompressed_file		[31-bit big-endian]
 *						high bit is compression flag 
 *	data_block_struct[]
 *	padded_file_name (char *)	includes as least one terminating <nul>
 *	padded_data_blocks
 */
/*
 *	data_block_struct:
 *	    data_length			(not including pad)	[32-bit big-endian]
 *	    data_block_offset		(start of each block)	[32-bit big-endian]
 */

typedef struct romfs_inode_s {
    unsigned long length;		/* blocks is (length+ROMFS_BLOCKSIZE-1)/ROMFS_BLOCKSIZE */
    char *name;				/* nul terminated */
    unsigned long *data_lengths;	/* this could be short if ROMFS_BLOCKSIZE */
					/* is < 64k, but the cost is small to use int */
    unsigned char **data;
} romfs_inode;

typedef struct Xlist_element_s {
	void *next;
	char *path;
    } Xlist_element;

byte *minimal_alloc_bytes(gs_memory_t * mem, uint size, client_name_t cname);
byte *minimal_alloc_byte_array(gs_memory_t * mem, uint num_elements,
			     uint elt_size, client_name_t cname);
void *minimal_alloc_struct(gs_memory_t * mem, gs_memory_type_ptr_t pstype,
	       client_name_t cname);
void minimal_free_object(gs_memory_t * mem, void * data, client_name_t cname);
void minimal_free_string(gs_memory_t * mem, byte * data, uint nbytes, client_name_t cname);

/*******************************************************************************
 * The following is a REALLY minimal gs_memory_t for use by the gp_ functions
 *******************************************************************************/
byte *
minimal_alloc_bytes(gs_memory_t * mem, uint size, client_name_t cname)
{
    return malloc(size);
}

byte *
minimal_alloc_byte_array(gs_memory_t * mem, uint num_elements,
			     uint elt_size, client_name_t cname)
{
    return malloc(num_elements * elt_size);
}

void *
minimal_alloc_struct(gs_memory_t * mem, gs_memory_type_ptr_t pstype,
	       client_name_t cname)
{
    return malloc(pstype->ssize);
}

void
minimal_free_object(gs_memory_t * mem, void * data, client_name_t cname)
{
    free(data);
    return;
}

void
minimal_free_string(gs_memory_t * mem, byte * data, uint nbytes, client_name_t cname)
{
    free(data);
    return;
}

void basic_enum_ptrs(void);
void basic_reloc_ptrs(void);

void basic_enum_ptrs() {
    printf("basic_enum_ptrs is only called by a GC. Abort.\n");
    exit(1);
}

void basic_reloc_ptrs() {
    printf("basic_reloc_ptrs is only called by a GC. Abort.\n");
    exit(1);
}


const gs_malloc_memory_t minimal_memory = {
    (gs_memory_t *)&minimal_memory, /* stable */
    { minimal_alloc_bytes, /* alloc_bytes_immovable */
      NULL, /* resize_object */
      minimal_free_object, /* free_object */
      NULL, /* stable */
      NULL, /* status */
      NULL, /* free_all */
      NULL, /* consolidate_free */
      minimal_alloc_bytes, /* alloc_bytes */ 
      minimal_alloc_struct, /* alloc_struct */
      minimal_alloc_struct, /* alloc_struct_immovable */
      minimal_alloc_byte_array, /* alloc_byte_array */
      minimal_alloc_byte_array, /* alloc_byte_array_immovable */
      NULL, /* alloc_struct_array */
      NULL, /* alloc_struct_array_immovable */
      NULL, /* object_size */
      NULL, /* object_type */
      minimal_alloc_bytes, /* alloc_string */
      minimal_alloc_bytes, /* alloc_string_immovable */
      NULL, /* resize_string */
      minimal_free_string, /* free_string */
      NULL, /* register_root */ 
      NULL, /* unregister_root */
      NULL /* enable_free */ 
    },    
    NULL, /* gs_lib_ctx */
    NULL, /* head */
    NULL, /* non_gc_memory */
    0, /* allocated */
    0, /* limit */ 
    0, /* used */ 
    0  /* max used */
};

void put_uint32(FILE *out, const unsigned int q);
void put_bytes_padded(FILE *out, unsigned char *p, unsigned int len);
void inode_clear(romfs_inode* node);
void inode_write(FILE *out, romfs_inode *node, int compression, int inode_count, int*totlen);
void process_path(char *path, const char *prefix, const char *add_prefix, Xlist_element *Xlist_head, int compression,
	int *inode_count, int *totlen, FILE *out);

/* put 4 byte integer, big endian */
void put_uint32(FILE *out, const unsigned int q)
{
#if ARCH_IS_BIG_ENDIAN
    fprintf (out, "0x%08x,", q);
#else
    fprintf (out, "0x%02x%02x%02x%02x,", q & 0xff, (q>>8) & 0xff, (q>>16) & 0xff, (q>>24) & 0xff);
#endif
}

/* write string as 4 character chunks, padded to 4 byte words. */
void put_bytes_padded(FILE *out, unsigned char *p, unsigned int len)
{
    int i, j=0;
    union {
	uint32_t w;
	struct {
	    unsigned char c1;
	    unsigned char c2;
	    unsigned char c3;
	    unsigned char c4;
	} c;
    } w2c;

    for (i=0; i<(len/4); i++) {
	j = i*4;
	w2c.c.c1 = p[j++];
	w2c.c.c2 = p[j++];
	w2c.c.c3 = p[j++];
	w2c.c.c4 = p[j++];
	fprintf(out, "0x%08x,", w2c.w);
	if ((i & 7) == 7)
	    fprintf(out, "\n\t");
    }
    w2c.w = 0;
    switch (len - j) {
      case 3:
        w2c.c.c3 = p[j+2];
      case 2:
        w2c.c.c2 = p[j+1];
      case 1:
        w2c.c.c1 = p[j];
	fprintf(out, "0x%08x,", w2c.w);
      default: ;
    }
    fprintf(out, "\n\t");
}

/* clear the internal memory of an inode */
void inode_clear(romfs_inode* node)
{
    int i, blocks = (node->length+ROMFS_BLOCKSIZE-1)/ROMFS_BLOCKSIZE;
    
    if (node) {
        if (node->data) {
            for (i = 0; i < blocks; i++) {
                if (node->data[i]) free(node->data[i]);
            }
            free(node->data);
        }
        if (node->data_lengths) free(node->data_lengths);
    }
}

/* write out and inode and its file data */
void
inode_write(FILE *out, romfs_inode *node, int compression, int inode_count, int *totlen)
{
    int i, offset;
    int blocks = (node->length+ROMFS_BLOCKSIZE-1)/ROMFS_BLOCKSIZE;
    int namelen = strlen(node->name) + 1;	/* include terminating <nul> */
    
    /* write the node header */
    fprintf(out,"    static uint32_t node_%d[] = {\n\t", inode_count);
    /* 4 byte file length + compression flag in high bit */
    put_uint32(out, node->length | (compression ? 0x80000000 : 0));
    fprintf(out, "\t/* compression_flag_bit + file length */\n\t");
    
    printf("writing node '%s' len=%ld", node->name, node->length);
#ifdef DEBUG
    printf(" %ld blocks %s", blocks, compression ? "compressed" : "binary");
#endif
    printf("\n");
    
    /* write out data block structures */
    offset = 4 + (8*blocks) + ((namelen+3) & ~3);
    *totlen += offset;			/* add in the header size */
    for (i = 0; i < blocks; i++) {
	put_uint32(out, node->data_lengths[i]);
	put_uint32(out, offset);
	offset += (node->data_lengths[i]+3) & ~3;
	fprintf(out, "\t/* data_block_length, offset to data_block */\n\t");
    }
    /* write file name (path) padded to 4 byte multiple */
    fprintf(out, "\t/* file name '%s' */\n\t", node->name);
    put_bytes_padded(out, node->name, namelen);
    
    /* write out data */
    for (i = 0; i < blocks; i++) {
	put_bytes_padded(out, node->data[i], node->data_lengths[i]);
	*totlen += (node->data_lengths[i]+3) & ~3;	/* padded block length */
    }
    fprintf(out, "\t0 };\t/* end-of-node */\n");
}


/* This relies on the gp_enumerate_* which should not return directories, nor */
/* should it recurse into directories (unlike Adobe's implementation)         */
void process_path(char *path, const char *prefix, const char *add_prefix, Xlist_element *Xlist_head,
		int compression, int *inode_count, int *totlen, FILE *out)
{
    int namelen, excluded, save_count=*inode_count;
    Xlist_element *Xlist_scan;
    char *prefixed_path;
    char *found_path, *rom_filename;
    file_enum *pfenum;
    int ret, block, blocks;
    romfs_inode *node;
    unsigned char *ubuf, *cbuf;
    unsigned long ulen, clen;
    FILE *in;

    prefixed_path = malloc(1024);
    found_path = malloc(1024);
    rom_filename = malloc(1024);
    ubuf = malloc(ROMFS_BLOCKSIZE);
    cbuf = malloc(ROMFS_CBUFSIZE);
    if (ubuf == NULL || cbuf == NULL || prefixed_path == NULL ||
		found_path == NULL || rom_filename == NULL) {
	printf("malloc fail in process_path\n");
	exit(1);
    }
    prefixed_path[0] = 0;	/* empty string */
    strcat(prefixed_path, prefix);
    strcat(prefixed_path, path);
    strcat(prefixed_path, "*");
    strcpy(rom_filename, add_prefix);
#ifdef __WIN32__
    {
	int i;

	/* On Windows, the paths may (will) have '\' instead of '/' so we translate them */
	for (i=0; i<strlen(prefixed_path); i++)
	    if (prefixed_path[i] == '\\')
		prefixed_path[i] = '/';
	for (i=0; i<strlen(rom_filename); i++)
	    if (rom_filename[i] == '\\')
		rom_filename[i] = '/';
    }
#endif
    /* check for the file on the Xlist */
    pfenum = gp_enumerate_files_init(prefixed_path, strlen(prefixed_path),
		    	(gs_memory_t *)&minimal_memory);
    if (pfenum == NULL) {
	printf("gp_enumerate_files_init failed.\n");
	exit(1);
    }
    while ((namelen=gp_enumerate_files_next(pfenum, found_path, 1024)) >= 0) {
	excluded = 0;
	found_path[namelen] = 0;		/* terminate the string */
	/* check to see if the tail of the path we found matches one on the exclusion list */
	for (Xlist_scan = Xlist_head; Xlist_scan != NULL; Xlist_scan = Xlist_scan->next) {
	    if (strlen(found_path) >= strlen(Xlist_scan->path)) {
		if (strcmp(Xlist_scan->path, found_path+strlen(found_path)-strlen(Xlist_scan->path)) == 0) {
		    excluded = 1;
		    break;
		}
	    }
	}
	if (excluded)
	    continue;

	/* process a file */
	node = calloc(1, sizeof(romfs_inode));
	/* get info for this file */
	in = fopen(found_path, "rb");
	if (in == NULL) {
	    printf("unable to open file for processing: %s\n", found_path);
	    continue;
	}
	/* rom_filename + strlen(add_prefix) is first char after the new prefix we want to add */
	/* found_path + strlen(prefix) is the file name after the -P prefix */
	rom_filename[strlen(add_prefix)] = 0;		/* truncate afater prefix */
	strcat(rom_filename, found_path + strlen(prefix));
	node->name = rom_filename;	/* without -P prefix, with -d add_prefix */
	fseek(in, 0, SEEK_END);
	node->length = ftell(in);
	blocks = (node->length+ROMFS_BLOCKSIZE-1) / ROMFS_BLOCKSIZE + 1;
	node->data_lengths = calloc(blocks, sizeof(*node->data_lengths));
	node->data = calloc(blocks, sizeof(*node->data));
	fclose(in);
	in = fopen(found_path, "rb");
	block = 0;
	while (!feof(in)) {
	    ulen = fread(ubuf, 1, ROMFS_BLOCKSIZE, in);
	    if (!ulen) break;
	    clen = ROMFS_CBUFSIZE;
	    if (compression) {
		/* compress data here */
		ret = compress(cbuf, &clen, ubuf, ulen);
		if (ret != Z_OK) {
		    printf("error compressing data block!\n");
		    exit(1);
		}
	    } else {
		memcpy(cbuf, ubuf, ulen);
		clen = ulen;
	    }
	    node->data_lengths[block] = clen; 
	    node->data[block] = malloc(clen);
	    memcpy(node->data[block], cbuf, clen);
	    block++;
	}
	fclose(in);
	/* write out data for this file */
	inode_write(out, node, compression, *inode_count, totlen);
	/* clean up */
	inode_clear(node);
	free(node);
	(*inode_count)++;
    }
    free(cbuf);
    free(ubuf);
    free(found_path);
    free(prefixed_path);
    if (save_count == *inode_count) {
	printf("warning: no files found from path '%s%s'\n", prefix, path);
    }
}

int
main(int argc, char *argv[])
{
    int i;
    int inode_count = 0, totlen = 0;
    FILE *out;
    const char *outfilename = "obj/gsromfs.c";
    const char *prefix = "";
    const char *add_prefix = "";
    int atarg = 1;
    int compression = 1;			/* default to doing compression */
    Xlist_element *Xlist_scan, *Xlist_head = NULL;
    
    if (argc < 2) {
	printf("\n"
 		"	Usage: mkromfs [-o outputfile] [options ...] paths\n"
 		"	    options and paths can be interspersed and are processed in order\n"
 		"	    options:\n"
 		"		-o outputfile	default: obj/gsromfs.c if this option present, must be first.\n"
 		"		-P prefix	use prefix to find path. prefix not included in %%rom%%\n"
 		"		-X path		exclude the path from further processing.\n"
 		"		-d string       directory in %%rom file system (just a prefix string on filename)\n"
 		"		-c		compression on\n"
 		"		-b		compression off (binary).\n"
 		"\n"
 		"	    Note: The tail of any path encountered will be tested so .svn on the -X\n"
 		"		  list will exclude that path in all subsequent paths enumerated.\n"
	    );
	exit(1);
    }

#ifdef DEBUG
    printf("compression will use %d byte blocksize (zlib output buffer %d bytes)\n",
        ROMFS_BLOCKSIZE, ROMFS_CBUFSIZE);
#endif /* DEBUG */
    
    if (argc > 3 && argv[1][0] == '-' && argv[1][1] == 'o') {
	/* process -o option for outputfile */
	outfilename = argv[2];
	atarg += 2;
    }
#ifdef DEBUG
    printf("   writing romfs data to '%s'\n", outfilename);
#endif /* DEBUG */
    out = fopen(outfilename, "w");

    fprintf(out,"\t/* Generated data for %%rom%% device, see mkromfs.c */\n");
#if ARCH_IS_BIG_ENDIAN
    fprintf(out,"\t/* this code assumes a big endian target platform */\n");
#else
    fprintf(out,"\t/* this code assumes a little endian target platform */\n");
#endif
    fprintf(out,"\n#include \"stdint_.h\"\n");
    fprintf(out,"\n#include \"time_.h\"\n\n");
    fprintf(out,"    time_t gs_romfs_buildtime = %ld;\n\n", time(NULL));

    /* process the remaining arguments (options interspersed with paths) */
    for (; atarg < argc; atarg++) {  
	if (argv[atarg][0] == '-') {
	    /* process an option */
	    switch (argv[atarg][1]) {
	      case 'b':
	        compression = 0;
	        break;
	      case 'c':
	        compression = 1;
	        break;
	      case 'd':
		if (++atarg == argc) {
		    printf("   option %s missing required argument\n", argv[atarg-1]);
		    exit(1);
		}
		add_prefix = argv[atarg];
	        break;
	      case 'P':
		if (++atarg == argc) {
		    printf("   option %s missing required argument\n", argv[atarg-1]);
		    exit(1);
		}
		prefix = argv[atarg];
	        break;
	      case 'X':
		if (++atarg == argc) {
		    printf("   option %s missing required argument\n", argv[atarg-1]);
		    exit(1);
		}
		Xlist_scan = malloc(sizeof(Xlist_element));
		if (Xlist_scan == NULL) {
		    exit(1);
		}
		Xlist_scan->next = Xlist_head;
		Xlist_head = Xlist_scan;
		Xlist_head->path = argv[atarg];
	        break;
	      default:
	        printf("  unknown option: %s \n", argv[atarg]);
	    }
	    continue;
	}
	/* process a path */
	process_path(argv[atarg], prefix, add_prefix, Xlist_head, compression, &inode_count, &totlen, out);
    }
    /* now write out the array of nodes */
    fprintf(out, "    uint32_t *gs_romfs[] = {\n");
    for (i=0; i<inode_count; i++)
	fprintf(out, "\tnode_%d,\n", i);
    fprintf(out, "\t0 };\n");
    
    fclose(out);

    printf("Total %%rom%% structure size is %d bytes.\n", totlen);
    
    return 0;
}

