#define CHAZ_USE_SHORT_NAMES

#include "Charmonizer/Core/HeadCheck.h"
#include "Charmonizer/Core/ModHandler.h"
#include "Charmonizer/Core/Stat.h"
#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Probe/LargeFiles.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/* sets of symbols which might provide large file support */
typedef struct off64_combo {
    const char *includes;
    const char *ftell_command;
    const char *fseek_command;
    const char *offset64_type;
} off64_combo;
static off64_combo off64_combos[] = {
    { "#include <sys/types.h>\n", "ftello64", "fseeko64", "off64_t" },
    { "#include <sys/types.h>\n", "ftello", "fseeko", "off_t" },
    { "", "ftell","fseek", "long" },
    { "", "_ftelli64","_fseeki64", "__int64" },
    { NULL, NULL, NULL, NULL }
};

/* Check what name 64-bit ftell, fseek go by.
 */
static chaz_bool_t
probe_off64(off64_combo *combo);

/* Determine whether we can use sparse files.
 */
static chaz_bool_t 
check_sparse_files();

/* Helper for check_sparse_files().  
 */
static void
test_sparse_file(long offset, Stat *st);

/* See if trying to write a 5 GB file in a subprocess bombs out.  If it
 * doesn't, then the test suite can safely verify large file support.
 */
static chaz_bool_t
can_create_big_files();

static char* code_buf = NULL;
static size_t code_buf_len = 0;

/* vars for holding lfs commands, once they're discovered */
static char fseek_command[10];
static char ftell_command[10];
static char off64_type[10];

void
chaz_LargeFiles_run(void) 
{
    chaz_bool_t success = false;
    unsigned i;

    START_RUN("LargeFiles");

    /* see if off64_t and friends exist or have synonyms */
    for (i = 0; off64_combos[i].includes != NULL; i++) {
        off64_combo combo = off64_combos[i];
        success = probe_off64(&combo);
        if (success) {
            strcpy(fseek_command, combo.fseek_command);
            strcpy(ftell_command, combo.ftell_command);
            strcpy(off64_type, combo.offset64_type);
            break;
        }
    }

    /* write the affirmations/definitions */
    if (success) {
        append_conf("#define CHY_HAS_LARGE_FILE_SUPPORT\n");
        /* alias these only if they're not already provided and correct */
        if (strcmp(off64_type, "off64_t") != 0) {
            append_conf("#define chy_off64_t %s\n",  off64_type);
            append_conf("#define chy_ftello64 %s\n", ftell_command);
            append_conf("#define chy_fseeko64 %s\n", fseek_command);
        }
    }

    /* check for sparse files */
    if (check_sparse_files()) {
        append_conf("#define CHAZ_HAS_SPARSE_FILES\n");
        /* see if we can create a 5 GB file without crashing */
        if (success && can_create_big_files())
            append_conf("#define CHAZ_CAN_CREATE_BIG_FILES\n");
    }
    else {
        append_conf("#define CHAZ_NO_SPARSE_FILES\n");
    }

    /* short names */
    if (success) {
        START_SHORT_NAMES;
        shorten_macro("HAS_LARGE_FILE_SUPPORT");

        /* alias these only if they're not already provided and correct */
        if (strcmp(off64_type, "off64_t") != 0) {
            shorten_typedef("off64_t");
            shorten_function("ftello64");
            shorten_function("fseeko64");
        }
        END_SHORT_NAMES;
    }
    
    free(code_buf);
    END_RUN;
}

/* code for checking ftello64 and friends */
static char off64_code[] = "\n"
    "    %s\n"
    "    #include \"_charm.h\"\n"
    "    int main() {\n"
    "        %s foo;\n"
    "        Charm_Setup;\n"
    "        printf(\"%%d\", (int)sizeof(%s));\n"
    "        foo = %s(stdout);\n"
    "        %s(stdout, 0, SEEK_SET);\n"
    "        return 0;\n"
    "    }\n"
    "";


static chaz_bool_t
probe_off64(off64_combo *combo)
{
    char *output = NULL;
    size_t output_len;
    size_t needed = sizeof(off64_code) + (2 * strlen(combo->offset64_type)) 
        + strlen(combo->ftell_command) + strlen(combo->fseek_command) + 20;
    chaz_bool_t success = false;

    /* allocate buffer as necessary and prepare the source code */
    if (code_buf_len < needed) {
        code_buf = (char*)realloc(code_buf, needed);
        code_buf_len = needed;
    }
    sprintf(code_buf, off64_code, combo->includes, combo->offset64_type, 
        combo->offset64_type, combo->ftell_command, combo->fseek_command);

    /* verify compilation and that the offset type has 8 bytes */
    output = capture_output(code_buf, strlen(code_buf), 
        &output_len);
    if (output != NULL) {
        long size = strtol(output, NULL, 10);
        if (size == 8)
            success = true;
        free(output);
    }

    return success;
}

static chaz_bool_t 
check_sparse_files()
{
    Stat st_a, st_b;

    /* bail out if we can't stat() a file */
    if (!check_header("sys/stat.h"))
        return false;

    /* write and stat a 1 MB file and a 2 MB file, both of them sparse */
    test_sparse_file(1000000, &st_a);
    test_sparse_file(2000000, &st_b);
    if (!(st_a.valid && st_b.valid))
        return false;
    if (st_a.size != 1000001)
        die ("Expected size of 1000001 but got %ld", (long)st_a.size);
    if (st_b.size != 2000001)
        die ("Expected size of 2000001 but got %ld", (long)st_b.size);

    /* see if two files with very different lengths have the same block size */
    if (st_a.blocks == st_b.blocks)
        return true;
    else
        return false;
} 

static void
test_sparse_file(long offset, Stat *st)
{
    FILE *sparse_fh;

    /* make sure the file's not there, then open */
    remove_and_verify("_charm_sparse");
    if ( (sparse_fh = fopen("_charm_sparse", "w+")) == NULL )
        die("Couldn't open file '_charm_sparse'");

    /* seek fh to [offset], write a byte, close file */
    if ( (fseek(sparse_fh, offset, SEEK_SET)) == -1)
        die("seek failed: %s", strerror(errno));
    if ( (fprintf(sparse_fh, "X")) != 1 )
        die("fprintf failed");
    if (fclose(sparse_fh))
        die("Error closing file '_charm_sparse': %s", strerror(errno));

    /* stat the file */
    Stat_stat("_charm_sparse", st);

    remove("_charm_sparse");
}

/* open a file, seek to a loc, print a char, and communicate success */
static char create_bigfile_code_a[] = "\n"
    "    #include \"_charm.h\"\n"
    "    int main() {\n"
    "        FILE *fh = fopen(\"_charm_large_file_test\", \"w+\");\n"
    "        int check_seek;\n"
    "        Charm_Setup;\n"
    "        /* bail unless seek succeeds */\n"
    "        check_seek = \n"
    "";

static char create_bigfile_code_b[] = "\n"
    "            (fh, 5000000000, SEEK_SET);\n"
    "        if (check_seek == -1)\n"
    "            exit(1);\n"
    "        /* bail unless we write successfully */\n"
    "        if (fprintf(fh, \"X\") != 1)\n"
    "            exit(1);\n"
    "        if (fclose(fh))\n"
    "            exit(1);\n"
    "        /* communicate success to Charmonizer */\n"
    "        printf(\"1\");\n"
    "        return 0;\n"
    "    }\n"
    "";

static chaz_bool_t
can_create_big_files()
{
    char *output;
    size_t output_len;
    FILE *truncating_fh;

    /* concat the source strings, compile the file, capture output */
    code_buf_len = join_strings(&code_buf, code_buf_len,
        create_bigfile_code_a, fseek_command, create_bigfile_code_b, NULL);

    output = capture_output(code_buf, code_buf_len, &output_len);

    /* truncate, just in case the call to remove fails */
    truncating_fh = fopen("_charm_large_file_test", "w");
    if (truncating_fh != NULL)
        fclose(truncating_fh);
    remove_and_verify("_charm_large_file_test");

    /* return true if the test app made it to the finish line */
    return output == NULL ? false : true;
}


/* Copyright 2006-2007 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */