/******************************************************************************
 * hiperiso_unix.c 
 *
 * Copyright (c) 2020, longpanda <admin@hiperiso.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/device.h>
#include <grub/term.h>
#include <grub/partition.h>
#include <grub/file.h>
#include <grub/normal.h>
#include <grub/extcmd.h>
#include <grub/datetime.h>
#include <grub/i18n.h>
#include <grub/net.h>
#include <grub/time.h>
#include <grub/elf.h>
#include <grub/elfload.h>
#include <grub/hiperiso.h>
#include "hiperiso_def.h"

GRUB_MOD_LICENSE ("GPLv3+");

char g_ko_mod_path[256];
int g_conf_new_len = 0;
char *g_conf_new_data = NULL;

int g_mod_new_len = 0;
char *g_mod_new_data = NULL;

int g_mod_search_magic = 0;
int g_unix_hlnk_boot = 0;

int g_ko_fillmap_len = 0;
char *g_ko_fillmap_data = NULL;

grub_uint64_t g_mod_override_offset = 0;
grub_uint64_t g_conf_override_offset = 0;

static int hiperiso_get_file_override(const char *filename, grub_uint64_t *offset)
{
    grub_file_t file;

    *offset = 0;

    file = hiperiso_grub_file_open(HIPERISO_FILE_TYPE, "(loop)%s", filename);
    if (!file)
    {
        return 1;
    }

    *offset = grub_iso9660_get_last_file_dirent_pos(file) + 2;
    
    grub_file_close(file);
    
    return 0;
}

static grub_uint32_t hiperiso_unix_get_override_chunk_count(void)
{
    grub_uint32_t count = 0;

    if (g_conf_new_len > 0)
    {
        count++;
    }
    
    if (g_mod_new_len > 0)
    {
        count++;
    }
    
    if (g_ko_fillmap_len > 0)
    {
        count += (g_ko_fillmap_len / 512);
        if ((g_ko_fillmap_len % 512) > 0)
        {
            count++;
        }
    }

    return count;
}

static grub_uint32_t hiperiso_unix_get_virt_chunk_count(void)
{
    grub_uint32_t count = 0;

    if (g_conf_new_len > 0)
    {
        count++;
    }
    
    if (g_mod_new_len > 0)
    {
        count++;
    }

    return count;
}
static grub_uint32_t hiperiso_unix_get_virt_chunk_size(void)
{
    grub_uint32_t size;

    size = sizeof(hiperiso_virt_chunk) * hiperiso_unix_get_virt_chunk_count();

    if (g_conf_new_len > 0)
    {
        size += hiperiso_align_2k(g_conf_new_len);
    }
    
    if (g_mod_new_len > 0)
    {
        size += hiperiso_align_2k(g_mod_new_len);
    }

    return size;
}

static void hiperiso_unix_fill_map_data(hiperiso_chain_head *chain, struct g_hiperiso_map *map)
{
    grub_uint32_t i;
    hiperiso_img_chunk *chunk = NULL;

    debug("Fill unix map data: <%llu> <%u> %p\n", 
        (unsigned long long)chain->os_param.hiso_disk_size, g_img_chunk_list.cur_chunk, map);
    
    map->magic1[0] = map->magic2[0] = HIPERISO_UNIX_SEG_MAGIC0;
    map->magic1[1] = map->magic2[1] = HIPERISO_UNIX_SEG_MAGIC1;
    map->magic1[2] = map->magic2[2] = HIPERISO_UNIX_SEG_MAGIC2;
    map->magic1[3] = map->magic2[3] = HIPERISO_UNIX_SEG_MAGIC3;

    map->disksize = chain->os_param.hiso_disk_size;
    grub_memcpy(map->diskuuid, chain->os_param.hiso_disk_guid, 16);

    map->segnum = g_img_chunk_list.cur_chunk;
    if (g_img_chunk_list.cur_chunk > HIPERISO_UNIX_MAX_SEGNUM)
    {
        debug("####[FAIL] Too many segments for the ISO file %u\n", g_img_chunk_list.cur_chunk);
        map->segnum = HIPERISO_UNIX_MAX_SEGNUM;
    }
    
    for (i = 0; i < (grub_uint32_t)(map->segnum); i++)
    {
        chunk = g_img_chunk_list.chunk + i;
        map->seglist[i].seg_start_bytes = chunk->disk_start_sector * 512ULL;
        map->seglist[i].seg_end_bytes = (chunk->disk_end_sector + 1) * 512ULL;        
    }
}

static void hiperiso_unix_fill_override_data(    grub_uint64_t isosize, hiperiso_chain_head *chain)
{
    int i;
    int left;
    char *data = NULL;
    grub_uint64_t offset;
    grub_uint64_t sector;
    hiperiso_override_chunk *cur;
    hiperiso_iso9660_override *dirent;
    
    sector = (isosize + 2047) / 2048;

    cur = (hiperiso_override_chunk *)((char *)chain + chain->override_chunk_offset);

    if (g_conf_new_len > 0)
    {
        /* loader.conf */
        cur->img_offset = g_conf_override_offset;
        cur->override_size = sizeof(hiperiso_iso9660_override);
        dirent = (hiperiso_iso9660_override *)cur->override_data;
        dirent->first_sector    = (grub_uint32_t)sector;
        dirent->size            = (grub_uint32_t)g_conf_new_len;
        dirent->first_sector_be = grub_swap_bytes32(dirent->first_sector);
        dirent->size_be         = grub_swap_bytes32(dirent->size);
        sector += (dirent->size + 2047) / 2048;
        cur++;
    }

    if (g_mod_new_len > 0)
    {
        /* mod.ko */
        cur->img_offset = g_mod_override_offset;
        cur->override_size = sizeof(hiperiso_iso9660_override);
        dirent = (hiperiso_iso9660_override *)cur->override_data;
        dirent->first_sector    = (grub_uint32_t)sector;
        dirent->size            = (grub_uint32_t)g_mod_new_len;
        dirent->first_sector_be = grub_swap_bytes32(dirent->first_sector);
        dirent->size_be         = grub_swap_bytes32(dirent->size);
        sector += (dirent->size + 2047) / 2048;
        cur++;
    }

    if (g_ko_fillmap_len > 0)
    {
        data = g_ko_fillmap_data;
        offset = g_mod_override_offset;

        hiperiso_unix_fill_map_data(chain, (struct g_hiperiso_map *)data);
        
        for (i = 0; i < g_ko_fillmap_len / 512; i++)
        {
            cur->img_offset = offset;
            cur->override_size = 512;
            grub_memcpy(cur->override_data, data, 512);

            offset += 512;
            data += 512;
            cur++;
        }

        left = (g_ko_fillmap_len % 512);
        if (left > 0)
        {
            cur->img_offset = offset;
            cur->override_size = left;
            grub_memcpy(cur->override_data, data, left);

            offset += left;
            cur++;
        }
    }

    return;
}

static void hiperiso_unix_fill_virt_data(    grub_uint64_t isosize, hiperiso_chain_head *chain)
{
    grub_uint64_t sector;
    grub_uint32_t offset;
    grub_uint32_t data_secs;
    char *override;
    hiperiso_virt_chunk *cur;

    override = (char *)chain + chain->virt_chunk_offset;
    cur = (hiperiso_virt_chunk *)override;
    
    sector = (isosize + 2047) / 2048;
    offset = 2 * sizeof(hiperiso_virt_chunk);

    if (g_conf_new_len > 0)
    {
        hiperiso_unix_fill_virt(g_conf_new_data, g_conf_new_len);
    }

    if (g_mod_new_len > 0)
    {
        if (g_mod_search_magic > 0)
        {
            hiperiso_unix_fill_map_data(chain, (struct g_hiperiso_map *)(g_mod_new_data + g_mod_search_magic));
        }
    
        hiperiso_unix_fill_virt(g_mod_new_data, g_mod_new_len);
    }
    
    return;
}

static int hiperiso_freebsd_append_conf(char *buf, const char *isopath, const char *alias)
{
    int pos = 0;
    grub_uint32_t i;
    grub_disk_t disk;
    grub_file_t isofile;
    char uuid[64] = {0};
    const char *val = NULL;
    hiperiso_img_chunk *chunk;
    grub_uint8_t disk_sig[4];
    grub_uint8_t disk_guid[16];

    debug("hiperiso_freebsd_append_conf %s\n", isopath);
    
    isofile = hiperiso_grub_file_open(HIPERISO_FILE_TYPE, "%s", isopath);
    if (!isofile)
    {
        return 1;
    }

    hiso_ssprintf(buf, pos, "hiperiso_load=\"%s\"\n", "YES");
    hiso_ssprintf(buf, pos, "hiperiso_name=\"%s\"\n", g_ko_mod_path);
    
    if (alias)
    {
        hiso_ssprintf(buf, pos, "hint.hiperiso.0.alias=\"%s\"\n", alias);
    }

    if (g_unix_hlnk_boot)
    {
        hiso_ssprintf(buf, pos, "hint.hiperiso.0.hlnk=%d\n", 1);
    }

    val = hiperiso_get_env("HISO_UNIX_REMOUNT");
    if (val && val[0] == '1' && val[1] == 0)
    {
        hiso_ssprintf(buf, pos, "hint.hiperiso.0.remount=%d\n", 1);
    }

    if (g_mod_search_magic)
    {
        debug("hint.hiperiso NO need\n");
        goto out;
    }

    debug("Fill hint.hiperiso info\n");
    
    disk = isofile->device->disk;

    hiperiso_get_disk_guid(isofile->name, disk_guid, disk_sig);

    for (i = 0; i < 16; i++)
    {
        grub_snprintf(uuid + i * 2, sizeof(uuid), "%02x", disk_guid[i]);
    }

    hiso_ssprintf(buf, pos, "hint.hiperiso.0.disksize=%llu\n", (ulonglong)(disk->total_sectors * (1 << disk->log_sector_size)));
    hiso_ssprintf(buf, pos, "hint.hiperiso.0.diskuuid=\"%s\"\n", uuid);
    hiso_ssprintf(buf, pos, "hint.hiperiso.0.disksignature=%02x%02x%02x%02x\n", disk_sig[0], disk_sig[1], disk_sig[2], disk_sig[3]);
    hiso_ssprintf(buf, pos, "hint.hiperiso.0.segnum=%u\n", g_img_chunk_list.cur_chunk);

    for (i = 0; i < g_img_chunk_list.cur_chunk; i++)
    {
        chunk = g_img_chunk_list.chunk + i;
        hiso_ssprintf(buf, pos, "hint.hiperiso.%u.seg=\"0x%llx@0x%llx\"\n", 
            i, (ulonglong)(chunk->disk_start_sector * 512),
            (ulonglong)((chunk->disk_end_sector + 1) * 512));
    }

out:
    grub_file_close(isofile);
    return pos;
}

static int hiperiso_dragonfly_append_conf(char *buf, const char *isopath)
{
    int pos = 0;

    debug("hiperiso_dragonfly_append_conf %s\n", isopath);

    hiso_ssprintf(buf, pos, "tmpfs_load=\"%s\"\n", "YES");
    hiso_ssprintf(buf, pos, "dm_target_linear_load=\"%s\"\n", "YES");
    hiso_ssprintf(buf, pos, "initrd.img_load=\"%s\"\n", "YES");
    hiso_ssprintf(buf, pos, "initrd.img_type=\"%s\"\n", "md_image");
    hiso_ssprintf(buf, pos, "vfs.root.mountfrom=\"%s\"\n", "ufs:md0s0");

    return pos;
}

grub_err_t hiperiso_cmd_unix_reset(grub_extcmd_context_t ctxt, int argc, char **args)
{
    (void)ctxt;
    (void)argc;
    (void)args;
    
    g_unix_hlnk_boot = 0;
    g_mod_search_magic = 0;
    g_conf_new_len = 0;
    g_mod_new_len = 0;
    g_mod_override_offset = 0;
    g_conf_override_offset = 0;
    g_ko_fillmap_len = 0;

    check_free(g_mod_new_data, grub_free);
    check_free(g_conf_new_data, grub_free);
    check_free(g_ko_fillmap_data, grub_free);

    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_check_hlnk(grub_extcmd_context_t ctxt, int argc, char **args)
{
    grub_file_t file;

    (void)ctxt;

    if (argc != 1)
    {
        return 1;
    }

    file = grub_file_open(args[0], HIPERISO_FILE_TYPE);
    if (file)
    {
        g_unix_hlnk_boot = file->hlnk;
        grub_file_close(file);
    }

    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_parse_freenas_ver(grub_extcmd_context_t ctxt, int argc, char **args)
{
    grub_file_t file;
    const char *ver = NULL;
    char *buf = NULL;
    HISO_JSON *json = NULL;
    
    (void)ctxt;
    (void)argc;

    file = hiperiso_grub_file_open(HIPERISO_FILE_TYPE, "%s", args[0]);
    if (!file)
    {
        debug("Failed to open file %s\n", args[0]);
        return 1;
    }

    buf = grub_malloc(file->size + 2);
    if (!buf)
    {
        grub_file_close(file);
        return 0;
    }
    grub_file_read(file, buf, file->size);
    buf[file->size] = 0;

    json = hiso_json_create();
    if (!json)
    {
        goto end;
    }

    if (hiso_json_parse(json, buf))
    {
        goto end;
    }

    ver = hiso_json_get_string_ex(json->pstChild, "Version");
    if (ver)
    {
        debug("NAS version:<%s>\n", ver);
        if (grub_strncmp(ver, "TrueNAS-", 8) == 0)
        {
            ver += 8;
        }
        hiperiso_set_env(args[1], ver);
    }
    else
    {
        debug("NAS version:<%s>\n", "NOT FOUND");
        grub_env_unset(args[1]);
    }

end:
    grub_check_free(buf);
    check_free(json, hiso_json_destroy);
    grub_file_close(file);

    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_freebsd_ver(grub_extcmd_context_t ctxt, int argc, char **args)
{
    grub_file_t file;
    char *buf;
    char *start = NULL;
    char *nextline = NULL;
    
    (void)ctxt;
    (void)argc;
    (void)args;

    file = hiperiso_grub_file_open(GRUB_FILE_TYPE_LINUX_INITRD, "%s", args[0]);
    if (!file)
    {
        debug("Failed to open file %s\n", args[0]);
        return 1;
    }

    buf = grub_zalloc(file->size + 2);
    if (!buf)
    {
        grub_file_close(file);
        return 0;
    }
    grub_file_read(file, buf, file->size);

    for (start = buf; start; start = nextline)
    {
        if (grub_strncmp(start, "USERLAND_VERSION", 16) == 0)
        {
            nextline = start;
            while (*nextline && *nextline != '\r' && *nextline != '\n')
            {
                nextline++;
            }

            *nextline = 0;            
            break;
        }
        nextline = hiperiso_get_line(start);
    }

    if (start)
    {
        debug("freebsd version:<%s>\n", start);
        hiperiso_set_env(args[1], start);
    }
    else
    {
        debug("freebsd version:<%s>\n", "NOT FOUND");
        grub_env_unset(args[1]);
    }

    grub_free(buf);
    grub_file_close(file);
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_freebsd_ver_elf(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int j;
    int k;
    grub_elf_t elf = NULL;
    grub_off_t offset = 0;
    grub_uint32_t len = 0;
    char *str = NULL;
    char *data = NULL;
    void *hdr = NULL;
    char ver[64] = {0};
    
    (void)ctxt;
    (void)argc;
    (void)args;

    if (argc != 3)
    {
        debug("Invalid argc %d\n", argc);
        return 1;
    }

    data = grub_zalloc(8192);
    if (!data)
    {
        goto out;
    }

    elf = grub_elf_open(args[0], GRUB_FILE_TYPE_LINUX_INITRD);
    if (!elf)
    {
        debug("Failed to open file %s\n", args[0]);
        goto out;
    }

    if (args[1][0] == '6')
    {
        Elf64_Ehdr *e = &(elf->ehdr.ehdr64);
        Elf64_Shdr *h;
        Elf64_Shdr *s;
        Elf64_Shdr *t;
        Elf64_Half i;
        
        h = hdr = grub_zalloc(e->e_shnum * e->e_shentsize);
        if (!h)
        {
            goto out;
        }

        debug("read section header %u %u %u\n", e->e_shnum, e->e_shentsize, e->e_shstrndx);
        grub_file_seek(elf->file, e->e_shoff);
        grub_file_read(elf->file, h, e->e_shnum * e->e_shentsize);

        s = (Elf64_Shdr *)((char *)h + e->e_shstrndx * e->e_shentsize);        
        str = grub_malloc(s->sh_size + 1);
        if (!str)
        {
            goto out;
        }
        str[s->sh_size] = 0;

        debug("read string table %u %u\n", (grub_uint32_t)s->sh_offset, (grub_uint32_t)s->sh_size);
        grub_file_seek(elf->file, s->sh_offset);
        grub_file_read(elf->file, str, s->sh_size);

        for (t = h, i = 0; i < e->e_shnum; i++)
        {
            if (grub_strcmp(str + t->sh_name, ".data") == 0)
            {
                offset = t->sh_offset;
                len = t->sh_size;
                debug("find .data section at %u %u\n", (grub_uint32_t)offset, len);
                break;
            }
            t = (Elf64_Shdr *)((char *)t + e->e_shentsize);
        }
    }
    else
    {
        Elf32_Ehdr *e = &(elf->ehdr.ehdr32);
        Elf32_Shdr *h;
        Elf32_Shdr *s;
        Elf32_Shdr *t;
        Elf32_Half i;
        
        h = hdr = grub_zalloc(e->e_shnum * e->e_shentsize);
        if (!h)
        {
            goto out;
        }

        debug("read section header %u %u %u\n", e->e_shnum, e->e_shentsize, e->e_shstrndx);
        grub_file_seek(elf->file, e->e_shoff);
        grub_file_read(elf->file, h, e->e_shnum * e->e_shentsize);

        s = (Elf32_Shdr *)((char *)h + e->e_shstrndx * e->e_shentsize);        
        str = grub_malloc(s->sh_size + 1);
        if (!str)
        {
            goto out;
        }
        str[s->sh_size] = 0;

        debug("read string table %u %u\n", (grub_uint32_t)s->sh_offset, (grub_uint32_t)s->sh_size);
        grub_file_seek(elf->file, s->sh_offset);
        grub_file_read(elf->file, str, s->sh_size);

        for (t = h, i = 0; i < e->e_shnum; i++)
        {
            if (grub_strcmp(str + t->sh_name, ".data") == 0)
            {
                offset = t->sh_offset;
                len = t->sh_size;
                debug("find .data section at %u %u\n", (grub_uint32_t)offset, len);
                break;
            }
            t = (Elf32_Shdr *)((char *)t + e->e_shentsize);
        }
    }

    if (offset == 0 || len == 0)
    {
        debug(".data section not found %s\n", args[0]);
        goto out;
    }

    grub_file_seek(elf->file, offset + len - 8192);
    grub_file_read(elf->file, data, 8192);

    for (j = 0; j < 8192 - 12; j++)
    {
        if (grub_strncmp(data + j, "@(#)FreeBSD ", 12) == 0)
        {
            for (k = j + 12; k < 8192; k++)
            {
                if (0 == grub_isdigit(data[k]) && data[k] != '.')
                {
                    data[k] = 0;
                    break;
                }
            }
        
            grub_snprintf(ver, sizeof(ver), "%s", data + j + 12);
            break;
        }
    }

    if (ver[0])
    {
        k = (int)grub_strtoul(ver, NULL, 10);
        debug("freebsd version:<%s> <%d.x>\n", ver, k);
        grub_snprintf(ver, sizeof(ver), "%d.x", k);
        hiperiso_set_env(args[2], ver);
    }
    else
    {
        debug("freebsd version:<%s>\n", "NOT FOUND");
    }

out:
    grub_check_free(str);
    grub_check_free(hdr);
    grub_check_free(data);
    check_free(elf, grub_elf_close);
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_replace_grub_conf(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int len = 0;
    grub_uint32_t i;
    char *data;
    char *pos;
    const char *val = NULL;
    grub_uint64_t offset;
    grub_file_t file;
    char extcfg[512];
    const char *confile = NULL;
    const char * loader_conf[] = 
    {
        "/boot/grub/grub.cfg",
    };

    (void)ctxt;

    if (argc != 1 && argc != 2)
    {
        debug("Replace conf invalid argc %d\n", argc);
        return 1;
    }
 
    for (i = 0; i < sizeof(loader_conf) / sizeof(loader_conf[0]); i++)
    {
        if (hiperiso_get_file_override(loader_conf[i], &offset) == 0)
        {
            confile = loader_conf[i];
            g_conf_override_offset = offset;
            break;
        }
    }

    if (confile == NULL)
    {   
        debug("Can't find grub.cfg file from %u locations\n", i);
        return 1;
    }

    file = hiperiso_grub_file_open(GRUB_FILE_TYPE_LINUX_INITRD, "(loop)/%s", confile);
    if (!file)
    {
        debug("Failed to open %s \n", confile);
        return 1;
    }

    debug("old grub2 conf file size:%d\n", (int)file->size);

    data = grub_malloc(HISO_MAX_SCRIPT_BUF);
    if (!data)
    {
        grub_file_close(file);    
        return 1;
    }

    grub_file_read(file, data, file->size);
    grub_file_close(file);
    
    g_conf_new_data = data;
    g_conf_new_len = (int)file->size;

    pos = grub_strstr(data, "kfreebsd /boot/kernel/kernel");
    if (pos)
    {
        pos += grub_strlen("kfreebsd /boot/kernel/kernel");
        if (grub_strncmp(pos, ".gz", 3) == 0)
        {
            pos += 3;
        }

        if (argc == 2)
        {
            hiso_ssprintf(extcfg, len, ";kfreebsd_module_elf %s; set kFreeBSD.hint.hiperiso.0.alias=\"%s\"", args[0], args[1]);
        }
        else
        {
            hiso_ssprintf(extcfg, len, ";kfreebsd_module_elf %s", args[0]);
        }

        if (g_unix_hlnk_boot)
        {
            hiso_ssprintf(extcfg, len, ";set kFreeBSD.hint.hiperiso.0.hlnk=%d", 1);
        }

        val = hiperiso_get_env("HISO_UNIX_REMOUNT");
        if (val && val[0] == '1' && val[1] == 0)
        {
            hiso_ssprintf(extcfg, len, ";set kFreeBSD.hint.hiperiso.0.remount=%d", 1);
        }
        
        grub_memmove(pos + len, pos, (int)(file->size - (pos - data)));
        grub_memcpy(pos, extcfg, len);
        g_conf_new_len += len;
    }
    else
    {
        debug("no kfreebsd found\n");
    }
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_replace_conf(grub_extcmd_context_t ctxt, int argc, char **args)
{
    grub_uint32_t i;
    char *data;
    grub_uint64_t offset;
    grub_file_t file;
    const char *confile = NULL;
    const char * loader_conf[] = 
    {
        "/boot/loader.conf",
        "/boot/defaults/loader.conf",
    };

    (void)ctxt;

    if (argc != 2 && argc != 3)
    {
        debug("Replace conf invalid argc %d\n", argc);
        return 1;
    }
 
    for (i = 0; i < sizeof(loader_conf) / sizeof(loader_conf[0]); i++)
    {
        if (hiperiso_get_file_override(loader_conf[i], &offset) == 0)
        {
            confile = loader_conf[i];
            g_conf_override_offset = offset;
            break;
        }
    }

    if (confile == NULL)
    {   
        debug("Can't find loader.conf file from %u locations\n", i);
        return 1;
    }

    file = hiperiso_grub_file_open(GRUB_FILE_TYPE_LINUX_INITRD, "(loop)/%s", confile);
    if (!file)
    {
        debug("Failed to open %s \n", confile);
        return 1;
    }

    debug("old conf file <%s> size:%d\n", confile, (int)file->size);

    data = grub_malloc(HISO_MAX_SCRIPT_BUF);
    if (!data)
    {
        grub_file_close(file);    
        return 1;
    }

    grub_file_read(file, data, file->size);
    grub_file_close(file);
    
    g_conf_new_data = data;
    g_conf_new_len = (int)file->size;

    if (grub_strcmp(args[0], "FreeBSD") == 0)
    {
        g_conf_new_len += hiperiso_freebsd_append_conf(data + file->size, args[1], (argc > 2) ? args[2] : NULL);
    }
    else if (grub_strcmp(args[0], "DragonFly") == 0)
    {
        g_conf_new_len += hiperiso_dragonfly_append_conf(data + file->size, args[1]);
    }
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

static int hiperiso_unix_search_magic(char *data, int len)
{
    int i;
    grub_uint32_t *magic = NULL;    

    for (i = 0; i < len; i += 4096)
    {
        magic = (grub_uint32_t *)(data + i);
        if (magic[0] == HIPERISO_UNIX_SEG_MAGIC0 && magic[1] == HIPERISO_UNIX_SEG_MAGIC1 && 
            magic[2] == HIPERISO_UNIX_SEG_MAGIC2 && magic[3] == HIPERISO_UNIX_SEG_MAGIC3)
        {
            debug("unix find search magic at 0x%x loop:%d\n", i, (i >> 12));
            g_mod_search_magic = i;
            return 0;
        }
    }

    debug("unix can not find search magic\n");
    return 1;
}

grub_err_t hiperiso_cmd_unix_replace_ko(grub_extcmd_context_t ctxt, int argc, char **args)
{
    char *data;
    grub_uint64_t offset;
    grub_file_t file;

    (void)ctxt;

    if (argc != 2)
    {
        debug("Replace ko invalid argc %d\n", argc);
        return 1;
    }

    debug("replace ko %s\n", args[0]);

    if (hiperiso_get_file_override(args[0], &offset) == 0)
    {
        grub_snprintf(g_ko_mod_path, sizeof(g_ko_mod_path), "%s", args[0]);
        g_mod_override_offset = offset;
    }
    else
    {   
        debug("Can't find replace ko file from %s\n", args[0]);
        return 1;
    }

    file = hiperiso_grub_file_open(GRUB_FILE_TYPE_LINUX_INITRD, "%s", args[1]);
    if (!file)
    {
        debug("Failed to open %s \n", args[1]);
        return 1;
    }

    debug("new ko file size:%d\n", (int)file->size);

    data = grub_malloc(file->size);
    if (!data)
    {
        debug("Failed to alloc memory for new ko %d\n", (int)file->size);
        grub_file_close(file);    
        return 1;
    }

    grub_file_read(file, data, file->size);
    grub_file_close(file);
    
    g_mod_new_data = data;
    g_mod_new_len = (int)file->size;

    hiperiso_unix_search_magic(g_mod_new_data, g_mod_new_len);
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_ko_fillmap(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int i;
    grub_file_t file;
    grub_uint32_t magic[4];
    grub_uint32_t len;

    (void)ctxt;

    if (argc != 1)
    {
        debug("Fillmap ko invalid argc %d\n", argc);
        return 1;
    }

    debug("Fillmap ko %s\n", args[0]);

    file = hiperiso_grub_file_open(HIPERISO_FILE_TYPE, "(loop)%s", args[0]);
    if (file)
    {
        grub_file_read(file, magic, 4); /* read for trigger */
        g_mod_override_offset = grub_iso9660_get_last_read_pos(file);
    }
    else
    {   
        debug("Can't find replace ko file from %s\n", args[0]);
        return 1;
    }

    for (i = 0; i < (int)(file->size); i += 65536)
    {
        magic[0] = 0;
        grub_file_seek(file, i);
        grub_file_read(file, magic, sizeof(magic));
    
        if (magic[0] == HIPERISO_UNIX_SEG_MAGIC0 && magic[1] == HIPERISO_UNIX_SEG_MAGIC1 && 
            magic[2] == HIPERISO_UNIX_SEG_MAGIC2 && magic[3] == HIPERISO_UNIX_SEG_MAGIC3)
        {
            debug("unix find search magic at 0x%x loop:%d\n", i, (i >> 16));
            g_mod_override_offset += i;
            break;
        }
    }

    len = (grub_uint32_t)OFFSET_OF(struct g_hiperiso_map, seglist) + 
        (sizeof(struct g_hiperiso_seg) * g_img_chunk_list.cur_chunk);

    g_ko_fillmap_len = (int)len;
    g_ko_fillmap_data = grub_malloc(len);
    if (!g_ko_fillmap_data)
    {
        g_ko_fillmap_len = 0;
        debug("Failed to malloc fillmap data\n");
    }

    debug("Fillmap ko segnum:%u, override len:%u data:%p\n", g_img_chunk_list.cur_chunk, len, g_ko_fillmap_data);

    grub_file_close(file);
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_fill_image_desc(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int i;
    grub_uint8_t *byte;
    grub_uint32_t memsize;
    hiperiso_image_desc *desc;
    grub_uint8_t flag[32] = {
        0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
    };

    (void)ctxt;
    (void)argc;
    (void)args;

    debug("hiperiso_cmd_unix_fill_image_desc %p\n", g_mod_new_data);

    if (!g_mod_new_data)
    {
        goto end;
    }

    byte = (grub_uint8_t *)g_mod_new_data;
    for (i = 0; i < g_mod_new_len - 32; i += 16)
    {
        if (byte[i] == 0xFF && byte[i + 1] == 0xEE)
        {
            if (grub_memcmp(flag, byte + i, 32) == 0)
            {
                debug("Find position flag at %d(0x%x)\n", i, i);
                break;                
            }
        }
    }

    if (i >= g_mod_new_len - 32)
    {
        debug("Failed to find position flag %d\n", i);
        goto end;
    }

    desc = (hiperiso_image_desc *)(byte + i);
    desc->disk_size = g_hiperiso_disk_size;
    desc->part1_size = g_hiperiso_disk_part_size[0];
    grub_memcpy(desc->disk_uuid, g_hiperiso_part_info->MBR.BootCode + 0x180, 16);
    grub_memcpy(desc->disk_signature, g_hiperiso_part_info->MBR.BootCode + 0x1B8, 4);

    desc->img_chunk_count = g_img_chunk_list.cur_chunk;
    memsize = g_img_chunk_list.cur_chunk * sizeof(hiperiso_img_chunk);

    debug("image chunk count:%u  memsize:%u\n", desc->img_chunk_count, memsize);

    if (memsize >= HISO_SIZE_1MB * 8)
    {
        grub_printf("image chunk count:%u  memsize:%u too big\n", desc->img_chunk_count, memsize);
        goto end;
    }

    grub_memcpy(desc + 1, g_img_chunk_list.chunk, memsize);

end:
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_gzip_newko(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int newlen;
    grub_uint8_t *buf;

    (void)ctxt;
    (void)argc;
    (void)args;

    debug("hiperiso_cmd_unix_gzip_newko %p\n", g_mod_new_data);

    if (!g_mod_new_data)
    {
        goto end;
    }

    buf = grub_malloc(g_mod_new_len);
    if (!buf)
    {
        goto end;
    }

    newlen = hiperiso_gzip_compress(g_mod_new_data, g_mod_new_len, buf, g_mod_new_len);

    grub_free(g_mod_new_data);

    debug("gzip org len:%d  newlen:%d\n", g_mod_new_len, newlen);

    g_mod_new_data = (char *)buf;
    g_mod_new_len = newlen;

end:
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

grub_err_t hiperiso_cmd_unix_chain_data(grub_extcmd_context_t ctxt, int argc, char **args)
{
    int hiperiso_compatible = 0;
    grub_uint32_t size = 0;
    grub_uint64_t isosize = 0;
    grub_uint32_t boot_catlog = 0;
    grub_uint32_t img_chunk_size = 0;
    grub_uint32_t override_count = 0;
    grub_uint32_t override_size = 0;
    grub_uint32_t virt_chunk_size = 0;
    grub_file_t file;
    grub_disk_t disk;
    const char *pLastChain = NULL;
    const char *compatible;
    hiperiso_chain_head *chain;
    
    (void)ctxt;
    (void)argc;

    compatible = grub_env_get("hiperiso_compatible");
    if (compatible && compatible[0] == 'Y')
    {
        hiperiso_compatible = 1;
    }

    if (NULL == g_img_chunk_list.chunk)
    {
        grub_printf("hiperiso not ready\n");
        return 1;
    }

    file = hiperiso_grub_file_open(HIPERISO_FILE_TYPE, "%s", args[0]);
    if (!file)
    {
        return 1;
    }

    isosize = file->size;

    boot_catlog = hiperiso_get_iso_boot_catlog(file);
    if (boot_catlog)
    {
        if (hiperiso_is_efi_os() && (!hiperiso_has_efi_eltorito(file, boot_catlog)))
        {
            grub_env_set("LoadIsoEfiDriver", "on");
        }
    }
    else
    {
        if (hiperiso_is_efi_os())
        {
            grub_env_set("LoadIsoEfiDriver", "on");
        }
        else
        {
            return grub_error(GRUB_ERR_BAD_ARGUMENT, "File %s is not bootable", args[0]);
        }
    }
    
    img_chunk_size = g_img_chunk_list.cur_chunk * sizeof(hiperiso_img_chunk);
    
    if (hiperiso_compatible)
    {
        size = sizeof(hiperiso_chain_head) + img_chunk_size;
    }
    else
    {
        override_count = hiperiso_unix_get_override_chunk_count();
        override_size = override_count * sizeof(hiperiso_override_chunk);
        
        virt_chunk_size = hiperiso_unix_get_virt_chunk_size();
        size = sizeof(hiperiso_chain_head) + img_chunk_size + override_size + virt_chunk_size;
    }
    
    pLastChain = grub_env_get("hiso_chain_mem_addr");
    if (pLastChain)
    {
        chain = (hiperiso_chain_head *)grub_strtoul(pLastChain, NULL, 16);
        if (chain)
        {
            debug("free last chain memory %p\n", chain);
            grub_free(chain);
        }
    }

    chain = hiperiso_alloc_chain(size);
    if (!chain)
    {
        grub_printf("Failed to alloc chain unix memory size %u\n", size);
        grub_file_close(file);
        return 1;
    }

    hiperiso_memfile_env_set("hiso_chain_mem", chain, (ulonglong)size);

    grub_memset(chain, 0, sizeof(hiperiso_chain_head));

    /* part 1: os parameter */
    g_hiperiso_chain_type = hiperiso_chain_linux;
    hiperiso_fill_os_param(file, &(chain->os_param));

    /* part 2: chain head */
    disk = file->device->disk;
    chain->disk_drive = disk->id;
    chain->disk_sector_size = (1 << disk->log_sector_size);
    chain->real_img_size_in_bytes = file->size;
    chain->virt_img_size_in_bytes = (file->size + 2047) / 2048 * 2048;
    chain->boot_catalog = boot_catlog;

    if (!hiperiso_is_efi_os())
    {
        grub_file_seek(file, boot_catlog * 2048);
        grub_file_read(file, chain->boot_catalog_sector, sizeof(chain->boot_catalog_sector));
    }

    /* part 3: image chunk */
    chain->img_chunk_offset = sizeof(hiperiso_chain_head);
    chain->img_chunk_num = g_img_chunk_list.cur_chunk;
    grub_memcpy((char *)chain + chain->img_chunk_offset, g_img_chunk_list.chunk, img_chunk_size);

    if (hiperiso_compatible)
    {
        return 0;
    }

    /* part 4: override chunk */
    chain->override_chunk_offset = chain->img_chunk_offset + img_chunk_size;
    chain->override_chunk_num = override_count;
    hiperiso_unix_fill_override_data(isosize, chain);

    /* part 5: virt chunk */
    chain->virt_chunk_offset = chain->override_chunk_offset + override_size;
    chain->virt_chunk_num = hiperiso_unix_get_virt_chunk_count();
    hiperiso_unix_fill_virt_data(isosize, chain);

    grub_file_close(file);
    
    HIPERISO_CMD_RETURN(GRUB_ERR_NONE);
}

