#include "tar.h" #include "queue.h" #include #include #include #include #include #include #include #include #include #include struct tar_superblock { struct fs_superblock sb_base; struct tar_bin_header *sb_data; }; struct tar_entry { struct fs_dentry e_dentry; struct fs_inode e_inode; void *e_data; struct queue_entry e_entry; struct queue e_children; }; size_t getsize(const char *in) { size_t size = 0; size_t j; size_t count = 1; for (j = 11; j > 0; j--, count *= 8) { size += ((in[j - 1] - '0') * count); } return size; } int tar_init(struct tar *tar, uintptr_t base) { memset(tar, 0x0, sizeof *tar); tar->tar_entries = (struct tar_bin_header *)base; return 0; } int tar_open(struct tar *tar, const char *path, struct tar_file *out) { while (*path == '/') { path++; } if (*path == '\0') { return -1; } struct tar_bin_header *bin_header = tar->tar_entries; struct tar_header header; for (size_t i = 0;; i++) { tar_header_decode(bin_header, &header); if (bin_header->filename[0] == 0) { break; } char *s = (char *)bin_header; s += sizeof *bin_header; if (!strcmp(bin_header->filename, path)) { out->f_header = header; out->f_data = s; return 0; } s += header.size; s += ((sizeof *bin_header) - ((uintptr_t)s % (sizeof *bin_header))); bin_header = (struct tar_bin_header *)s; } return -1; } int tar_file_create_vm_object(const struct tar_file *file, kern_handle_t *out) { const void *data = file->f_data; size_t len = file->f_header.size; kern_handle_t vmo = KERN_HANDLE_INVALID; kern_status_t status = vm_object_create( NULL, 0, file->f_header.size, VM_PROT_READ | VM_PROT_EXEC | VM_PROT_USER, &vmo); if (status != KERN_OK) { return -1; } size_t nr_written = 0; status = vm_object_write(vmo, data, 0, len, &nr_written); if (status != KERN_OK || nr_written != len) { kern_handle_close(vmo); return -1; } *out = vmo; return 0; } int tar_header_decode(const struct tar_bin_header *in, struct tar_header *out) { memcpy(out->filename, in->filename, sizeof out->filename); out->size = getsize(in->size); return 0; } static struct tar_entry *entry_from_dentry(struct fs_dentry *dent) { return QUEUE_CONTAINER(struct tar_entry, e_dentry, dent); } static struct tar_entry *entry_from_inode(struct fs_inode *inode) { return QUEUE_CONTAINER(struct tar_entry, e_inode, inode); } static struct tar_entry *entry_get_child( struct tar_entry *entry, const char *name) { struct queue_entry *cur = queue_first(&entry->e_children); while (cur) { struct tar_entry *child = QUEUE_CONTAINER(struct tar_entry, e_entry, cur); if (!strcmp(child->e_dentry.d_name, name)) { return child; } cur = queue_next(cur); } return NULL; } static const struct fs_dentry_ops dentry_ops = {}; static enum fs_status dir_lookup( struct fs_inode *inode, const char *name, struct fs_dentry **out) { struct tar_entry *entry = entry_from_inode(inode); struct tar_entry *child = entry_get_child(entry, name); if (!child) { return FS_ERR_NO_ENTRY; } *out = &child->e_dentry; return FS_SUCCESS; } static enum fs_status file_read( struct fs_file *f, xpc_buffer_t *buf, size_t count, off_t *seek) { off_t offset = *seek; struct tar_entry *entry = entry_from_inode(fs_file_get_inode(f)); if (!entry) { return FS_ERR_BAD_STATE; } if (offset >= entry->e_inode.i_size) { count = 0; } else if (offset + count > entry->e_inode.i_size) { count = entry->e_inode.i_size - offset; } if (count == 0) { return FS_SUCCESS; } char *src = (char *)entry->e_data + offset; size_t w; xpc_buffer_write(buf, src, count, &w); offset += count; *seek = offset; return FS_SUCCESS; } static const struct fs_file_ops file_ops = { .f_read = file_read, }; static const struct fs_inode_ops file_inode_ops = { .i_lookup = NULL, }; static const struct fs_inode_ops dir_inode_ops = { .i_lookup = dir_lookup, }; static struct tar_entry *create_dir_entry( struct fs_context *ctx, struct tar_superblock *sb, const char *name) { struct tar_entry *entry = fs_context_alloc(ctx, sizeof *entry); if (!entry) { return NULL; } memset(entry, 0x0, sizeof *entry); entry->e_inode.i_sb = &sb->sb_base; entry->e_inode.i_mode = FS_INODE_DIR; entry->e_inode.i_ops = &dir_inode_ops; entry->e_dentry.d_sb = &sb->sb_base; entry->e_dentry.d_ops = &dentry_ops; entry->e_dentry.d_inode = &entry->e_inode; size_t name_len = strlen(name); entry->e_dentry.d_name = fs_context_alloc(ctx, name_len + 1); if (!entry->e_dentry.d_name) { fs_context_free(ctx, entry); return NULL; } memcpy(entry->e_dentry.d_name, name, name_len + 1); return entry; } static struct tar_entry *create_file_entry( struct fs_context *ctx, struct tar_superblock *sb, struct tar_header *header, void *data, const char *name) { struct tar_entry *entry = fs_context_alloc(ctx, sizeof *entry); if (!entry) { return NULL; } memset(entry, 0x0, sizeof *entry); entry->e_inode.i_sb = &sb->sb_base; entry->e_inode.i_mode = FS_INODE_REG; entry->e_inode.i_ops = &file_inode_ops; entry->e_inode.i_fops = &file_ops; entry->e_inode.i_size = header->size; entry->e_dentry.d_sb = &sb->sb_base; entry->e_dentry.d_ops = &dentry_ops; entry->e_dentry.d_inode = &entry->e_inode; entry->e_data = data; size_t name_len = strlen(name); entry->e_dentry.d_name = fs_context_alloc(ctx, name_len + 1); if (!entry->e_dentry.d_name) { fs_context_free(ctx, entry); return NULL; } memcpy(entry->e_dentry.d_name, name, name_len + 1); return entry; } static size_t get_first_path_component(const char *in, char *out, size_t max) { size_t i = 0; while (i < max - 1) { if (in[i] == '\0' || in[i] == '/') { break; } out[i] = in[i]; i++; } out[i] = '\0'; return i; } static enum fs_status add_entry_to_tree( struct fs_context *ctx, struct tar_superblock *sb, struct tar_header *entry, void *data) { struct tar_entry *cur = entry_from_dentry(sb->sb_base.s_root); const char *path = entry->filename; char tok[256]; while (*path != '\0') { while (*path == '/') { path++; } size_t tok_len = get_first_path_component(path, tok, sizeof tok); if (!tok_len) { break; } struct tar_entry *next = entry_get_child(cur, tok); bool is_file = *(path + tok_len) == '\0'; if (next) { goto next; } if (is_file) { next = create_file_entry(ctx, sb, entry, data, tok); } else { next = create_dir_entry(ctx, sb, tok); } if (!next) { return FS_ERR_NO_MEMORY; } queue_push_back(&cur->e_children, &next->e_entry); next->e_dentry.d_parent = &cur->e_dentry; next: cur = next; path += tok_len; } return FS_SUCCESS; } static enum fs_status build_dentry_tree( struct fs_context *ctx, struct tar_superblock *sb) { struct tar_entry *root = create_dir_entry(ctx, sb, "/"); if (!root) { return FS_ERR_NO_MEMORY; } sb->sb_base.s_root = &root->e_dentry; struct tar_bin_header *bin_header = sb->sb_data; struct tar_header header; enum fs_status status = FS_SUCCESS; for (size_t i = 0;; i++) { tar_header_decode(bin_header, &header); if (bin_header->filename[0] == 0) { break; } char *s = (char *)bin_header; s += sizeof *bin_header; bool add = true; if (!strcmp(header.filename, "././@PaxHeader")) { add = false; } if (add) { status = add_entry_to_tree(ctx, sb, &header, s); } if (status != FS_SUCCESS) { break; } s += header.size; s += ((sizeof *bin_header) - ((uintptr_t)s % (sizeof *bin_header))); bin_header = (struct tar_bin_header *)s; } return FS_SUCCESS; } enum fs_status tar_mount( struct fs_context *ctx, void *arg, enum fs_mount_flags flags, struct fs_superblock **out) { struct tar_superblock *sb = fs_context_alloc(ctx, sizeof *sb); if (!sb) { return FS_ERR_NO_MEMORY; } sb->sb_data = arg; enum fs_status status = build_dentry_tree(ctx, sb); if (status != FS_SUCCESS) { fs_context_free(ctx, sb); return status; } *out = (struct fs_superblock *)sb; return FS_SUCCESS; }