/*
 * build a map in mem of the files 
 */
#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>

#define NUM_PARTS 2

struct part_table
{
    char type;
    gulong block_ofs;
    gulong size;
};

guint64 get64(char *ptr)
{
    gint64 *ptr64 = (gint64*) ptr;
    return GUINT64_FROM_BE(*ptr64);
}

void traverse_inodes(FILE *fp, gint64 start, 
        gint64 cluster, int cluster_size, int level, 
        int plev, const char *parent, int bucket)
{
    int i;
    char *buf = malloc(cluster_size);

    fseeko(fp, start + cluster * cluster_size, SEEK_SET);

    fread(buf, 1, cluster_size, fp);

    // check header
    gint64 me = get64(buf);
    if (me != cluster)
    {
        printf("Incorrect cluster: %llx (wanted  %llx)\n", me, cluster);
        return;
    }

    char type = buf[0x53];
    char *name = &buf[0x98];
    if (type == 'F' || type == 'D')
    {
        int printed = strlen(name) + 4 * level;
        if (plev == level)
        {
            for (i=0; i<level; i++)
                printf("    ");
        }
        else
        {

            for (i=0; i<level-1; i++)
                printf("    ");
            printf("`-- ");
        }
        printf("%s", name);
        if (type == 'D')
        {
            printf("/");
            printed++;
        }
        if (plev != level)
        {
            char bstr[10];
            sprintf(bstr,  " (%d)", bucket);
            printed += strlen(bstr);
            printf("%s", bstr);
        }

        printf("%*s[%06x]\n", 40-printed, " ", cluster);
        
        if (type == 'F')
        {
            // try a sibling
            gint64 sibling = get64(&buf[0x20]);
                
            if (sibling > 0)
                traverse_inodes(fp, start, sibling, cluster_size, level, level, name, 0);
            else printf("\n");
        }
    }
    else
    {
        printf ("type %u at %llu\n", type, ftell(fp));
    }

    int data_size = GUINT_FROM_BE(*(int*) &buf[8]);

    int dir_ofs = 0x1b8;
    char *dirent = &buf[dir_ofs];

    for (i=0; type == 'D' && i<(data_size - dir_ofs)/8; i++)
    {
        gint64 nextblk = get64(dirent);
        if (nextblk > 0)
        {
            traverse_inodes(fp, start, nextblk, cluster_size, level+1, level, name, i);
        }
        dirent+=8;
    }
    free(buf);
}

void display_partition(FILE *fp, int id, struct part_table *part)
{
    char buf[338];
    guint clusters, cluster_size, rootptr, mirrors;

    double size = part->size * 512.0 / 1000000.0;

    fseeko(fp, part->block_ofs * 512, SEEK_SET);

    // signature block follows, just skip to the super
    fseeko(fp, 0x2000, SEEK_CUR);

    fread(buf, 1, sizeof(buf), fp);

    clusters = GUINT_FROM_BE(*(gint*) (buf + 0x24));
    rootptr = GUINT_FROM_BE(*(gint*) (buf + 0x2c));
    cluster_size = GUINT_FROM_BE(*(gint*) (buf + 0x38));
    mirrors = GUINT_FROM_BE(*(gint*) (buf + 0x44));

    traverse_inodes(fp, part->block_ofs * 512, rootptr, cluster_size, 0, 0, "root", 0);
}

int read_partitions(FILE *fp, struct part_table *parts)
{
    struct part_table p;
    char buf[16];
    gint32 *iptr = (gint32 *) &buf[8];
    int i;

    fseeko(fp, 0x10e, SEEK_SET);
    for (i=0; i < NUM_PARTS; i++)
    {
        fread(&buf, 1, sizeof(buf), fp);
        parts[i].type = buf[4];
        parts[i].block_ofs = GINT_FROM_LE(*iptr);
        parts[i].size = GINT_FROM_LE(*(iptr+1));

        if (parts[i].type != 0x4d)
        {
            fprintf(stderr, "invalid partition type: %02x\n", p.type);
            return -1;
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{
    FILE *fp;
    int i;
    struct part_table parts[2];

    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s <device>\n", argv[0]);
        exit(1);
    }

    fp = fopen(argv[1], "rb");

    if (!fp)
    {
        perror("karma_ls: ");
        exit(1);
    }
    if (read_partitions(fp, parts))
    {
        exit(1);
    }
    for (i=0; i<2; i++)
        display_partition(fp, i, &parts[i]);

    fclose(fp);
}

