Browse Source

tarfs: new filesystem driver for read-only ustar mounting

K. Lange 3 years ago
parent
commit
f993a753cb
2 changed files with 486 additions and 0 deletions
  1. 1 0
      boot/cstuff.c
  2. 485 0
      modules/tarfs.c

+ 1 - 0
boot/cstuff.c

@@ -84,6 +84,7 @@ static char * modules[] = {
 	"E1000.KO",    // 21
 	"PCSPKR.KO",   // 22
 	"PORTIO.KO",   // 23
+	"TARFS.KO",    // 24
 	0
 };
 

+ 485 - 0
modules/tarfs.c

@@ -0,0 +1,485 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * tarfs - Allows read-only mounting of ustar archives
+ */
+#include <kernel/system.h>
+#include <kernel/types.h>
+#include <kernel/fs.h>
+#include <kernel/logging.h>
+#include <kernel/module.h>
+#include <kernel/args.h>
+#include <kernel/printf.h>
+#include <kernel/tokenize.h>
+
+#include <toaru/list.h>
+#include <toaru/hashmap.h>
+
+#define TARFS_LOG_LEVEL WARNING
+
+struct tarfs {
+	fs_node_t * device;
+	unsigned int length;
+};
+
+struct ustar {
+	char filename[100];
+	char mode[8];
+	char ownerid[8];
+	char groupid[8];
+
+	char size[12];
+	char mtime[12];
+
+	char checksum[8];
+	char type[1];
+	char link[100];
+
+	char ustar[6];
+	char version[2];
+
+	char owner[32];
+	char group[32];
+
+	char dev_major[8];
+	char dev_minor[8];
+
+	char prefix[155];
+};
+
+static unsigned int interpret_uid(struct ustar * file) {
+	return 
+		((file->ownerid[0] - '0') << 18) |
+		((file->ownerid[1] - '0') << 15) |
+		((file->ownerid[2] - '0') << 12) |
+		((file->ownerid[3] - '0') <<  9) |
+		((file->ownerid[4] - '0') <<  6) |
+		((file->ownerid[5] - '0') <<  3) |
+		((file->ownerid[6] - '0') <<  0);
+}
+
+static unsigned int interpret_gid(struct ustar * file) {
+	return 
+		((file->groupid[0] - '0') << 18) |
+		((file->groupid[1] - '0') << 15) |
+		((file->groupid[2] - '0') << 12) |
+		((file->groupid[3] - '0') <<  9) |
+		((file->groupid[4] - '0') <<  6) |
+		((file->groupid[5] - '0') <<  3) |
+		((file->groupid[6] - '0') <<  0);
+}
+
+static unsigned int interpret_mode(struct ustar * file) {
+	return 
+		((file->mode[0] - '0') << 18) |
+		((file->mode[1] - '0') << 15) |
+		((file->mode[2] - '0') << 12) |
+		((file->mode[3] - '0') <<  9) |
+		((file->mode[4] - '0') <<  6) |
+		((file->mode[5] - '0') <<  3) |
+		((file->mode[6] - '0') <<  0);
+}
+
+static unsigned int interpret_size(struct ustar * file) {
+	return
+		((file->size[ 0] - '0') << 30) |
+		((file->size[ 1] - '0') << 27) |
+		((file->size[ 2] - '0') << 24) |
+		((file->size[ 3] - '0') << 21) |
+		((file->size[ 4] - '0') << 18) |
+		((file->size[ 5] - '0') << 15) |
+		((file->size[ 6] - '0') << 12) |
+		((file->size[ 7] - '0') <<  9) |
+		((file->size[ 8] - '0') <<  6) |
+		((file->size[ 9] - '0') <<  3) |
+		((file->size[10] - '0') <<  0);
+}
+
+static unsigned int round_to_512(unsigned int i) {
+	unsigned int t = i % 512;
+
+	if (!t) return i;
+	return i + (512 - t);
+}
+
+struct ustar * ustar_from_offset(struct tarfs * self, unsigned int offset);
+static fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset);
+
+static char filename_workspace[256];
+
+#ifndef strncat
+static char * strncat(char *dest, const char *src, size_t n) {
+	char * end = dest;
+	while (*end != '\0') {
+		++end;
+	}
+	size_t i = 0;
+	while (*src && i < n) {
+		*end = *src;
+		end++;
+		src++;
+		i++;
+	}
+	*end = '\0';
+	return dest;
+}
+#endif
+
+static int count_slashes(char * string) {
+	int i = 0;
+	char * s = strstr(string, "/");
+	while (s) {
+		if (*(s+1) == '\0') return i;
+		i++;
+		s = strstr(s+1,"/");
+	}
+	return i;
+}
+
+static struct dirent * readdir_tar_root(fs_node_t *node, uint32_t index) {
+	if (index == 0) {
+		struct dirent * out = malloc(sizeof(struct dirent));
+		memset(out, 0x00, sizeof(struct dirent));
+		out->ino = 0;
+		strcpy(out->name, ".");
+		return out;
+	}
+
+	if (index == 1) {
+		struct dirent * out = malloc(sizeof(struct dirent));
+		memset(out, 0x00, sizeof(struct dirent));
+		out->ino = 0;
+		strcpy(out->name, "..");
+		return out;
+	}
+
+	index -= 2;
+
+	struct tarfs * self = node->device;
+	/* Go through each file and pick the ones are at the root */
+	/* Root files will have no /, so this is easy */
+	unsigned int offset = 0;
+	while (offset < self->length) {
+		struct ustar * file = ustar_from_offset(self, offset);
+
+		if (!file) {
+			return NULL;
+		}
+
+		memset(filename_workspace, 0, 256);
+		strncat(filename_workspace, file->prefix, 155);
+		strncat(filename_workspace, file->filename, 100);
+
+		if (!count_slashes(filename_workspace)) {
+			char * slash = strstr(filename_workspace,"/");
+			if (slash) *slash = '\0'; /* remove trailing slash */
+			if (strlen(filename_workspace)) {
+				if (index == 0) {
+					struct dirent * out = malloc(sizeof(struct dirent));
+					memset(out, 0x00, sizeof(struct dirent));
+					out->ino = offset;
+					strcpy(out->name, filename_workspace);
+					return out;
+				} else {
+					index--;
+				}
+			}
+		}
+
+		offset += 512;
+		offset += round_to_512(interpret_size(file));
+
+	}
+
+	return NULL;
+}
+
+static uint32_t read_tarfs(fs_node_t * node, uint64_t offset, uint32_t size, uint8_t * buffer) {
+	struct tarfs * self = node->device;
+	struct ustar * file = ustar_from_offset(self, node->inode);
+	size_t file_size = interpret_size(file);
+
+	if (offset > file_size) return 0;
+	if (offset + size > file_size) {
+		size = file_size - offset;
+	}
+
+	return read_fs(self->device, offset + node->inode + 512, size, buffer);
+}
+
+static char my_filename[256];
+static struct dirent * readdir_tarfs(fs_node_t *node, uint32_t index) {
+	if (index == 0) {
+		struct dirent * out = malloc(sizeof(struct dirent));
+		memset(out, 0x00, sizeof(struct dirent));
+		out->ino = 0;
+		strcpy(out->name, ".");
+		return out;
+	}
+
+	if (index == 1) {
+		struct dirent * out = malloc(sizeof(struct dirent));
+		memset(out, 0x00, sizeof(struct dirent));
+		out->ino = 0;
+		strcpy(out->name, "..");
+		return out;
+	}
+
+	index -= 2;
+
+	struct tarfs * self = node->device;
+
+	/* Go through each file and pick the ones are at the root */
+	/* Root files will have no /, so this is easy */
+	unsigned int offset = 0;
+
+	/* Read myself */
+	struct ustar * directory = ustar_from_offset(self, node->inode);
+
+	/* Figure out my own filename, with forward slash */
+	memset(my_filename, 0, 256);
+	strncat(my_filename, directory->prefix, 155);
+	strncat(my_filename, directory->filename, 100);
+
+	while (offset < self->length) {
+		struct ustar * file = ustar_from_offset(self, offset);
+
+		if (!file) {
+			return NULL;
+		}
+
+		memset(filename_workspace, 0, 256);
+		strncat(filename_workspace, file->prefix, 155);
+		strncat(filename_workspace, file->filename, 100);
+
+		if (startswith(filename_workspace, my_filename)) {
+			if (!count_slashes(filename_workspace + strlen(my_filename))) {
+				if (strlen(filename_workspace + strlen(my_filename))) {
+					if (index == 0) {
+						char * slash = strstr(filename_workspace+strlen(my_filename),"/");
+						if (slash) *slash = '\0'; /* remove trailing slash */
+						struct dirent * out = malloc(sizeof(struct dirent));
+						memset(out, 0x00, sizeof(struct dirent));
+						out->ino = offset;
+						strcpy(out->name, filename_workspace+strlen(my_filename));
+						return out;
+					} else {
+						index--;
+					}
+				}
+			}
+		}
+
+		offset += 512;
+		offset += round_to_512(interpret_size(file));
+	}
+
+	return NULL;
+}
+
+static fs_node_t * finddir_tarfs(fs_node_t *node, char *name) {
+	struct tarfs * self = node->device;
+
+	/* find my own filename */
+	struct ustar * directory = ustar_from_offset(self, node->inode);
+
+	/* Figure out my own filename, with forward slash */
+	memset(my_filename, 0, 256);
+	strncat(my_filename, directory->prefix, 155);
+	strncat(my_filename, directory->filename, 100);
+
+	/* Append name */
+	strncat(my_filename, name, strlen(name));
+	if (strlen(my_filename) > 255) {
+		debug_print(CRITICAL, "what");
+	}
+
+	unsigned int offset = 0;
+	while (offset < self->length) {
+		struct ustar * file = ustar_from_offset(self, offset);
+
+		if (!file) {
+			return NULL;
+		}
+
+		memset(filename_workspace, 0, 256);
+		strncat(filename_workspace, file->prefix, 155);
+		strncat(filename_workspace, file->filename, 100);
+
+		if (filename_workspace[strlen(filename_workspace)-1] == '/') {
+			filename_workspace[strlen(filename_workspace)-1] = '\0';
+		}
+		if (!strcmp(filename_workspace, my_filename)) {
+			return file_from_ustar(self, file, offset);
+		}
+
+		offset += 512;
+		offset += round_to_512(interpret_size(file));
+	}
+
+	return NULL;
+}
+
+static int readlink_tarfs(fs_node_t * node, char * buf, size_t size) {
+	struct tarfs * self = node->device;
+	struct ustar * file = ustar_from_offset(self, node->inode);
+
+	if (size < strlen(file->link) + 1) {
+		debug_print(INFO, "Requested read size was only %d, need %d.", size, strlen(file->link)+1);
+		memcpy(buf, file->link, size-1);
+		buf[size-1] = '\0';
+		return size-1;
+	} else {
+		debug_print(INFO, "Reading link target is [%s]", file->link);
+		memcpy(buf, file->link, strlen(file->link) + 1);
+		return strlen(file->link);
+	}
+
+}
+
+static fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset) {
+	fs_node_t * fs = malloc(sizeof(fs_node_t));
+	memset(fs, 0, sizeof(fs_node_t));
+	fs->device = self;
+	fs->inode  = offset;
+	fs->impl   = 0;
+	memcpy(fs->name, filename_workspace, strlen(filename_workspace)+1);
+
+	fs->uid = interpret_uid(file);
+	fs->gid = interpret_gid(file);
+	fs->length = interpret_size(file);
+	fs->mask = interpret_mode(file);
+	fs->nlink = 0; /* Unsupported */
+	fs->flags = FS_FILE;
+	if (file->type[0] == '5') {
+		fs->flags = FS_DIRECTORY;
+		fs->readdir = readdir_tarfs;
+		fs->finddir = finddir_tarfs;
+	} else if (file->type[0] == '1') {
+		debug_print(ERROR, "Hardlink detected");
+		/* go through file and find target, reassign inode to point to that */
+	} else if (file->type[0] == '2') {
+		fs->flags = FS_SYMLINK;
+		fs->readlink = readlink_tarfs;
+	} else {
+		fs->flags = FS_FILE;
+		fs->read = read_tarfs;
+	}
+#if 0
+	/* TODO times are also available from the file */
+	fs->atime = now();
+	fs->mtime = now();
+	fs->ctime = now();
+#endif
+	return fs;
+}
+
+static fs_node_t * finddir_tar_root(fs_node_t *node, char *name) {
+	struct tarfs * self = node->device;
+
+	unsigned int offset = 0;
+	while (offset < self->length) {
+		struct ustar * file = ustar_from_offset(self, offset);
+
+		if (!file) {
+			return NULL;
+		}
+
+		memset(filename_workspace, 0, 256);
+		strncat(filename_workspace, file->prefix, 155);
+		strncat(filename_workspace, file->filename, 100);
+
+		if (count_slashes(filename_workspace)) {
+			/* skip */
+		} else {
+			char * slash = strstr(filename_workspace,"/");
+			if (slash) *slash = '\0';
+			if (!strcmp(filename_workspace, name)) {
+				return file_from_ustar(self, file, offset);
+			}
+		}
+
+		offset += 512;
+		offset += round_to_512(interpret_size(file));
+	}
+
+	return NULL;
+}
+
+static struct ustar _star = {0};
+
+struct ustar * ustar_from_offset(struct tarfs * self, unsigned int offset) {
+	read_fs(self->device, offset, sizeof(struct ustar), (unsigned char*)&_star);
+	if (_star.ustar[0] != 'u' ||
+		_star.ustar[1] != 's' ||
+		_star.ustar[2] != 't' ||
+		_star.ustar[3] != 'a' ||
+		_star.ustar[4] != 'r') {
+		return NULL;
+	}
+	return &_star;
+}
+
+#if 0
+static fs_node_t * node_from_directory(struct tarfs * self, struct tarfs_file * file) {
+	struct ustar * s = ustar_from_offset(self, file->offset);
+	fs_node_t * fnode = malloc(sizeof(fs_node_t));
+	memset(fnode, 0, sizeof(fs_node_t));
+
+	fnode->flags = FS_DIRECTORY;
+
+	return fnode;
+}
+#endif
+
+static fs_node_t * tar_mount(char * device, char * mount_path) {
+	char * arg = strdup(device);
+	char * argv[10];
+	int argc = tokenize(arg, ",", argv);
+
+	if (argc > 1) {
+		debug_print(WARNING, "tarfs driver takes no options");
+	}
+
+	fs_node_t * dev = kopen(argv[0], 0);
+	free(arg); /* Shouldn't need the filename or args anymore */
+
+	if (!dev) {
+		debug_print(ERROR, "failed to open %s", device);
+		return NULL;
+	}
+
+	/* Create a metadata struct for this mount */
+	struct tarfs * self = malloc(sizeof(struct tarfs));
+
+	self->device = dev;
+	self->length = dev->length;
+
+	fs_node_t * root = malloc(sizeof(fs_node_t));
+	memset(root, 0, sizeof(fs_node_t));
+
+	root->uid     = 0;
+	root->gid     = 0;
+	root->length  = 0;
+	root->mask    = 0555;
+	root->readdir = readdir_tar_root;
+	root->finddir = finddir_tar_root;
+	root->flags   = FS_DIRECTORY;
+	root->device  = self;
+
+	return root;
+}
+
+static int init(void) {
+	vfs_register("tar", tar_mount);
+	return 0;
+}
+
+static int fini(void) {
+	return 0;
+}
+
+MODULE_DEF(tarfs, init, fini);
+