/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF.  The full HDF copyright notice, including       *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://support.hdfgroup.org/ftp/HDF/releases/.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 ** FILE
 **   hdfpack.c
 ** USAGE
 **   hdfpack [options] <infile> <outfile>
 ** DESCRIPTION
 **   This program will compact an HDF file by reading in all the data
 **   elements in the file and writing them out to a new file.
 **      Options are:
 **          -b  Don't coalesce linked-block elements
 **          -i  Interactive mode; prompt for each linked-block element
 **          -d# Force the output file to have # DDs per DD block
 **          -t# Force output file to have # linked blocks per table entry
 **          -x  Keep external objects external
 **          -r <from> <to> rename external objects
 **      Only one of options b and i can be specified.
 ** COMMENTS, BUGS, ASSUMPTIONS
 **   Both arguments must be supplied to the program and they cannot be
 **   identical.
 **   You must have enough additional disk space for the compacted file.
 */

#include "hdf.h"
#include "herr.h"
#include "hfile.h"
#include <stdio.h>
#include <string.h>

typedef struct mydd_t {
    uint16 tag;
    uint16 ref;
    int32  offset;
    int32  length;
    int16  special;
} mydd_t;

/* Static function prototypes */
int promptblocks(mydd_t *dd);

void copy_blocks(mydd_t *dd, int32 infile, int32 outfile);

void merge_blocks(mydd_t *dd, int32 infile, int32 outfile);

int         main(int, char *a[]);
static void usage(char *);
static void hdferror(void);
static void error(const char *);
int         desc_comp(const void *d1, const void *d2);

unsigned char *data;
char           invoke[81];
int32          data_size;
int32          nblk      = 0;
char          *from_file = NULL;
char          *to_file   = NULL;

int
main(int argc, char *argv[])
{
    int     i, num_desc, fnum, merge;
    int32   infile, outfile, aid, ret;
    mydd_t *dlist;
    int32   oldoff, oldlen;
    int     blocks   = 1;
    int     intr     = 0;
    int16   ndds     = 0;
    int     optset   = 0;
    intn    external = FALSE;
    char   *tmp, fname[2][80];

    /*
     **   Get invocation name of program
     */
    tmp = (char *)NULL;
    HDstrcpy(invoke, strtok(argv[0], "/]\\\0"));
    for (;;) {
        if (tmp != NULL)
            HDstrcpy(invoke, tmp);
        if ((tmp = strtok((char *)NULL, "/]\\\0")) == NULL)
            break;
    }

    if (argc < 3) {
        usage(invoke);
        exit(1);
    }

    /*
     **   Get option flags
     */
    fnum = 0;
    i    = 1;
    while (i < argc) {
        if (argv[i][0] == '-') {
            switch (argv[i][1]) {
                case 'b':
                    if (optset == 0) {
                        blocks = 0;
                        optset = 1;
                    }
                    else {
                        error("incompatible options: -i and -b");
                    }
                    break;
                case 'i':
                    if (optset == 0) {
                        intr   = 1;
                        blocks = 0;
                        optset = 1;
                    }
                    else {
                        error("incompatible options: -i and -b");
                    }
                    break;
                case 'd':
                    ndds = (int16)atoi(&argv[i][2]);
                    break;
                case 't':
                    nblk = atoi(&argv[i][2]);
                    break;
                case 'x':
                    external = TRUE;
                    break;
                case 'r':
                    from_file = argv[++i];
                    to_file   = argv[++i];
                    break;
                default:
                    fprintf(stderr, "Unknown option -%c ignored\n", argv[i][1]);
                    break;
            }
        }
        else {
            if (fnum < 2) {
                HDstrcpy(fname[fnum], argv[i]);
                fnum++;
            }
        }
        i++;
    }

    /*
     **   Enough [unique] file arguments?
     */
    if ((fnum != 2) || (HDstrcmp(fname[0], fname[1]) == 0)) {
        error("need 2 unique file names");
    }

    if (from_file && to_file)
        printf("Going to rename external file %s to %s\n", from_file, to_file);

    /*
     **   Check to make sure input file is HDF
     */
    ret = (int)Hishdf(fname[0]);
    if (ret == FALSE)
        hdferror();

    /*
     **   Open input and output files
     */
    infile = Hopen(fname[0], DFACC_READ, 0);

    if (infile == FAIL)
        hdferror();

    outfile = Hopen(fname[1], DFACC_CREATE, ndds);

    if (outfile == FAIL)
        hdferror();

    /*
     **   See how many data elements there
     **   are and make room for their DD's
     */
    num_desc = (int)Hnumber(infile, DFTAG_WILDCARD);
    if (num_desc == FAIL)
        hdferror();

    dlist = (mydd_t *)malloc(num_desc * sizeof(*dlist));
    if (dlist == NULL)
        error("\tWow!  That file must be HUGE!\n\tThere isn't enough memory to hold the DD's.\n");

    /*
     **   Allocate data buffer - try 1 Meg first, work down
     */
    data_size = 1048576; /* 1 MB */
    data      = NULL;
    while ((data = (unsigned char *)malloc(data_size)) == NULL)
        data_size /= 2; /* okay then, cut request by half */

    /*
     **   Get all DD's for data elements
     */
    aid = Hstartread(infile, DFTAG_WILDCARD, DFREF_WILDCARD);
    if (aid == FAIL) {
        printf("MAJOR PROBLEM: Hstartread for DD's; line %d\n", __LINE__);
        hdferror();
    }
    for (i = 0; i < num_desc; i++) {

        /*
         * Get data about the current one
         */
        Hinquire(aid, NULL, &dlist[i].tag, &dlist[i].ref, &dlist[i].length, &dlist[i].offset, NULL, NULL,
                 &dlist[i].special);

        /*
         * Move to the next one
         */
        ret = Hnextread(aid, DFTAG_WILDCARD, DFREF_WILDCARD, DF_CURRENT);

        /*
         * Fail if there are none left and we expect more
         */
        if ((ret == FAIL) && (i + 1 < num_desc)) {
            printf("MAJOR PROBLEM: DDs; only got %d of %d; line %d\n", i, num_desc, __LINE__);
            hdferror();
        }
    }

    /*
     * Close the access element
     */
    ret = Hendaccess(aid);
    if (ret == FAIL)
        hdferror();

    /*
     **   Sort DD's by offset to make it easy to
     **   find DD's that point to the same data
     */
    qsort((char *)dlist, (size_t)num_desc, sizeof(*dlist), desc_comp);

    oldoff = -2;
    oldlen = -2;
    for (i = 0; i < num_desc; i++) {
        if (((dlist[i].tag != DFTAG_NULL) && (dlist[i].tag != DFTAG_VERSION)) &&
            (dlist[i].tag != DFTAG_LINKED)) {
            if ((dlist[i].offset != oldoff) || (dlist[i].length != oldlen)) {
                /*
                 **   if this DD points to different data, read the data
                 **   from the old file and write it to the new file
                 */
                switch (dlist[i].special) {
                    case SPECIAL_LINKED:
                        if (intr == 1)
                            merge = promptblocks(&dlist[i]);
                        else
                            merge = blocks;
                        if (merge == 0) {
                            copy_blocks(&dlist[i], infile, outfile);
                        }
                        else {
                            merge_blocks(&dlist[i], infile, outfile);
                        }
                        break;
                    case SPECIAL_EXT:
                        if (external) {
                            sp_info_block_t info;
                            int32           aid1, new_aid;
                            char           *name;

                            /* get file name and offset */
                            aid1 = Hstartread(infile, dlist[i].tag, dlist[i].ref);
                            if (aid1 == FAIL)
                                continue;

                            ret = HDget_special_info(aid1, &info);
                            if ((ret == FAIL) || (info.key != SPECIAL_EXT))
                                continue;

                            /* see if should be renamed */
                            if (from_file && !HDstrcmp(info.path, from_file))
                                name = to_file;
                            else
                                name = info.path;

                            /* create the new one */
                            new_aid = HXcreate(outfile, BASETAG(dlist[i].tag), dlist[i].ref, name,
                                               info.offset, dlist[i].length);

                            /* close the elements */
                            Hendaccess(aid1);
                            Hendaccess(new_aid);
                        }
                        else {
                            /* pull into the file */
                            merge_blocks(&dlist[i], infile, outfile);
                        }
                        break;
                    case SPECIAL_COMP: /* This code assumes that you'd like to leave the compressed data that
                                          way and not expand it */
                    {
                        int32  aid, len;
                        void **buf;

                        /* Read in old compressed data description */
                        if ((aid = Hstartaccess(infile, dlist[i].tag, dlist[i].ref, DFACC_READ)) == FAIL)
                            continue;
                        HQuerylength(aid, &len);
                        buf = malloc(len);
                        Hread(aid, len, buf);
                        Hendaccess(aid);

                        /* Write compressed data description into new file */
                        if ((aid = Hstartaccess(outfile, dlist[i].tag, dlist[i].ref, DFACC_WRITE)) == FAIL)
                            continue;
                        Hwrite(aid, len, buf);
                        Hendaccess(aid);
                        free(buf);
                    } break;
                    default:
                        merge_blocks(&dlist[i], infile, outfile);
                        break;
                } /* switch (special) */
            }
            else {
                /*
                 **   otherwise, just make a new DD for same data
                 */
                ret = Hdupdd(outfile, dlist[i].tag, dlist[i].ref, dlist[i - 1].tag, dlist[i - 1].ref);
                if (ret == FAIL) {
                    HERROR(DFE_GENAPP);
                    hdferror();
                }
            }
        }
        /*
         **   save offset of data to check against next DD
         */
        oldoff = dlist[i].offset;
    }

    free(data);
    free(dlist);

    /*
     **   done; close files
     */
    Hclose(infile);
    Hclose(outfile);

    return (0);
}

/*
 ** NAME
 **      promptblocks
 */
int
promptblocks(mydd_t *dd)
{
    char ans[80];

    printf("Would you like to coalesce the following linked-block element:\n");
    printf("\ttag = %d\n\tref = %d\n(y/n): ", dd->tag, dd->ref);
    fgets(ans, 80, stdin);
    if ((ans[0] == 'y') || (ans[0] == 'Y'))
        return (1);
    else
        return (0);
}

/*
 ** NAME
 **      copy_blocks -- move a linked-block element; preserve blocking
 */
void
copy_blocks(mydd_t *dd, int32 infile, int32 outfile)
{
    int32           inaid, ret, rdret, outaid;
    sp_info_block_t info;

    inaid = Hstartread(infile, dd->tag, dd->ref);
    ret   = HDget_special_info(inaid, &info);
    if ((ret != SUCCEED) || (info.key != SPECIAL_LINKED)) {
        HERROR(DFE_GENAPP);
        hdferror();
    }
    /*
     **  copy first block
     */
    outaid = Hstartwrite(outfile, BASETAG(dd->tag), dd->ref, info.first_len);
    if (outaid == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }
    rdret = 0;
    while (rdret < info.first_len) {
        ret = Hread(inaid, (data_size < info.first_len) ? data_size : info.first_len, data);
        if (ret == FAIL) {
            HERROR(DFE_GENAPP);
            hdferror();
        }
        rdret += ret;
        ret = Hwrite(outaid, ret, data);
        if (ret == FAIL) {
            HERROR(DFE_GENAPP);
            hdferror();
        }
    }
    ret = Hendaccess(outaid);
    /*
     **  promote to linked-block element
     */
    if (nblk > 0)
        info.nblocks = nblk;

    outaid = HLcreate(outfile, BASETAG(dd->tag), dd->ref, info.block_len, info.nblocks);
    if (outaid == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }
    /*
     **  copy remaining blocks
     */
    rdret = data_size;
    while (rdret == data_size) {
        rdret = Hread(inaid, data_size, data);
        if (rdret == FAIL) {
            HERROR(DFE_GENAPP);
            hdferror();
        }
        if (rdret != 0) {
            ret = Hwrite(outaid, rdret, data);
            if (ret == FAIL) {
                HERROR(DFE_GENAPP);
                hdferror();
            }
        }
    }
    Hendaccess(outaid);
    Hendaccess(inaid);
}

/*
 ** NAME
 **      merge_blocks
 */
void
merge_blocks(mydd_t *dd, int32 infile, int32 outfile)
{
    int32 inaid, outaid, ret, len;

    inaid = Hstartread(infile, dd->tag, dd->ref);
    if (inaid == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }

    outaid = Hstartaccess(outfile, BASETAG(dd->tag), dd->ref, DFACC_WRITE);
    if (outaid == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }

    while (dd->length > 0) {
        dd->length -= (len = Hread(inaid, data_size, data));
        if (len == FAIL) {
            HERROR(DFE_GENAPP);
            hdferror();
        }
        ret = Hwrite(outaid, len, data);
        if (ret == FAIL) {
            HERROR(DFE_GENAPP);
            hdferror();
        }
    }

    ret = Hendaccess(inaid);
    if (ret == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }

    ret = Hendaccess(outaid);
    if (ret == FAIL) {
        HERROR(DFE_GENAPP);
        hdferror();
    }
}

/*
 ** NAME
 **   usage -- print out usage template
 ** USAGE
 **   int usage()
 ** RETURNS
 **   none
 ** DESCRIPTION
 **   Print hdfpack's usage template to stdout.
 ** GLOBAL VARIABLES
 ** COMMENTS, BUGS, ASSUMPTIONS
 ** EXAMPLES
 */
static void
usage(char *name)
{
    fprintf(stderr, "Usage:  %s [-i | -b] [-d#] [-t#] [-x] [-r <from> <to>] <infile> <outfile>\n", name);
    fprintf(stderr, "\t-t# : reset the linked block sizing\n");
    fprintf(stderr, "\t-d# : reset the number of DDs per DD block\n");
    fprintf(stderr, "\t-i  : interactively merge linked blocks\n");
    fprintf(stderr, "\t-b  : maintain linked blocking sizes\n");
    fprintf(stderr, "\t-x  : keep external elements external\n");
    fprintf(stderr, "\t-r <from> <to> :\n");
    fprintf(stderr, "\t      change external element names matching <from> to <to>\n");
}

/*
 ** NAME
 **   hdferror -- print out HDF error number
 ** USAGE
 **   int hdferror()
 ** RETURNS
 **   none
 ** DESCRIPTION
 **   Print an HDF error number to stderr.
 ** GLOBAL VARIABLES
 ** COMMENTS, BUGS, ASSUMPTIONS
 **   This routine terminates the program with code 1.
 ** EXAMPLES
 */
static void
hdferror(void)
{
    HEprint(stderr, 0);
    exit(1);
}

/*
 ** NAME
 **      error -- print error to stderr
 ** USAGE
 **      int error(string);
 **      char *string;           IN: pointer to error description string
 ** RETURNS
 **   none
 ** DESCRIPTION
 **   Print an HDF error number to stderr.
 ** GLOBAL VARIABLES
 ** COMMENTS, BUGS, ASSUMPTIONS
 **   This routine terminates the program with code 1.
 ** EXAMPLES
 */
static void
error(const char *string)
{
    fprintf(stderr, "%s: %s\n", invoke, string);
    exit(1);
}

/*
 ** NAME
 **   desc_comp -- compare two DD's by offset
 ** USAGE
 **   int desc_cmp(d1, d2)
 **   mydd_t *d1              IN: pointer to a DD
 **   mydd_t *d2              IN: pointer to a DD
 ** RETURNS
 **   A number less than, greater than, or equal to 0, according to
 **   whether d1's offset is less than, greater than, or equal to
 **   d2's offset.
 ** DESCRIPTION
 **   A pointer to this routine is passed to qsort() as the comparison
 **   function for the sort.
 ** GLOBAL VARIABLES
 ** COMMENTS, BUGS, ASSUMPTIONS
 ** EXAMPLES
 */
int
desc_comp(const void *d1, const void *d2)
{
    return ((int)(((const mydd_t *)d1)->offset - ((const mydd_t *)d2)->offset));
}
