2026-02-19 19:31:15 +00:00
|
|
|
#include "tar.h"
|
|
|
|
|
|
2026-03-10 19:16:22 +00:00
|
|
|
#include "queue.h"
|
|
|
|
|
|
|
|
|
|
#include <fs/dentry.h>
|
|
|
|
|
#include <fs/file.h>
|
|
|
|
|
#include <fs/inode.h>
|
|
|
|
|
#include <fs/superblock.h>
|
2026-02-19 19:31:15 +00:00
|
|
|
#include <mango/handle.h>
|
2026-03-10 19:16:22 +00:00
|
|
|
#include <mango/log.h>
|
2026-02-19 19:31:15 +00:00
|
|
|
#include <mango/vm.h>
|
2026-03-10 19:16:22 +00:00
|
|
|
#include <stdio.h>
|
2026-02-19 19:31:15 +00:00
|
|
|
#include <string.h>
|
2026-03-10 19:16:22 +00:00
|
|
|
#include <xpc/buffer.h>
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|
2026-02-19 19:31:15 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-03-10 19:16:22 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|