#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/statfs.h>
#include <dirent.h>
#include <unistd.h>

#include "hlnk.h"

#ifndef PATH_MAX
#define PATH_MAX  4096
#endif

#define IS_DIGIT(x) ((x) >= '0' && (x) <= '9')

static int verbose = 0;
#define debug(fmt, args...) if(verbose) printf(fmt, ##args)

static uint8_t g_hlnk_buf[HLNK_FILE_LEN];

static int64_t get_file_size(char *file)
{
    struct stat stStat;
    
    if (stat(file, &stStat) < 0)
    {
        return -1;
    }

    return (int64_t)(stStat.st_size);
}

static int get_disk_sig(char *diskname, uint32_t *sig)
{
    int fd;
    uint8_t buf[512] = {0};
    
    fd = open(diskname, O_RDONLY);
    if (fd < 0)
    {
        printf("Failed to open %s\n", diskname);
        return 1;
    }

    read(fd, buf, 512);
    close(fd);

    memcpy(sig, buf + 0x1b8, 4);    
    return 0;
}

static int hiso_get_disk_guid(const char *diskname, uint8_t *vtguid, uint8_t *vtsig)
{
    int i = 0;
    int fd = 0;
    char devdisk[128] = {0};

    snprintf(devdisk, sizeof(devdisk) - 1, "/dev/%s", diskname);
    
    fd = open(devdisk, O_RDONLY);
    if (fd >= 0)
    {
        lseek(fd, 0x180, SEEK_SET);
        read(fd, vtguid, 16);
        
        lseek(fd, 0x1b8, SEEK_SET);
        read(fd, vtsig, 4);
        close(fd);
        return 0;
    }
    else
    {
        debug("failed to open %s %d\n", devdisk, errno);
        return errno;
    }
}

static int hiso_is_possible_blkdev(const char *name)
{
    if (name[0] == '.')
    {
        return 0;
    }

    /* /dev/ramX */
    if (name[0] == 'r' && name[1] == 'a' && name[2] == 'm')
    {
        return 0;
    }

    /* /dev/loopX */
    if (name[0] == 'l' && name[1] == 'o' && name[2] == 'o' && name[3] == 'p')
    {
        return 0;
    }

    /* /dev/dm-X */
    if (name[0] == 'd' && name[1] == 'm' && name[2] == '-' && IS_DIGIT(name[3]))
    {
        return 0;
    }

    /* /dev/srX */
    if (name[0] == 's' && name[1] == 'r' && IS_DIGIT(name[2]))
    {
        return 0;
    }
    
    return 1;
}


static int find_disk_by_sig(uint8_t *sig, char *diskname)
{
    int rc = 0;
    int count = 0;
    DIR* dir = NULL;
    struct dirent* p = NULL;
    uint8_t vtguid[16];
    uint8_t vtsig[16];

    dir = opendir("/sys/block");
    if (!dir)
    {
        return 0;
    }
    
    while ((p = readdir(dir)) != NULL)
    {
        if (!hiso_is_possible_blkdev(p->d_name))
        {
            continue;
        }
    
        rc = hiso_get_disk_guid(p->d_name, vtguid, vtsig);
        if (rc == 0 && memcmp(vtsig, sig, 4) == 0)
        {
            sprintf(diskname, "%s", p->d_name);
            count++;
        }
    }
    closedir(dir);
    
    return count;    
}

static uint64_t get_part_offset(char *partname)
{
    int fd;
    uint64_t offset;
    char buf[32] = {0};
    char path[PATH_MAX];

    snprintf(path, PATH_MAX - 1, "/sys/class/block/%s/start", partname);

    fd = open(path, O_RDONLY);
    if (fd < 0)
    {
        return 0;
    }

    read(fd, buf, sizeof(buf));
    close(fd);

    offset = (uint64_t)strtoull(buf, NULL, 10);
    offset *= 512;

    return offset;
}

static int create_hlnk(char *infile, char *diskname, uint64_t partoff, char *outfile)
{
    FILE *fp;
    int len;
    uint32_t sig = 0;

    debug("create hlnk\n");
    
    if (infile[0] == 0 || outfile[0] == 0 || diskname[0] == 0 || partoff == 0)
    {
        debug("Invalid parameters: %d %d %d %llu\n", infile[0], outfile[0], diskname[0], (unsigned long long)partoff);
        return 1;
    }

    len = (int)strlen(infile);
    if (len >= HLNK_NAME_MAX)
    {
        printf("File name length %d is too long for hlnk!\n", len);
        return 1;
    }

    if (get_disk_sig(diskname, &sig))
    {
        printf("Failed to read disk sig\n");
        return 1;
    }

    fp = fopen(outfile, "wb+");
    if (!fp)
    {
        printf("Failed to create file %s\n", outfile);
        return 1;
    }

    memset(g_hlnk_buf, 0, sizeof(g_hlnk_buf));
    hiperiso_create_hlnk(sig, partoff, infile, (hiperiso_hlnk *)g_hlnk_buf);
    fwrite(g_hlnk_buf, 1, HLNK_FILE_LEN, fp);
    fclose(fp);

    return 0;
}

static int get_mount_point(char *partname, char *mntpoint)
{
    int i;
    int len;
    int rc = 1;
    FILE *fp = NULL;
    char line[PATH_MAX];

    fp = fopen("/proc/mounts", "r");
    if (!fp)
    {
        return 1;
    }

    len = (int)strlen(partname);
    while (fgets(line, sizeof(line), fp))
    {
        if (strncmp(line, partname, len) == 0)
        {
            for (i = len; i < PATH_MAX && line[i]; i++)
            {
                if (line[i] == ' ')
                {
                    line[i] = 0;
                    rc = 0;
                    strncpy(mntpoint, line + len, PATH_MAX - 1);                    
                    break;
                }
            }
            break;
        }
    }

    fclose(fp);
    return rc;
}

static int parse_hlnk(char *infile)
{
    int i;
    int fd;
    int cnt;
    int pflag = 0;
    char diskname[128] = {0};
    char partname[128] = {0};
    char partpath[256] = {0};
    char mntpoint[PATH_MAX];
    hiperiso_hlnk hlnk;

    debug("parse hlnk\n");
    
    if (infile[0] == 0)
    {
        debug("input file null\n");
        return 1;
    }

    fd = open(infile, O_RDONLY);
    if (fd < 0)
    {
        printf("Failed to open file %s error %d\n", infile, errno);
        return 1;
    }

    memset(&hlnk, 0, sizeof(hlnk));
    read(fd, &hlnk, sizeof(hlnk));
    close(fd);

    debug("disk_signature:%08X\n", hlnk.disk_signature);
    debug("file path:<%s>\n", hlnk.filepath);
    debug("part offset: %llu\n", (unsigned long long)hlnk.part_offset);
    
    cnt = find_disk_by_sig((uint8_t *)&(hlnk.disk_signature), diskname);
    if (cnt != 1)
    {
        printf("Disk in hlnk not found!\n");
        return 1;
    }

    debug("Disk is <%s>\n", diskname);

    if (strstr(diskname, "nvme") || strstr(diskname, "mmc") || strstr(diskname, "nbd"))
    {
        pflag = 1;
    }

    for (i = 1; i <= 128; i++)
    {
        if (pflag)
        {
            snprintf(partname, sizeof(partname) - 1, "%sp%d", diskname, i);            
        }
        else
        {
            snprintf(partname, sizeof(partname) - 1, "%s%d", diskname, i);            
        }

        if (get_part_offset(partname) == hlnk.part_offset)
        {
            debug("Find correct partition </dev/%s>\n", partname);
            break;
        }
    }

    if (i > 128)
    {
        printf("Partition in hlnk not found!");
        return 1;
    }

    snprintf(partpath, sizeof(partpath), "/dev/%s ", partname);
    if (get_mount_point(partpath, mntpoint))
    {
        printf("Mountpoint of %s is not found!\n", partpath);
        return 1;
    }
    debug("moutpoint of %s is <%s>\n", partpath, mntpoint);

    strcat(mntpoint, hlnk.filepath);
    printf("Hlnk Point: %s\n", mntpoint);
    if (access(mntpoint, F_OK) >= 0)
    {
        printf("File Exist: YES\n");    
    }
    else
    {
        printf("File Exist: NO\n");    
    }

    return 0;
}

static int check_hlnk(char *infile)
{
    int fd;
    int64_t size;
    hiperiso_hlnk hlnk;

    debug("check hlnk\n");
    
    if (infile[0] == 0)
    {
        debug("input file null\n");
        return 1;
    }

    size = get_file_size(infile);
    if (size != HLNK_FILE_LEN)
    {
        debug("file size %lld is not a hlnk file size\n", (long long)size);
        return 1;
    }

    fd = open(infile, O_RDONLY);
    if (fd < 0)
    {
        debug("Failed to open file %s error %d\n", infile, errno);
        return 1;
    }

    memset(&hlnk, 0, sizeof(hlnk));
    read(fd, &hlnk, sizeof(hlnk));
    close(fd);

    if (CheckHlnkData(&hlnk))
    {
        return 0;
    }

    return 1;
}

int main(int argc, char **argv)
{
    int ch = 0;
    int cmd = 0;
    uint64_t partoff = 0;
    char infile[PATH_MAX] = {0};
    char outfile[PATH_MAX] = {0};
    char diskname[256] = {0};

    while ((ch = getopt(argc, argv, "c:t:l:d:p:o:v::")) != -1)
    {
        if (ch == 'c')
        {
            cmd = 1;
            strncpy(infile, optarg, sizeof(infile) - 1);
        }
        else if (ch == 'o')
        {
            strncpy(outfile, optarg, sizeof(outfile) - 1);
        }
        else if (ch == 'l')
        {
            cmd = 2;
            strncpy(infile, optarg, sizeof(infile) - 1);
        }
        else if (ch == 't')
        {
            cmd = 3;
            strncpy(infile, optarg, sizeof(infile) - 1);
        }        
        else if (ch == 'd')
        {
            strncpy(diskname, optarg, sizeof(diskname) - 1);
        }
        else if (ch == 'p')
        {
            partoff = (uint64_t)strtoull(optarg, NULL, 10);
            partoff *= 512;
        }
        else if (ch == 'v')
        {
            verbose = 1;
        }
        else
        {
            return 1;
        }
    }

    if (cmd == 1)
    {
        return create_hlnk(infile, diskname, partoff, outfile);
    }
    else if (cmd == 2)
    {
        return parse_hlnk(infile);
    }
    else if (cmd == 3)
    {
        return check_hlnk(infile);
    }
    else
    {
        printf("Invalid command %d\n", cmd);
        return 1;
    }
}

