nativeos/kernel/fs/tarfs/tar.c

322 lines
7.1 KiB
C

#include <fs/tarfs/tar.h>
#include <stddef.h>
#include <sys/list.h>
#include <sys/stdkern.h>
#include <sys/vfs.h>
/**
* An internal TAR file node, both as a header block reference for the TAR file
* and as the VFS node that is exposed as the file system.
*/
struct tarfs_node {
vfs_node_t *node;
tar_header_block_t *block;
};
/** The internals associated with a TARFS volume. */
struct tarfs_payload {
/** A pointer to the memory buffer of the TAR file. */
unsigned char *buf;
/** A linked list with all the tarfs_node for all the files. */
list_t *nodes;
/** Points back to the mounted VFS volume backing this payload. */
vfs_volume_t *volume;
/** Points to the root node of the mounted TAR. */
vfs_node_t *root;
};
static unsigned int
octal2int(char *octstring)
{
unsigned int acc = 0;
while (*octstring) {
acc = acc * 8 + (*octstring - '0');
octstring++;
}
return acc;
}
static int
decode_block(struct tarfs_payload *tar, tar_header_block_t *block)
{
struct tarfs_node *tnode;
if ((tnode = malloc(sizeof(struct tarfs_node))) != 0) {
tnode->block = block;
tnode->node = 0;
list_append(tar->nodes, tnode);
return 0;
} else {
return -1;
}
}
static void
split_basename(char *path, char **dirname, char **basename)
{
char *ptr = path, *slash = 0;
while (*ptr) {
if (*ptr == '/') {
slash = ptr;
}
ptr++;
}
if (slash) {
*slash = 0;
*basename = slash + 1;
*dirname = path;
} else {
*basename = path;
*dirname = "";
}
}
static void reassemble_nodes(struct tarfs_payload *tarfile,
struct tarfs_node *node);
static vfs_node_t *
lookup_vnode(struct tarfs_payload *tarfile, const char *path)
{
listnode_t *listnode;
struct tarfs_node *tarnode;
char *kiwi;
list_foreach(tarfile->nodes, listnode)
{
unsigned int last_char;
tarnode = (struct tarfs_node *) listnode->data;
kiwi = tarnode->block->metadata.name;
/* If file name is ./, remove trailing dot. */
if (*kiwi == '.' && *(kiwi + 1) == '/') {
kiwi++;
}
/* If starts with slash, remove it. */
if (*kiwi == '/') {
kiwi++;
}
/* Remove possible trailing slash. */
last_char = strlen(kiwi) - 1;
if (*kiwi && kiwi[last_char] == '/') {
kiwi[last_char] = 0;
}
if (!strncmp(kiwi, path, 256)) {
reassemble_nodes(tarfile, tarnode);
return tarnode->node;
}
}
return NULL;
}
static void
reassemble_nodes(struct tarfs_payload *tarfile, struct tarfs_node *node)
{
char *orig_path, *path, *dirname, *basename;
unsigned int last_char;
vfs_node_t *vnode;
if (!node->node) {
orig_path = strdup(node->block->metadata.name);
path = orig_path;
/* Normalize paths removing absolute dir slashes and dots. */
if (*path == '.' && *(path + 1) == '/') {
path++;
}
if (*path == '/') {
path++;
}
/* Remove possible trailing slash. */
last_char = strlen(path) - 1;
if (*path && path[last_char] == '/') {
path[last_char] = 0;
}
/* Divide my path in dirname and basename. */
vnode = malloc(sizeof(vfs_node_t));
vnode->vn_flags = 0;
if (*path) {
split_basename(path, &dirname, &basename);
strcpy(vnode->vn_name, basename);
vnode->vn_parent = lookup_vnode(tarfile, dirname);
} else {
strcpy(vnode->vn_name, "");
vnode->vn_parent = NULL;
if (!tarfile->root) {
tarfile->root = vnode;
}
}
vnode->vn_flags = 0;
switch (node->block->metadata.type) {
case '0':
vnode->vn_flags |= VN_FREGFILE;
break;
case '5':
vnode->vn_flags |= VN_FDIR;
break;
}
vnode->vn_volume = tarfile->volume;
vnode->vn_payload = node;
node->node = vnode;
free(orig_path);
}
}
static void
init_nodes(struct tarfs_payload *tar)
{
unsigned int bx = 0, file_length;
tar_header_block_t *block = (tar_header_block_t *) tar->buf;
listnode_t *lnode;
/* First we decode all the files in this TAR. */
while (*block[bx].metadata.name) {
decode_block(tar, &block[bx]);
file_length = octal2int(block[bx].metadata.size);
bx += (file_length / 512) + 1;
if ((file_length % 512)) {
bx++;
}
}
/* Then we reassemble the vfs_node_t data structures. */
list_foreach(tar->nodes, lnode)
{
reassemble_nodes(tar, (struct tarfs_node *) lnode->data);
}
}
static int tarfs_mount(vfs_volume_t *volume);
static int tarfs_open(vfs_node_t *node, unsigned int flags);
static unsigned int tarfs_read(vfs_node_t *, unsigned, void *, unsigned);
static unsigned int tarfs_write(vfs_node_t *, unsigned, void *, unsigned);
static int tarfs_close(vfs_node_t *node);
static vfs_node_t *tarfs_readdir(vfs_node_t *node, unsigned int index);
static vfs_node_t *tarfs_finddir(vfs_node_t *node, char *name);
static vfs_ops_t tarfs_ops = {
.vfs_close = &tarfs_close,
.vfs_open = &tarfs_open,
.vfs_read = &tarfs_read,
.vfs_readdir = &tarfs_readdir,
.vfs_finddir = &tarfs_finddir,
};
static vfs_filesys_t tarfs_driver = {
.fsd_ident = "tarfs",
.fsd_name = "TAR File System",
.fsd_mount = &tarfs_mount,
.fsd_ops = &tarfs_ops,
};
FS_DESCRIPTOR(tarfs, tarfs_driver);
static int
tarfs_mount(vfs_volume_t *luna)
{
struct tarfs_payload *payload;
if ((payload = malloc(sizeof(struct tarfs_payload)))) {
payload->buf = luna->vv_payload;
payload->nodes = list_alloc();
payload->volume = luna;
init_nodes(payload);
luna->vv_root = payload->root;
luna->vv_payload = payload;
return 0;
} else {
return -1;
}
}
static int
tarfs_open(vfs_node_t *node, unsigned int flags)
{
/* TODO: Should check the flags, and mark the file as opened. */
return 0;
}
static unsigned int
tarfs_read(vfs_node_t *node, unsigned int offt, void *buf, unsigned int len)
{
unsigned char *data, *dst;
struct tarfs_node *tar = (struct tarfs_node *) node->vn_payload;
unsigned int read = 0, size = octal2int(tar->block->metadata.size);
data = (unsigned char *) &tar->block->metadata + 512;
dst = (unsigned char *) buf;
while (len > 0 && offt < size) {
dst[read++] = data[offt++];
len--;
}
return read;
}
static unsigned int
tarfs_write(vfs_node_t *node, unsigned int offt, void *buf, unsigned int len)
{
return -1;
}
static int
tarfs_close(vfs_node_t *node)
{
return 0;
}
static vfs_node_t *
tarfs_readdir(vfs_node_t *klairm_cocayketa_voltereta,
unsigned int clank_will_not_die)
{
listnode_t *esta_variable_es_del_mejor_mod_de_discord;
struct tarfs_payload *dia_de_pago;
struct tarfs_node *kiwi;
dia_de_pago = (struct tarfs_payload *)
klairm_cocayketa_voltereta->vn_volume->vv_payload;
list_foreach(dia_de_pago->nodes,
esta_variable_es_del_mejor_mod_de_discord)
{
kiwi = (struct tarfs_node *)
esta_variable_es_del_mejor_mod_de_discord->data;
if (kiwi->node->vn_parent == klairm_cocayketa_voltereta) {
if (clank_will_not_die == 0) {
return kiwi->node;
} else {
--clank_will_not_die;
}
}
}
return NULL;
}
static vfs_node_t *
tarfs_finddir(vfs_node_t *node, char *name)
{
struct tarfs_payload *payload;
struct tarfs_node *tarnode;
listnode_t *listnode;
payload = node->vn_volume->vv_payload;
list_foreach(payload->nodes, listnode)
{
tarnode = (struct tarfs_node *) listnode->data;
if (tarnode->node->vn_parent == node
&& !strcmp(name, tarnode->node->vn_name)) {
return tarnode->node;
}
}
return NULL;
}