diff options
author | Natanael Copa <ncopa@alpinelinux.org> | 2011-03-01 08:25:24 +0000 |
---|---|---|
committer | Natanael Copa <ncopa@alpinelinux.org> | 2011-03-01 08:25:24 +0000 |
commit | 04de523b9c04675560f33a1ebfd8e80ee2db2bb8 (patch) | |
tree | df68eab906deeabe9bb1cf9126fcc843fc3bb489 /uniso.c | |
parent | 40e4d11f385a6e4d9672e57f14c6b9c1a0f918a8 (diff) | |
download | alpine-conf-04de523b9c04675560f33a1ebfd8e80ee2db2bb8.zip |
added uniso
A tool for extracting files from iso images frrom a stream.
Will be used for setup-bootable.
Diffstat (limited to 'uniso.c')
-rw-r--r-- | uniso.c | 571 |
1 files changed, 571 insertions, 0 deletions
@@ -0,0 +1,571 @@ +/* uniso.c - Unpack ISO9660 File System from a stream + * + * Copyright (C) 2011 Timo Teräs <timo.teras@iki.fi> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +/* Compile with: + * gcc -std=gnu99 -D_GNU_SOURCE -D_BSD_SOURCE -O2 uniso.c -o uniso */ + +/* + * TODO: + * - fix unaligned 16-bit accesses from iso headers (ARM / MIPS) + * - help, options, verbose logs + */ + +/* needed for SPLICE_F_MOVE */ +#define _GNU_SOURCE + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <wchar.h> +#include <malloc.h> +#include <unistd.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <endian.h> +#include <sys/stat.h> + +/**/ + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +#define LIST_POISON1 (void *) 0xdeadbeef +#define LIST_POISON2 (void *) 0xabbaabba + +struct list_head { + struct list_head *next, *prev; +}; + +static inline void list_init(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +static inline int list_hashed(const struct list_head *n) +{ + return n->next != n && n->next != NULL; +} + +#define list_entry(ptr, type, member) container_of(ptr,type,member) + +/**/ + +#define ISOFS_BLOCK_SIZE 2048 +#define ISOFS_TMPBUF_SIZE (32*ISOFS_BLOCK_SIZE) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define endianess le +#else +#define endianess be +#endif + +struct isofs723 { + u_int16_t le; + u_int16_t be; +}; + +struct isofs731 { + u_int32_t le; +}; + +struct isofs732 { + u_int32_t be; +}; + +struct isofs733 { + u_int32_t le; + u_int32_t be; +}; + +#define ISOFS_VD_PRIMARY 1 +#define ISOFS_VD_SUPPLEMENTARY 2 +#define ISOFS_VD_END 255 +#define ISOFS_STANDARD_ID "CD001" + +struct isofs_volume_descriptor { + unsigned char type; + unsigned char id[5]; + unsigned char version; + unsigned char data[2040]; +}; + +/* Primary or Supplementary Volume Descriptor + * (they are about the same, Supplementary just has few more fields + * and the date they point to might be encoded differently) */ +struct isofs_pri_sup_descriptor { + unsigned char type; + unsigned char id[5]; + unsigned char version; + char flags; /* unused1 in primary */ + char system_id[32]; + char volume_id[32]; + char unused2[8]; + struct isofs733 volume_space_size; + char escape[32]; /* unused3 in primary */ + struct isofs723 volume_set_size; + struct isofs723 volume_sequence_number; + struct isofs723 logical_block_size; + struct isofs733 path_table_size; + struct isofs731 type_l_path_table, opt_type_l_path_table; + struct isofs732 type_m_path_table, opt_type_m_path_table; + char root_directory_record[34]; + char volume_set_id[128]; + char publisher_id[128]; + char preparer_id[128]; + char application_id[128]; + char copyright_file_id[37]; + char abstract_file_id[37]; + char bibliographic_file_id[37]; + char creation_date[17]; + char modification_date[17]; + char expiration_date[17]; + char effective_date[17]; + char file_structure_version; + char unused4[1]; + char application_data[512]; + char unused5[653]; +}; + +struct isofs_directory_record { + unsigned char length; + unsigned char ext_attr_length; + struct isofs733 extent; + struct isofs733 size; + char date[7]; + char flags; +#define ISOFS_DR_FLAG_DIRECTORY (1<<1) + unsigned char file_unit_size; + unsigned char interleave; + struct isofs723 volume_sequence_number; + unsigned char name_len; + char name[0]; +} __attribute__((packed)); + +struct uniso_reader; +struct uniso_context; + +typedef int (*uniso_handler_f)(struct uniso_context *, struct uniso_reader *); + +struct uniso_reader { + struct list_head parser_list; + size_t offset; + uniso_handler_f handler; +}; + +struct uniso_dirent { + struct uniso_reader reader; + u_int32_t size; + u_int32_t flags; + char name[0]; +}; + +struct uniso_context { + int stream_fd, null_fd; + int skip_method, copy_method; + int joliet_level, loglevel; + size_t pos, last_queued_read; + struct list_head parser_head; + struct uniso_reader volume_desc_reader; + unsigned char *tmpbuf; +}; + +static int do_splice(struct uniso_context *ctx, int to_fd, int bytes) +{ + int r; + + do { + r = splice(ctx->stream_fd, NULL, to_fd, NULL, bytes, + SPLICE_F_MOVE); + if (r < 0) + return -errno; + + bytes -= r; + ctx->pos += r; + } while (bytes != 0); + + return 0; +} + +static int do_read(struct uniso_context *ctx, unsigned char *buf, int bytes) +{ + int r; + + do { + r = read(ctx->stream_fd, buf, bytes); + if (r < 0) { + perror("read"); + return -errno; + } + bytes -= r; + buf += r; + ctx->pos += r; + } while (bytes != 0); + + return 0; +} + +static int do_write(int tofd, const unsigned char *buf, int bytes) +{ + int r; + + do { + r = write(tofd, buf, bytes); + if (r < 0) { + perror("write"); + return -errno; + } + bytes -= r; + buf += r; + } while (bytes != 0); + + return 0; +} + +static int do_skip(struct uniso_context *ctx, unsigned int bytes) +{ + int r, now; + + switch (ctx->skip_method) { + case 0: + if (lseek(ctx->stream_fd, bytes, SEEK_CUR) != (off_t) -1) { + ctx->pos += bytes; + break; + } + ctx->skip_method = 1; + case 1: + if (do_splice(ctx, ctx->null_fd, bytes) == 0) + break; + ctx->skip_method = 2; + case 2: + while (bytes) { + now = bytes; + if (now > ISOFS_TMPBUF_SIZE) + now = ISOFS_TMPBUF_SIZE; + + r = do_read(ctx, ctx->tmpbuf, now); + if (r < 0) + return r; + + bytes -= now; + } + break; + } + return 0; +} + +static int do_copy(struct uniso_context *ctx, int tofd, unsigned int bytes) +{ + int r, now; + + switch (ctx->copy_method) { + case 0: + if (do_splice(ctx, tofd, bytes) == 0) + break; + ctx->skip_method = 1; + case 1: + /* FIXME: Use mmaped IO */ + ctx->skip_method = 2; + case 2: + while (bytes) { + now = bytes; + if (now > ISOFS_TMPBUF_SIZE) + now = ISOFS_TMPBUF_SIZE; + + r = do_read(ctx, ctx->tmpbuf, now); + if (r < 0) + return r; + + r = do_write(tofd, ctx->tmpbuf, now); + if (r < 0) + return r; + + bytes -= now; + } + break; + } + return 0; +} + +static int queue_reader( + struct uniso_context *ctx, struct uniso_reader *reader, + size_t offset, uniso_handler_f handler) +{ + struct list_head *n; + struct uniso_reader *r; + + if (offset < ctx->pos) { + fprintf(stderr, "ERROR: non-linear reads are not supported " + "(asked %x, we are at %x)\n", + offset, ctx->last_queued_read); + return -1; + } + + reader->offset = offset; + reader->handler = handler; + list_init(&reader->parser_list); + + if (offset >= ctx->last_queued_read || !list_hashed(&ctx->parser_head)) { + ctx->last_queued_read = offset; + list_add_tail(&reader->parser_list, &ctx->parser_head); + return 0; + } + + for (n = ctx->parser_head.prev; n != &ctx->parser_head; n = n->prev) { + r = list_entry(n, struct uniso_reader, parser_list); + if (r->offset < offset) + break; + } + list_add(&reader->parser_list, n); + + return 0; +} + +static int queue_dirent(struct uniso_context *ctx, void *isode, const char *name); + +static int uniso_read_file(struct uniso_context *ctx, + struct uniso_reader *rd) +{ + struct uniso_dirent *dir = container_of(rd, struct uniso_dirent, reader); + int fd, rc; + + // FIXME: seems that hardlinked files get the same file extent + // shared. need to fix extraction of such files. + if (ctx->pos > rd->offset) { + if (ctx->loglevel > 0) + fprintf(stderr, "WARNING: Not extracting '%s' as " + "it's sharing file-extents with " + "another file\n", dir->name); + return 0; + } + + fd = open(dir->name, O_WRONLY | O_TRUNC | O_CREAT, 0777); + if (fd < 0) + return -errno; + + if (ctx->loglevel > 1) + fprintf(stderr, "%s : %d bytes, flags 0x%08x\n", + dir->name, dir->size, dir->flags); + rc = do_copy(ctx, fd, dir->size); + close(fd); + + return rc; +} + +static int uniso_read_directory(struct uniso_context *ctx, + struct uniso_reader *rd) +{ + struct uniso_dirent *dir = container_of(rd, struct uniso_dirent, reader); + struct isofs_directory_record *ide; + char buf[512], *name; + char *tmp; + int r, i, offs; + + if (ctx->loglevel > 1) + fprintf(stderr, "%s : DIR, flags 0x%08x\n", + dir->name, dir->flags); + mkdir(dir->name, 0777); + + tmp = malloc(dir->size); + if (tmp == NULL) { + r = -ENOMEM; + goto ret_nomem; + } + + r = do_read(ctx, tmp, dir->size); + if (r < 0) + goto ret; + + strcpy(buf, dir->name); + strcat(buf, "/"); + + for (offs = 0; offs < dir->size; ) { + ide = (void *) &tmp[offs]; + + offs += (ide->length + 1) & ~1; + if (ide->length == 0) { + offs &= ~(ISOFS_BLOCK_SIZE-1); + offs += ISOFS_BLOCK_SIZE; + continue; + } + + /* Skip '.' and '..' entries */ + if ((ide->name_len == 0) || + (ide->name_len == 1 && ide->name[0] <= 1)) + continue; + + name = &buf[strlen(dir->name) + 1]; + if (ctx->joliet_level) { + u_int16_t *wname = (u_int16_t *) ide->name; + for (i = 0; i < ide->name_len; i += 2, wname++) { + r = wctomb(name, be16toh(*wname)); + if (r == -1) + *name++ = '?'; + else + name += r; + } + *name = 0; + } else { + memcpy(name, ide->name, ide->name_len); + name[ide->name_len] = 0; + } + + queue_dirent(ctx, ide, buf); + } + r = 0; + +ret: + free(tmp); +ret_nomem: + free(dir); + return r; +} + +static int queue_dirent(struct uniso_context *ctx, void *isode, const char *name) +{ + struct isofs_directory_record *ide = isode; + struct uniso_dirent *dir; + + dir = calloc(1, sizeof(*dir) + strlen(name) + 1); + if (dir == NULL) + return -ENOMEM; + + dir->size = ide->size.endianess; + dir->flags = ide->flags; + strcpy(dir->name, name); + + return queue_reader(ctx, &dir->reader, + ide->extent.endianess * ISOFS_BLOCK_SIZE, + (ide->flags & ISOFS_DR_FLAG_DIRECTORY) ? + uniso_read_directory : uniso_read_file); +} + +static int uniso_read_volume_descriptor(struct uniso_context *ctx, + struct uniso_reader *rd) +{ + char buf[ISOFS_BLOCK_SIZE]; + struct isofs_volume_descriptor *vd = (void*) buf; + struct isofs_pri_sup_descriptor *pd = (void*) buf; + char root_dir[sizeof(pd->root_directory_record)]; + int r; + + do { + r = do_read(ctx, buf, ISOFS_BLOCK_SIZE); + if (r < 0) + return r; + + if (memcmp(vd->id, ISOFS_STANDARD_ID, sizeof(vd->id)) != 0) + return -EMEDIUMTYPE; + + switch (vd->type) { + case ISOFS_VD_PRIMARY: + if (ctx->joliet_level == 0) + memcpy(root_dir, pd->root_directory_record, sizeof(root_dir)); + break; + case ISOFS_VD_SUPPLEMENTARY: + if (pd->escape[0] == 0x25 && pd->escape[1] == 0x2f) { + switch (pd->escape[2]) { + case 0x45: + ctx->joliet_level++; + case 0x43: + ctx->joliet_level++; + case 0x40: + ctx->joliet_level++; + memcpy(root_dir, pd->root_directory_record, sizeof(root_dir)); + } + } + break; + } + } while (vd->type != ISOFS_VD_END); + + if (ctx->joliet_level && ctx->loglevel > 1) + fprintf(stderr, "INFO: Microsoft Joliet Level %d\n", + ctx->joliet_level); + + return queue_dirent(ctx, root_dir, "."); +} + +int uniso(int fd) +{ + struct uniso_context context, *ctx = &context; + struct uniso_reader *rd; + int r; + + memset(ctx, 0, sizeof(*ctx)); + list_init(&ctx->parser_head); + ctx->stream_fd = fd; + ctx->null_fd = open("/dev/null", O_RDWR); + ctx->tmpbuf = malloc(ISOFS_TMPBUF_SIZE); + ctx->loglevel = 1; + + queue_reader(ctx, &ctx->volume_desc_reader, + 16 * ISOFS_BLOCK_SIZE, + uniso_read_volume_descriptor); + + while (list_hashed(&ctx->parser_head)) { + rd = list_entry(ctx->parser_head.next, struct uniso_reader, parser_list); + list_del(&rd->parser_list); + + r = rd->offset - ctx->pos; + if (r > 0) { + if ((r = do_skip(ctx, r)) < 0) + return r; + } + r = rd->handler(ctx, rd); + if (r != 0) + return r; + } + + free(ctx->tmpbuf); + close(ctx->null_fd); +} + +int main(void) +{ + uniso(STDIN_FILENO); + return 0; +} |