/* * kcat.c - cat a file on the karma. * run like (1 is part #): * $ kcat karma.img /1/var/state > state */ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #define NUM_PARTS 2 struct part_table { char type; gulong block_ofs; gulong size; }; typedef struct _ctx { FILE *dev; struct part_table parts[2]; int part; guint64 start; guint64 cluster; guint64 cluster_size; } context_t; guint32 get32(char *ptr) { guint32 *ptr32 = (guint32*) ptr; return GUINT32_FROM_BE(*ptr32); } guint64 get64(char *ptr) { guint64 *ptr64 = (guint64*) ptr; return GUINT64_FROM_BE(*ptr64); } /* * recurses at most once (so that it can look at the files under * the current directory).. this is wack but it involved little * new code. return the cluster where found. */ gint64 look_for(FILE *fp, gint64 start, gint64 cluster, int cluster_size, const char *name, int maxlevel) { int i; if (maxlevel < 0) return 0; 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) { goto out; } char type = buf[0x53]; if (type == 'F' || type == 'D') { if (strcmp(&buf[0x098], name) == 0) { // got it. fprintf(stderr, "found %s\n", name); return cluster; } else { // try a sibling gint64 sibling = get64(&buf[0x20]); if (sibling > 0) return look_for(fp, start, sibling, cluster_size, name, maxlevel); } } else { goto out; } int data_size = GUINT_FROM_BE(*(int*) &buf[8]); // This part is really buggy at the moment. int dir_ofs = 0x200; char *dirent = &buf[dir_ofs]; for (i=0; type == 'D' && i<(data_size - dir_ofs)/8; i++) { gint64 nextblk = get64(dirent); if (nextblk > 0) { gint64 res = look_for(fp, start, nextblk, cluster_size, name, maxlevel-1); if (res) return res; } dirent+=8; } out: free(buf); return 0; } void read_extent(context_t *context, gint64 offs, gint64 count, gint64 *size) { if (offs < 0) return; if (*size <= 0) return; char *buf = malloc(context->cluster_size * count); FILE *fp = context->dev; off_t here = ftello(fp); fseeko(fp, context->start + offs * context->cluster_size, SEEK_SET); gint64 to_read = context->cluster_size * count; if (to_read > *size) to_read = *size; fread(buf, 1, to_read, fp); fwrite(buf, 1, to_read, stdout); *size -= to_read; fseeko(fp, here, SEEK_SET); free(buf); } /* * context->cluster points to file */ void display_file(context_t *context) { char *buf; FILE *fp = context->dev; fseeko(context->dev, context->start + context->cluster * context->cluster_size, SEEK_SET); buf = malloc(context->cluster_size); fread(buf, 1, context->cluster_size, fp); if (buf[0x53] != 'F') { fprintf(stderr, "Not a file\n"); } guint64 size = get64(&buf[0x198]); guint64 next_tbl = get64(&buf[0x1D0]); gint32 count = get32(&buf[0x1D8]); char *ext_tbl = &buf[0x1e0]; for(;;) { while (count > 0) { read_extent(context, get64(ext_tbl), get64(ext_tbl+8), &size); ext_tbl += 16; count --; } fprintf(stderr, "next: %llx\n", next_tbl); if (next_tbl == ~0) break; fseeko(context->dev, context->start + next_tbl * context->cluster_size, SEEK_SET); fread(buf, 1, context->cluster_size, fp); guint64 next_tbl = get64(&buf[0x40]); gint32 count = get32(&buf[0x48]); char *ext_tbl = &buf[0x50]; } free(buf); } void select_part(context_t *context) { char buf[338]; FILE *fp = context->dev; guint clusters, cluster_size, rootptr, mirrors; struct part_table *part = &context->parts[context->part]; 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)); context->cluster = rootptr; context->cluster_size = cluster_size; context->start = part->block_ofs * 512; } int read_partitions(FILE *fp, struct part_table *parts) { int i; char buf[16]; gint32 *iptr = (gint32 *) &buf[8]; fseeko(fp, 0x10e, SEEK_SET); for (i=0; itype = buf[4]; parts->block_ofs = GINT_FROM_LE(*iptr); parts->size = GINT_FROM_LE(*(iptr+1)); if (parts->type != 0x4d) { fprintf(stderr, "invalid partition type: %02x\n", parts->type); return 0; } } return 1; } void chdir_path(context_t *context, char *path) { char *orig = strdup(path); char *p = orig; while (*p == '/') p++; while (p) { char *next = strsep(&p, "/"); if (context->part == -1) { int part = atoi(next); if (part < 0 || part >= NUM_PARTS) { fprintf(stderr, "Partition %d is out of range\n", part); return; } context->part = part; select_part(context); continue; } fprintf(stderr, "searching %s\n", next); gint64 ofs = look_for(context->dev, context->start, context->cluster, context->cluster_size, next, 1); if (!ofs) { fprintf(stderr, "Could not find: %s\n", next); context->cluster = 0; return; } context->cluster = ofs; } free(orig); } int main(int argc, char *argv[]) { FILE *fp; int i; context_t context; context.part = -1; context.cluster = 0; if (argc < 3) { fprintf(stderr, "Usage: %s device /#/path/to/file\n", argv[0]); exit(1); } fp = fopen(argv[1], "rb"); if (!fp) { perror("kcat: "); exit(1); } context.dev = fp; if (!read_partitions(context.dev, context.parts)) { exit(1); } chdir_path(&context, argv[2]); if (context.cluster) display_file(&context); fclose(fp); }