With the help of the newly-introduced cfspack tool, we can mount a filesystem in our emulated shell and play around. Read-only for now. Unpacking incoming.pull/10/head
@@ -0,0 +1,2 @@ | |||
/cfspack | |||
/cfsunpack |
@@ -0,0 +1,4 @@ | |||
TARGETS = cfspack | |||
cfspack: cfspack.c | |||
$(CC) -o $@ $^ |
@@ -0,0 +1,23 @@ | |||
# cfspack | |||
A tool/library to pack a directory into a CFS blob and unpack a CFS blob into | |||
a directory. | |||
## Usage | |||
To pack a directory into a CFS blob, run: | |||
cfspack /path/to/directory | |||
The blob is spit to stdout. If there are subdirectories, they will be prefixes | |||
to the filenames under it. | |||
The program errors out if a file name is too long (> 26 bytes) or too big | |||
(> 0x10000 - 0x20 bytes). | |||
To unpack a blob to a directory: | |||
cfsunpack /path/to/dest < blob | |||
If destination exists, files are created alongside existing ones. If a file to | |||
unpack already exists, it is overwritten. |
@@ -0,0 +1,117 @@ | |||
#include <stdio.h> | |||
#include <dirent.h> | |||
#include <string.h> | |||
#define BLKSIZE 0x100 | |||
#define HEADERSIZE 0x20 | |||
#define MAX_FN_LEN 25 // 26 - null char | |||
#define MAX_FILE_SIZE (BLKSIZE * 0x100) - HEADERSIZE | |||
int spitblock(char *fullpath, char *fn) | |||
{ | |||
FILE *fp = fopen(fullpath, "r"); | |||
fseek(fp, 0, SEEK_END); | |||
long fsize = ftell(fp); | |||
if (fsize > MAX_FILE_SIZE) { | |||
fclose(fp); | |||
fprintf(stderr, "File too big: %s %ld\n", fullpath, fsize); | |||
return 1; | |||
} | |||
/* Compute block count. | |||
* We always have at least one, which contains 0x100 bytes - 0x20, which is | |||
* metadata. The rest of the blocks have a steady 0x100. | |||
*/ | |||
unsigned char blockcount = 1; | |||
int fsize2 = fsize - (BLKSIZE - HEADERSIZE); | |||
if (fsize2 > 0) { | |||
blockcount += (fsize2 / BLKSIZE); | |||
} | |||
if (blockcount * BLKSIZE < fsize) { | |||
blockcount++; | |||
} | |||
putchar('C'); | |||
putchar('F'); | |||
putchar('S'); | |||
putchar(blockcount); | |||
// file size is little endian | |||
putchar(fsize & 0xff); | |||
putchar((fsize >> 8) & 0xff); | |||
int fnlen = strlen(fn); | |||
for (int i=0; i<MAX_FN_LEN; i++) { | |||
if (i < fnlen) { | |||
putchar(fn[i]); | |||
} else { | |||
putchar(0); | |||
} | |||
} | |||
// And the last FN char which is always null | |||
putchar(0); | |||
char buf[MAX_FILE_SIZE] = {0}; | |||
rewind(fp); | |||
fread(buf, fsize, 1, fp); | |||
fclose(fp); | |||
fwrite(buf, (blockcount * BLKSIZE) - HEADERSIZE, 1, stdout); | |||
fflush(stdout); | |||
return 0; | |||
} | |||
int spitdir(char *path, char *prefix) | |||
{ | |||
DIR *dp; | |||
struct dirent *ep; | |||
int prefixlen = strlen(prefix); | |||
dp = opendir(path); | |||
if (dp == NULL) { | |||
fprintf(stderr, "Couldn't open directory.\n"); | |||
return 1; | |||
} | |||
while (ep = readdir(dp)) { | |||
if ((strcmp(ep->d_name, ".") == 0) || strcmp(ep->d_name, "..") == 0) { | |||
continue; | |||
} | |||
if (ep->d_type != DT_DIR && ep->d_type != DT_REG) { | |||
fprintf(stderr, "Only regular file or directories are supported\n"); | |||
return 1; | |||
} | |||
int slen = strlen(ep->d_name); | |||
if (prefixlen + slen> MAX_FN_LEN) { | |||
fprintf(stderr, "Filename too long: %s/%s\n", prefix, ep->d_name); | |||
return 1; | |||
} | |||
char fullpath[0x1000]; | |||
strcpy(fullpath, path); | |||
strcat(fullpath, "/"); | |||
strcat(fullpath, ep->d_name); | |||
char newprefix[MAX_FN_LEN]; | |||
strcpy(newprefix, prefix); | |||
if (prefixlen > 0) { | |||
strcat(newprefix, "/"); | |||
} | |||
strcat(newprefix, ep->d_name); | |||
if (ep->d_type == DT_DIR) { | |||
int r = spitdir(fullpath, newprefix); | |||
if (r != 0) { | |||
return r; | |||
} | |||
} else { | |||
int r = spitblock(fullpath, newprefix); | |||
if (r != 0) { | |||
return r; | |||
} | |||
} | |||
} | |||
closedir(dp); | |||
return 0; | |||
} | |||
int main(int argc, char *argv[]) | |||
{ | |||
if (argc != 2) { | |||
fprintf(stderr, "Usage: cfspack /path/to/dir\n"); | |||
return 1; | |||
} | |||
char *srcpath = argv[1]; | |||
return spitdir(srcpath, ""); | |||
} | |||
@@ -2,3 +2,5 @@ | |||
/zasm | |||
/*-kernel.h | |||
/*-user.h | |||
/cfsin | |||
/cfsout |
@@ -13,7 +13,7 @@ $(KERNEL_HEADERS): | |||
zasm-user.h: zasm_user.asm | |||
scas -o - -I ../../apps/zasm $< | ./bin2c.sh USERSPACE | tee $@ > /dev/null | |||
shell: shell.c libz80/libz80.o shell-kernel.h | |||
shell: shell.c libz80/libz80.o shell-kernel.h ../cfspack/cfspack | |||
zasm: zasm.c libz80/libz80.o zasm-kernel.h zasm-user.h | |||
$(TARGETS): | |||
cc $< libz80/libz80.o -o $@ | |||
@@ -22,6 +22,9 @@ libz80/libz80.o: libz80/z80.c | |||
make -C libz80/codegen opcodes | |||
gcc -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c | |||
../cfspack/cfspack: | |||
make -C ../cfspack | |||
.PHONY: clean | |||
clean: | |||
rm -f $(TARGETS) $(KERNEL_HEADERS) $(USER_HEADERS) |
@@ -4,7 +4,11 @@ | |||
#include "libz80/z80.h" | |||
#include "shell-kernel.h" | |||
/* Collapse OS vanilla shell | |||
/* Collapse OS shell with filesystem | |||
* | |||
* On startup, if "cfsin" directory exists, it packs it as a afke block device | |||
* and loads it in. Upon halting, unpcks the contents of that block device in | |||
* "cfsout" directory. | |||
* | |||
* Memory layout: | |||
* | |||
@@ -15,14 +19,23 @@ | |||
* I/O Ports: | |||
* | |||
* 0 - stdin / stdout | |||
* 1 - Filesystem blockdev data read/write. Reading and writing to it advances | |||
* the pointer. | |||
* 2 - Filesystem blockdev seek / tell. Low byte | |||
* 3 - Filesystem blockdev seek / tell. High byte | |||
*/ | |||
// in sync with shell.asm | |||
#define STDIO_PORT 0x00 | |||
#define STDIN_ST_PORT 0x01 | |||
#define FS_DATA_PORT 0x01 | |||
#define FS_SEEKL_PORT 0x02 | |||
#define FS_SEEKH_PORT 0x03 | |||
static Z80Context cpu; | |||
static uint8_t mem[0xffff]; | |||
static uint8_t mem[0xffff] = {0}; | |||
static uint8_t fsdev[0xffff] = {0}; | |||
static uint16_t fsdev_size = 0; | |||
static uint16_t fsdev_ptr = 0; | |||
static int running; | |||
static uint8_t io_read(int unused, uint16_t addr) | |||
@@ -34,6 +47,16 @@ static uint8_t io_read(int unused, uint16_t addr) | |||
running = 0; | |||
} | |||
return c; | |||
} else if (addr == FS_DATA_PORT) { | |||
if (fsdev_ptr < fsdev_size) { | |||
return fsdev[fsdev_ptr++]; | |||
} else { | |||
return 0; | |||
} | |||
} else if (addr == FS_SEEKL_PORT) { | |||
return fsdev_ptr && 0xff; | |||
} else if (addr == FS_SEEKH_PORT) { | |||
return fsdev_ptr >> 8; | |||
} else { | |||
fprintf(stderr, "Out of bounds I/O read: %d\n", addr); | |||
return 0; | |||
@@ -49,6 +72,14 @@ static void io_write(int unused, uint16_t addr, uint8_t val) | |||
} else { | |||
putchar(val); | |||
} | |||
} else if (addr == FS_DATA_PORT) { | |||
if (fsdev_ptr < fsdev_size) { | |||
fsdev[fsdev_ptr++] = val; | |||
} | |||
} else if (addr == FS_SEEKL_PORT) { | |||
fsdev_ptr = (fsdev_ptr & 0xff00) | val; | |||
} else if (addr == FS_SEEKH_PORT) { | |||
fsdev_ptr = (fsdev_ptr & 0x00ff) | (val << 8); | |||
} else { | |||
fprintf(stderr, "Out of bounds I/O write: %d / %d\n", addr, val); | |||
} | |||
@@ -66,6 +97,23 @@ static void mem_write(int unused, uint16_t addr, uint8_t val) | |||
int main() | |||
{ | |||
// Setup fs blockdev | |||
FILE *fp = popen("../cfspack/cfspack cfsin", "r"); | |||
if (fp != NULL) { | |||
printf("Initializing filesystem\n"); | |||
int i = 0; | |||
int c = fgetc(fp); | |||
while (c != EOF) { | |||
fsdev[i] = c & 0xff; | |||
i++; | |||
c = fgetc(fp); | |||
} | |||
fsdev_size = i; | |||
pclose(fp); | |||
} else { | |||
printf("Can't initialize filesystem. Leaving blank.\n"); | |||
} | |||
// Turn echo off: the shell takes care of its own echoing. | |||
struct termios termInfo; | |||
if (tcgetattr(0, &termInfo) == -1) { | |||
@@ -2,16 +2,11 @@ | |||
RAMSTART .equ 0x4000 | |||
RAMEND .equ 0x5000 | |||
STDIO_PORT .equ 0x00 | |||
FS_DATA_PORT .equ 0x01 | |||
FS_SEEKL_PORT .equ 0x02 | |||
FS_SEEKH_PORT .equ 0x03 | |||
jr init | |||
init: | |||
di | |||
; setup stack | |||
ld hl, RAMEND | |||
ld sp, hl | |||
call shellInit | |||
jp shellLoop | |||
jp init | |||
#include "core.asm" | |||
.define STDIO_GETC call emulGetC | |||
@@ -20,19 +15,40 @@ STDIO_RAMSTART .equ RAMEND | |||
#include "stdio.asm" | |||
BLOCKDEV_RAMSTART .equ STDIO_RAMEND | |||
BLOCKDEV_COUNT .equ 1 | |||
BLOCKDEV_COUNT .equ 2 | |||
#include "blockdev.asm" | |||
; List of devices | |||
.dw emulGetC, emulPutC, 0, 0 | |||
.dw fsdevGetC, fsdevPutC, fsdevSeek, fsdevTell | |||
#include "blockdev_cmds.asm" | |||
SHELL_RAMSTART .equ BLOCKDEV_RAMEND | |||
.equ FS_RAMSTART BLOCKDEV_RAMEND | |||
.equ FS_HANDLE_COUNT 2 | |||
#include "fs.asm" | |||
SHELL_RAMSTART .equ FS_RAMEND | |||
.define SHELL_IO_GETC call blkGetCW | |||
.define SHELL_IO_PUTC call blkPutC | |||
SHELL_EXTRA_CMD_COUNT .equ 2 | |||
SHELL_EXTRA_CMD_COUNT .equ 6 | |||
#include "shell.asm" | |||
.dw blkBselCmd, blkSeekCmd | |||
.dw blkBselCmd, blkSeekCmd, fsOnCmd, flsCmd, fnewCmd, fdelCmd | |||
init: | |||
di | |||
; setup stack | |||
ld hl, RAMEND | |||
ld sp, hl | |||
call fsInit | |||
ld a, 1 ; select fsdev | |||
ld de, BLOCKDEV_GETC | |||
call blkSel | |||
call fsOn | |||
xor a ; select ACIA | |||
ld de, BLOCKDEV_GETC | |||
call blkSel | |||
call shellInit | |||
jp shellLoop | |||
emulGetC: | |||
; Blocks until a char is returned | |||
@@ -44,3 +60,24 @@ emulPutC: | |||
out (STDIO_PORT), a | |||
ret | |||
fsdevGetC: | |||
in a, (FS_DATA_PORT) | |||
ret | |||
fsdevPutC: | |||
out (FS_DATA_PORT), a | |||
ret | |||
fsdevSeek: | |||
ld a, l | |||
out (FS_SEEKL_PORT), a | |||
ld a, h | |||
out (FS_SEEKH_PORT), a | |||
ret | |||
fsdevTell: | |||
in a, (FS_SEEKL_PORT) | |||
ld l, a | |||
in a, (FS_SEEKH_PORT) | |||
ld h, a | |||
ret |