backported more fixes from 256boss
authorJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 2 Sep 2019 13:28:08 +0000 (16:28 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 2 Sep 2019 13:28:08 +0000 (16:28 +0300)
29 files changed:
Makefile
src/bootdev.c [new file with mode: 0644]
src/bootdev.h [new file with mode: 0644]
src/floppy.c [new file with mode: 0644]
src/floppy.h [new file with mode: 0644]
src/fs.c [new file with mode: 0644]
src/fs.h [new file with mode: 0644]
src/fsfat.c [new file with mode: 0644]
src/fsmem.c [new file with mode: 0644]
src/libc/dirent.c [new file with mode: 0644]
src/libc/dirent.h [new file with mode: 0644]
src/libc/file.c [new file with mode: 0644]
src/libc/float.h [new file with mode: 0644]
src/libc/math.c [new file with mode: 0644]
src/libc/math.h [new file with mode: 0644]
src/libc/rand.c [new file with mode: 0644]
src/libc/setjmp.c [new file with mode: 0644]
src/libc/setjmp.h [new file with mode: 0644]
src/libc/time.c [new file with mode: 0644]
src/libc/time.h [new file with mode: 0644]
src/libc/unistd.c [new file with mode: 0644]
src/libc/unistd.h [new file with mode: 0644]
src/mtab.c [new file with mode: 0644]
src/mtab.h [new file with mode: 0644]
src/part.c [new file with mode: 0644]
src/part.h [new file with mode: 0644]
src/ptype.h [new file with mode: 0644]
src/rtc.c [new file with mode: 0644]
src/rtc.h [new file with mode: 0644]

index 6887307..2396eab 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ warn = -pedantic -Wall
 #opt = -O2
 dbg = -g
 inc = -Isrc -Isrc/libc -Isrc/test
-gccopt = -fno-pic -ffreestanding -nostdinc -fno-builtin
+gccopt = -fno-pic -ffreestanding -nostdinc -fno-builtin -ffast-math
 
 CFLAGS = $(ccarch) -march=i386 $(warn) $(opt) $(dbg) $(gccopt) $(inc) $(def)
 ASFLAGS = $(asarch) -march=i386 $(dbg) -nostdinc -fno-builtin $(inc)
diff --git a/src/bootdev.c b/src/bootdev.c
new file mode 100644 (file)
index 0000000..b3dd48a
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include "bootdev.h"
+#include "boot.h"
+#include "int86.h"
+#include "panic.h"
+#include "timer.h"
+#include "floppy.h"
+
+#define FLOPPY_MOTOR_OFF_TIMEOUT       4000
+#define DBG_RESET_ON_FAIL
+
+struct chs {
+       unsigned int cyl, head, tsect;
+};
+
+struct disk_access {
+       unsigned char pktsize;
+       unsigned char zero;
+       uint16_t num_sectors;   /* max 127 on some implementations */
+       uint16_t boffs, bseg;
+       uint32_t lba_low, lba_high;
+} __attribute__((packed));
+
+enum {OP_READ, OP_WRITE};
+
+#ifdef DBG_RESET_ON_FAIL
+static int bios_reset_dev(int dev);
+#endif
+static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf);
+static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf);
+static int get_drive_chs(int dev, struct chs *chs);
+static void calc_chs(uint64_t lba, struct chs *chs);
+
+static int have_bios_ext;
+static int bdev_is_floppy;
+static int num_cyl, num_heads, num_track_sect;
+
+void bdev_init(void)
+{
+       struct chs chs;
+       struct int86regs regs;
+
+       memset(&regs, 0, sizeof regs);
+       regs.eax = 0x4100;      /* function 41h: check int 13h extensions */
+       regs.ebx = 0x55aa;
+       regs.edx = boot_drive_number;
+
+       int86(0x13, &regs);
+       if((regs.flags & FLAGS_CARRY) || (regs.ecx & 1) == 0) {
+               printf("BIOS does not support int13h extensions (LBA access)\n");
+               have_bios_ext = 0;
+
+               if(get_drive_chs(boot_drive_number, &chs) == -1) {
+                       panic("drive (%d) parameter query failed\n", boot_drive_number);
+               }
+
+               num_cyl = chs.cyl;
+               num_heads = chs.head;
+               num_track_sect = chs.tsect;
+
+               printf("Drive params: %d cyl, %d heads, %d sect/track\n", num_cyl,
+                               num_heads, num_track_sect);
+       } else {
+               printf("BIOS supports int13h extensions (LBA access)\n");
+               have_bios_ext = 1;
+       }
+
+       bdev_is_floppy = !(boot_drive_number & 0x80);
+}
+
+#define NRETRIES       3
+
+int bdev_read_sect(uint64_t lba, void *buf)
+{
+       int i;
+       struct chs chs;
+
+       if(bdev_is_floppy) {
+               cancel_alarm(floppy_motors_off);
+               set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
+       }
+
+       if(have_bios_ext) {
+               return bios_rw_sect_lba(boot_drive_number, lba, 1, OP_READ, buf);
+       }
+
+       calc_chs(lba, &chs);
+
+       for(i=0; i<NRETRIES; i++) {
+               if(bios_rw_sect_chs(boot_drive_number, &chs, 1, OP_READ, buf) != -1) {
+                       return 0;
+               }
+#ifdef DBG_RESET_ON_FAIL
+               bios_reset_dev(boot_drive_number);
+#endif
+       }
+       return -1;
+}
+
+int bdev_write_sect(uint64_t lba, void *buf)
+{
+       struct chs chs;
+
+       if(bdev_is_floppy) {
+               cancel_alarm(floppy_motors_off);
+               set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
+       }
+
+       if(have_bios_ext) {
+               return bios_rw_sect_lba(boot_drive_number, lba, 1, OP_WRITE, buf);
+       }
+
+       calc_chs(lba, &chs);
+       return bios_rw_sect_chs(boot_drive_number, &chs, 1, OP_WRITE, buf);
+}
+
+int bdev_read_range(uint64_t lba, int nsect, void *buf)
+{
+       int i;
+       struct chs chs;
+
+       if(bdev_is_floppy) {
+               cancel_alarm(floppy_motors_off);
+               set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
+       }
+
+       if(have_bios_ext) {
+               return bios_rw_sect_lba(boot_drive_number, lba, nsect, OP_READ, buf);
+       }
+
+       calc_chs(lba, &chs);
+
+       for(i=0; i<NRETRIES; i++) {
+               int rd;
+               if((rd = bios_rw_sect_chs(boot_drive_number, &chs, nsect, OP_READ, buf)) == nsect) {
+                       return 0;
+               }
+
+               if(rd > 0) {
+                       nsect -= rd;
+                       buf = (char*)buf + rd * 512;
+                       lba += rd;
+                       calc_chs(lba, &chs);
+               }
+
+#ifdef DBG_RESET_ON_FAIL
+               bios_reset_dev(boot_drive_number);
+#endif
+       }
+       return -1;
+}
+
+int bdev_write_range(uint64_t lba, int nsect, void *buf)
+{
+       struct chs chs;
+
+       if(bdev_is_floppy) {
+               cancel_alarm(floppy_motors_off);
+               set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
+       }
+
+       if(have_bios_ext) {
+               return bios_rw_sect_lba(boot_drive_number, lba, nsect, OP_WRITE, buf);
+       }
+
+       calc_chs(lba, &chs);
+       return bios_rw_sect_chs(boot_drive_number, &chs, nsect, OP_WRITE, buf);
+}
+
+
+#ifdef DBG_RESET_ON_FAIL
+static int bios_reset_dev(int dev)
+{
+       struct int86regs regs;
+
+       printf("resetting drive %d ...", dev);
+
+       memset(&regs, 0, sizeof regs);
+       regs.edx = dev;
+
+       int86(0x13, &regs);
+
+       printf("%s\n", (regs.flags & FLAGS_CARRY) ? "failed" : "done");
+
+       return (regs.flags & FLAGS_CARRY) ? -1 : 0;
+}
+#endif
+
+static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf)
+{
+       struct int86regs regs;
+       struct disk_access *dap = (struct disk_access*)low_mem_buffer;
+       uint32_t addr = (uint32_t)low_mem_buffer;
+       uint32_t xaddr = (addr + sizeof *dap + 15) & 0xfffffff0;
+       void *xbuf = (void*)xaddr;
+       int func;
+
+       if(op == OP_READ) {
+               func = 0x42;    /* function 42h: extended read sector (LBA) */
+       } else {
+               func = 0x43;    /* function 43h: extended write sector (LBA) */
+               memcpy(xbuf, buf, nsect * 512);
+       }
+
+       dap->pktsize = sizeof *dap;
+       dap->zero = 0;
+       dap->num_sectors = nsect;
+       dap->boffs = 0;
+       dap->bseg = xaddr >> 4;
+       dap->lba_low = (uint32_t)lba;
+       dap->lba_high = (uint32_t)(lba >> 32);
+
+       memset(&regs, 0, sizeof regs);
+       regs.eax = func << 8;
+       regs.ds = addr >> 4;
+       regs.esi = 0;
+       regs.edx = dev;
+
+       int86(0x13, &regs);
+
+       if(regs.flags & FLAGS_CARRY) {
+               return -1;
+       }
+
+       if(op == OP_READ) {
+               memcpy(buf, xbuf, nsect * 512);
+       }
+       return dap->num_sectors;
+}
+
+/* TODO: fix: probably can't cross track boundaries, clamp nsect accordingly
+ * and return a short count
+ */
+static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf)
+{
+       struct int86regs regs;
+       uint32_t xaddr = (uint32_t)low_mem_buffer;
+       int func;
+
+       if(op == OP_READ) {
+               func = 2;
+       } else {
+               func = 3;
+               memcpy(low_mem_buffer, buf, nsect * 512);
+       }
+
+       memset(&regs, 0, sizeof regs);
+       regs.eax = (func << 8) | nsect; /* 1 sector */
+       regs.es = xaddr >> 4;   /* es:bx buffer */
+       regs.ecx = ((chs->cyl << 8) & 0xff00) | ((chs->cyl >> 10) & 0xc0) | chs->tsect;
+       regs.edx = dev | (chs->head << 8);
+
+       int86(0x13, &regs);
+
+       if(regs.flags & FLAGS_CARRY) {
+               return -1;
+       }
+
+       if(op == OP_READ) {
+               memcpy(buf, low_mem_buffer, nsect * 512);
+       }
+       return nsect;
+}
+
+static int get_drive_chs(int dev, struct chs *chs)
+{
+       struct int86regs regs;
+
+       memset(&regs, 0, sizeof regs);
+       regs.eax = 0x800;
+       regs.edx = dev;
+
+       int86(0x13, &regs);
+
+       if(regs.flags & FLAGS_CARRY) {
+               return -1;
+       }
+
+       chs->cyl = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x300)) + 1;
+       chs->head = (regs.edx >> 8) + 1;
+       chs->tsect = regs.ecx & 0x3f;
+       return 0;
+}
+
+static void calc_chs(uint64_t lba, struct chs *chs)
+{
+       uint32_t lba32, trk;
+
+       if(lba >> 32) {
+               /* XXX: 64bit ops require libgcc, and I don't want to link that for
+                * this. CHS I/O is only going to be for floppies and really old systems
+                * anyway, so there's no point in supporting anything more than LBA32 in
+                * the translation.
+                */
+               const char *fmt = "calc_chs only supports 32bit LBA. requested: %llx\n"
+                       "If you see this message, please file a bug report at:\n"
+                       "  https://github.com/jtsiomb/256boss/issues\n"
+                       "  or by email: nuclear@member.fsf.org\n";
+               panic(fmt, lba);
+       }
+
+       lba32 = (uint32_t)lba;
+       trk = lba32 / num_track_sect;
+       chs->tsect = (lba32 % num_track_sect) + 1;
+       chs->cyl = trk / num_heads;
+       chs->head = trk % num_heads;
+}
diff --git a/src/bootdev.h b/src/bootdev.h
new file mode 100644 (file)
index 0000000..3dd45eb
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef BOOTDEV_H_
+#define BOOTDEV_H_
+
+void bdev_init(void);
+
+int bdev_read_sect(uint64_t lba, void *buf);
+int bdev_write_sect(uint64_t lba, void *buf);
+
+int bdev_read_range(uint64_t lba, int nsect, void *buf);
+int bdev_write_range(uint64_t lba, int nsect, void *buf);
+
+#endif /* BOOTDEV_H_ */
diff --git a/src/floppy.c b/src/floppy.c
new file mode 100644 (file)
index 0000000..a524fe6
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "floppy.h"
+#include "asmops.h"
+
+void floppy_motors_off(void)
+{
+       unsigned char dout = inb(FDC_REG_DOUT);
+       outb(dout & 0xf, FDC_REG_DOUT);
+}
diff --git a/src/floppy.h b/src/floppy.h
new file mode 100644 (file)
index 0000000..f38e284
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef FLOPPY_H_
+#define FLOPPY_H_
+
+#define FDC_REG_DOUT   0x3f2
+#define FDC_REG_STAT   0x3f4
+#define FDC_REG_DATA   0x3f5
+
+#define FDC_STAT_ACTA  0x01
+#define FDC_STAT_ACTB  0x02
+#define FDC_STAT_ACTC  0x04
+#define FDC_STAT_ACTD  0x08
+#define FDC_STAT_BUSY  0x10
+#define FDC_STAT_NODMA 0x20
+#define FDC_STAT_IODIR 0x40
+#define FDC_STAT_RDY   0x80
+
+#define FDC_DOUT_SEL(x)        (x)
+#define FDC_DOUT_RST   0x04
+#define FDC_DOUT_DMA   0x08
+#define FDC_DOUT_MOTA  0x10
+#define FDC_DOUT_MOTB  0x20
+#define FDC_DOUT_MOTC  0x40
+#define FDC_DOUT_MOTD  0x80
+
+void floppy_motors_off(void);
+
+#endif /* FLOPPY_H_ */
diff --git a/src/fs.c b/src/fs.c
new file mode 100644 (file)
index 0000000..2e9cbaf
--- /dev/null
+++ b/src/fs.c
@@ -0,0 +1,278 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <alloca.h>
+#include "fs.h"
+#include "mtab.h"
+#include "panic.h"
+
+struct filesys *fsfat_create(int dev, uint64_t start, uint64_t size);
+struct filesys *fsmem_create(int dev, uint64_t start, uint64_t size);
+
+static struct filesys *(*createfs[])(int, uint64_t, uint64_t) = {
+       fsmem_create,
+       fsfat_create
+};
+
+struct filesys *fs_mount(int dev, uint64_t start, uint64_t size, struct fs_node *parent)
+{
+       int i;
+       struct filesys *fs;
+
+       if(!parent && rootfs) {
+               printf("fs_mount error: root filesystem already mounted!\n");
+               return 0;
+       }
+
+       for(i=0; i<NUM_FSTYPES; i++) {
+               if((fs = createfs[i](dev, start, size))) {
+                       if(!parent) {
+                               rootfs = fs;
+
+                               fs_chdir("/");
+                       } else {
+                               parent->mnt = fs;
+                               mtab_add(parent, fs);
+                       }
+                       return fs;
+               }
+       }
+
+       printf("failed to mount filesystem dev: %d, start %llu\n", dev, (unsigned long long)start);
+       return 0;
+}
+
+static char cwdpath[1024];
+static char *cwdpath_end = cwdpath;
+
+int fs_chdir(const char *path)
+{
+       struct fs_node *node;
+       char *uppath = 0;
+       int uplen;
+
+       if(!path || !*path) {
+               return -1;
+       }
+
+       if(strcmp(path, ".") == 0) {
+               return 0;
+       }
+       if(strcmp(path, "..") == 0) {
+               char *endptr;
+
+               if(cwdpath_end <= cwdpath + 1) {
+                       return -1;
+               }
+
+               endptr = cwdpath + (cwdpath_end - cwdpath);
+               while(endptr > cwdpath && *--endptr != '/');
+               if(endptr == cwdpath) endptr++;
+
+               uplen = endptr - cwdpath;
+               uppath = alloca(uplen + 1);
+               memcpy(uppath, cwdpath, uplen);
+               uppath[uplen] = 0;
+
+               path = uppath;
+       }
+
+       if(!(node = fs_open(path, 0))) {
+               return -1;
+       }
+       if(node->type != FSNODE_DIR) {
+               fs_close(node);
+               return -1;
+       }
+
+       if(uppath) {
+               memcpy(cwdpath, uppath, uplen + 1);
+               cwdpath_end = cwdpath + uplen;
+
+       } else {
+               int len = strlen(path);
+               if(cwdpath_end - cwdpath + len > sizeof cwdpath) {
+                       panic("fs_chdir: path too long: %s\n", path);
+               }
+               if(path[0] == '/') {
+                       memcpy(cwdpath, path, len + 1);
+                       cwdpath_end = cwdpath + len;
+               } else {
+                       if(cwdpath_end > cwdpath + 1) {
+                               *cwdpath_end++ = '/';
+                       }
+                       memcpy(cwdpath_end, path, len + 1);
+                       cwdpath_end += len;
+               }
+       }
+
+       fs_close(cwdnode);
+       cwdnode = node;
+       return 0;
+}
+
+char *fs_getcwd(void)
+{
+       return cwdpath;
+}
+
+/* TODO normalize path */
+struct fs_node *fs_open(const char *path, unsigned int flags)
+{
+       struct filesys *fs;
+       struct fs_node *node;
+
+       if(!path || !*path) {
+               return 0;
+       }
+
+       if(*path == '/') {
+               fs = rootfs;
+       } else {
+               if(!cwdnode) return 0;
+               fs = cwdnode->fs;
+       }
+
+       if(!(node = fs->fsop->open(fs, path, flags))) {
+               return 0;
+       }
+       return node;
+}
+
+int fs_close(struct fs_node *node)
+{
+       struct fs_operations *fsop;
+
+       if(!node) return -1;
+
+       fsop = node->fs->fsop;
+       fsop->close(node);
+       return 0;
+}
+
+int fs_rename(struct fs_node *node, const char *name)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+       return fsop->rename(node, name);
+}
+
+int fs_remove(struct fs_node *node)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+       return fsop->remove(node);
+}
+
+long fs_filesize(struct fs_node *node)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       return fsop->fsize(node);
+}
+
+int fs_seek(struct fs_node *node, int offs, int whence)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       return fsop->seek(node, offs, whence);
+}
+
+long fs_tell(struct fs_node *node)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       return fsop->tell(node);
+}
+
+int fs_read(struct fs_node *node, void *buf, int sz)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       return fsop->read(node, buf, sz);
+}
+
+int fs_write(struct fs_node *node, void *buf, int sz)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       return fsop->write(node, buf, sz);
+}
+
+int fs_rewinddir(struct fs_node *node)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_DIR) {
+               return -1;
+       }
+       return fsop->rewinddir(node);
+}
+
+struct fs_dirent *fs_readdir(struct fs_node *node)
+{
+       struct fs_operations *fsop = node->fs->fsop;
+
+       if(node->type != FSNODE_DIR) {
+               return 0;
+       }
+       return fsop->readdir(node);
+}
+
+/* fs utility functions */
+char *fs_path_skipsep(char *s)
+{
+       while(*s == '/') s++;
+       return s;
+}
+
+char *fs_path_next(char *s, char *namebuf, int bufsz)
+{
+       int len;
+       char *ptr;
+
+       ptr = s = fs_path_skipsep(s);
+
+       while(*ptr && *ptr != '/') ptr++;
+
+       if(namebuf) {
+               len = ptr - s;
+               if(len >= bufsz) len = bufsz - 1;
+
+               memcpy(namebuf, s, len);
+               namebuf[len] = 0;
+       }
+
+       return fs_path_skipsep(ptr);
+}
diff --git a/src/fs.h b/src/fs.h
new file mode 100644 (file)
index 0000000..f05f29a
--- /dev/null
+++ b/src/fs.h
@@ -0,0 +1,125 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef FS_H_
+#define FS_H_
+
+#include <inttypes.h>
+
+/* device ids for virtual filesystems */
+enum {
+       DEV_FLOPPY0             = 0,
+       DEV_FLOPPY1             = 1,
+       DEV_HDD0                = 0x80,
+       DEV_HDD1                = 0x81,
+       DEV_MEMDISK             = 0x10000
+};
+
+enum {
+       FSTYPE_MEM,
+       FSTYPE_FAT,
+
+       NUM_FSTYPES
+};
+
+enum { FSNODE_FILE, FSNODE_DIR };
+
+enum { FSSEEK_SET, FSSEEK_CUR, FSSEEK_END };
+
+enum {
+       FSO_CREATE      = 1,
+       FSO_DIR         = 2,
+       FSO_EXCL        = 4
+};
+
+struct filesys;
+struct fs_node;
+struct fs_dirent;
+
+struct fs_operations {
+       void (*destroy)(struct filesys *fs);
+
+       struct fs_node *(*open)(struct filesys *fs, const char *path, unsigned int flags);
+       void (*close)(struct fs_node *node);
+
+       long (*fsize)(struct fs_node *node);
+       int (*seek)(struct fs_node *node, int offs, int whence);
+       long (*tell)(struct fs_node *node);
+       int (*read)(struct fs_node *node, void *buf, int sz);
+       int (*write)(struct fs_node *node, void *buf, int sz);
+
+       int (*rewinddir)(struct fs_node *node);
+       struct fs_dirent *(*readdir)(struct fs_node *node);
+
+       int (*rename)(struct fs_node *node, const char *name);
+       int (*remove)(struct fs_node *node);
+};
+
+struct filesys {
+       int type;
+       char *name;
+       struct fs_operations *fsop;
+       void *data;
+};
+
+struct fs_node {
+       struct filesys *fs;
+       int type;
+       void *data;
+
+       struct filesys *mnt;
+};
+
+struct fs_dirent {
+       char *name;
+       void *data;
+       int type;
+       long fsize;
+};
+
+struct filesys *rootfs;
+struct fs_node *cwdnode;       /* current working directory node */
+
+struct filesys *fs_mount(int dev, uint64_t start, uint64_t size, struct fs_node *parent);
+
+int fs_chdir(const char *path);
+char *fs_getcwd(void);
+
+struct fs_node *fs_open(const char *path, unsigned int flags);
+int fs_close(struct fs_node *node);
+
+int fs_rename(struct fs_node *node, const char *name);
+int fs_remove(struct fs_node *node);
+
+long fs_filesize(struct fs_node *node);
+int fs_seek(struct fs_node *node, int offs, int whence);
+long fs_tell(struct fs_node *node);
+int fs_read(struct fs_node *node, void *buf, int sz);
+int fs_write(struct fs_node *node, void *buf, int sz);
+
+int fs_rewinddir(struct fs_node *node);
+struct fs_dirent *fs_readdir(struct fs_node *node);
+
+/* fs utility functions */
+char *fs_path_skipsep(char *s);
+
+/* copies the current name into the namebuf, and returns a pointer to the
+ * start of the next path component.
+ */
+char *fs_path_next(char *s, char *namebuf, int bufsz);
+
+#endif /* FS_H_ */
diff --git a/src/fsfat.c b/src/fsfat.c
new file mode 100644 (file)
index 0000000..d0b347a
--- /dev/null
@@ -0,0 +1,965 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <assert.h>
+#include "fs.h"
+#include "bootdev.h"
+#include "boot.h"
+#include "panic.h"
+
+#define MAX_NAME       195
+
+#define DIRENT_UNUSED  0xe5
+
+#define DENT_IS_NULL(dent)     (((unsigned char*)(dent))[0] == 0)
+#define DENT_IS_UNUSED(dent)   (((unsigned char*)(dent))[0] == DIRENT_UNUSED)
+
+#define ATTR_RO                0x01
+#define ATTR_HIDDEN    0x02
+#define ATTR_SYSTEM    0x04
+#define ATTR_VOLID     0x08
+#define ATTR_DIR       0x10
+#define ATTR_ARCHIVE   0x20
+#define ATTR_LFN       0xf
+
+
+enum { FAT12, FAT16, FAT32, EXFAT };
+static const char *typestr[] = { "fat12", "fat16", "fat32", "exfat" };
+
+struct fat_dirent;
+struct fat_dir;
+
+struct fatfs {
+       int type;
+       int dev;
+       uint64_t start_sect;
+       uint32_t size;
+       int cluster_size;
+       int fat_size;
+       uint32_t fat_sect;
+       uint32_t root_sect;
+       int root_size;
+       uint32_t first_data_sect;
+       uint32_t num_data_sect;
+       uint32_t num_clusters;
+       char label[12];
+
+       void *fat;
+       struct fat_dir *rootdir;
+       unsigned int clust_mask;
+       int clust_shift;
+};
+
+struct bparam {
+       uint8_t jmp[3];
+       unsigned char fmtid[8];
+       uint16_t sect_bytes;
+       uint8_t cluster_size;
+       uint16_t reserved_sect;
+       uint8_t num_fats;
+       uint16_t num_dirent;
+       uint16_t num_sectors;
+       uint8_t medium;
+       uint16_t fat_size;
+       uint16_t track_size;
+       uint16_t num_heads;
+       uint32_t num_hidden;
+       uint32_t num_sectors32;
+} __attribute__((packed));
+
+struct bparam_ext16 {
+       uint8_t driveno;
+       uint8_t ntflags;
+       uint8_t signature;
+       uint32_t volume_id;
+       char label[11];
+       char sysid[8];
+} __attribute__((packed));
+
+struct bparam_ext32 {
+       uint32_t fat_size;
+       uint16_t flags;
+       uint16_t version;
+       uint32_t root_clust;
+       uint16_t fsinfo_sect;
+       uint16_t backup_boot_sect;
+       char junk[12];
+       uint8_t driveno;
+       uint8_t ntflags;
+       uint8_t signature;
+       uint32_t volume_id;
+       char label[11];
+       char sysid[8];
+} __attribute__((packed));
+
+struct fat_dirent {
+       char name[11];
+       uint8_t attr;
+       uint8_t junk;
+       uint8_t ctime_tenths;
+       uint16_t ctime_halfsec;
+       uint16_t ctime_date;
+       uint16_t atime_date;
+       uint16_t first_cluster_high;    /* 0 for FAT12 and FAT16 */
+       uint16_t mtime_hsec;
+       uint16_t mtime_date;
+       uint16_t first_cluster_low;
+       uint32_t size_bytes;
+} __attribute__((packed));
+
+struct fat_lfnent {
+       uint8_t seq;
+       uint16_t part1[5];
+       uint8_t attr;
+       uint8_t type;
+       uint8_t csum;
+       uint16_t part2[6];
+       uint16_t zero;
+       uint16_t part3[2];
+} __attribute__((packed));
+
+
+struct fat_dir {
+       struct fatfs *fatfs;
+
+       struct fat_dirent *ent;
+       int max_nent;
+
+       struct fs_dirent *fsent;
+       int fsent_size;
+       int cur_ent;
+
+       int ref;
+};
+
+struct fat_file {
+       struct fat_dirent ent;
+       int32_t first_clust;
+       int64_t cur_pos;
+       int32_t cur_clust;      /* cluster number corresponding to cur_pos */
+
+       char *clustbuf;
+       int buf_valid;
+};
+
+
+static void destroy(struct filesys *fs);
+
+static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags);
+static void close(struct fs_node *node);
+static long fsize(struct fs_node *node);
+static int seek(struct fs_node *node, int offs, int whence);
+static long tell(struct fs_node *node);
+static int read(struct fs_node *node, void *buf, int sz);
+static int write(struct fs_node *node, void *buf, int sz);
+static int rewinddir(struct fs_node *node);
+static struct fs_dirent *readdir(struct fs_node *node);
+static int rename(struct fs_node *node, const char *name);
+static int remove(struct fs_node *node);
+
+static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent);
+static void parse_dir_entries(struct fat_dir *dir);
+static void free_dir(struct fat_dir *dir);
+
+static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent);
+static void free_file(struct fat_file *file);
+
+static int read_sectors(int dev, uint64_t sidx, int count, void *sect);
+static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust);
+static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf);
+static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name);
+
+static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr);
+static int32_t next_cluster(struct fatfs *fatfs, int32_t addr);
+static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust);
+
+/* static void dbg_printdir(struct fat_dirent *dir, int max_entries); */
+static void clean_trailws(char *s);
+
+static struct fs_operations fs_fat_ops = {
+       destroy,
+       open, close,
+
+       fsize,
+       seek, tell,
+       read, write,
+
+       rewinddir, readdir,
+
+       rename, remove
+};
+
+static unsigned char sectbuf[512];
+static int max_sect_once;
+
+struct filesys *fsfat_create(int dev, uint64_t start, uint64_t size)
+{
+       int num_read;
+       char *endp, *ptr;
+       struct filesys *fs;
+       struct fatfs *fatfs;
+       struct fat_dir *rootdir;
+       struct bparam *bpb;
+       struct bparam_ext16 *bpb16;
+       struct bparam_ext32 *bpb32;
+
+       max_sect_once = ((unsigned char*)0xa0000 - low_mem_buffer) / 512;
+       /* some BIOS implementations have a maximum limit of 127 sectors */
+       if(max_sect_once > 127) max_sect_once = 127;
+
+       if(read_sectors(dev, start, 1, sectbuf) == -1) {
+               return 0;
+       }
+       bpb = (struct bparam*)sectbuf;
+       bpb16 = (struct bparam_ext16*)(sectbuf + sizeof *bpb);
+       bpb32 = (struct bparam_ext32*)(sectbuf + sizeof *bpb);
+
+       if(bpb->jmp[0] != 0xeb || bpb->jmp[2] != 0x90) {
+               return 0;
+       }
+       if(bpb->sect_bytes != 512) {
+               return 0;
+       }
+
+       if(!(fatfs = malloc(sizeof *fatfs))) {
+               panic("FAT: create failed to allocate memory for the fat filesystem data\n");
+       }
+       memset(fatfs, 0, sizeof *fatfs);
+       fatfs->dev = dev < 0 ? boot_drive_number : dev;
+       fatfs->start_sect = start;
+       fatfs->size = bpb->num_sectors ? bpb->num_sectors : bpb->num_sectors32;
+       fatfs->cluster_size = bpb->cluster_size;
+       fatfs->fat_size = bpb->fat_size ? bpb->fat_size : bpb32->fat_size;
+       fatfs->fat_sect = bpb->reserved_sect;
+       fatfs->root_sect = fatfs->fat_sect + fatfs->fat_size * bpb->num_fats;
+       fatfs->root_size = (bpb->num_dirent * sizeof(struct fat_dirent) + 511) / 512;
+       fatfs->first_data_sect = bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size;
+       fatfs->num_data_sect = fatfs->size - (bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size);
+       fatfs->num_clusters = fatfs->num_data_sect / fatfs->cluster_size;
+
+       if(fatfs->num_clusters < 4085) {
+               fatfs->type = FAT12;
+       } else if(fatfs->num_clusters < 65525) {
+               fatfs->type = FAT16;
+       } else if(fatfs->num_clusters < 268435445) {
+               fatfs->type = FAT32;
+       } else {
+               fatfs->type = EXFAT;
+       }
+
+       switch(fatfs->type) {
+       case FAT16:
+               memcpy(fatfs->label, bpb16->label, sizeof bpb16->label);
+               break;
+
+       case FAT32:
+       case EXFAT:
+               fatfs->root_sect = bpb32->root_clust / fatfs->cluster_size;
+               fatfs->root_size = 0;
+               memcpy(fatfs->label, bpb32->label, sizeof bpb32->label);
+               break;
+
+       default:
+               break;
+       }
+
+       endp = fatfs->label + sizeof fatfs->label - 2;
+       while(endp >= fatfs->label && isspace(*endp)) {
+               *endp-- = 0;
+       }
+
+       /* read the FAT */
+       if(!(fatfs->fat = malloc(fatfs->fat_size * 512))) {
+               panic("FAT: failed to allocate memory for the FAT (%lu bytes)\n", (unsigned long)fatfs->fat_size * 512);
+       }
+       ptr = fatfs->fat;
+       num_read = 0;
+       while(num_read < fatfs->fat_size) {
+               int count = fatfs->fat_size - num_read;
+               if(count > max_sect_once) count = max_sect_once;
+
+               read_sectors(dev, fatfs->start_sect + fatfs->fat_sect + num_read, count, ptr);
+               ptr += count * 512;
+               num_read += count;
+       }
+
+       /* open root directory */
+       if(fatfs->type == FAT32) {
+               struct fat_dirent ent;
+               ent.attr = ATTR_DIR;
+               ent.first_cluster_low = bpb32->root_clust;
+               ent.first_cluster_high = bpb32->root_clust >> 16;
+               if(!(rootdir = load_dir(fatfs, &ent))) {
+                       panic("FAT: failed to load FAT32 root directory\n");
+               }
+
+       } else {
+               if(!(rootdir = malloc(sizeof *rootdir))) {
+                       panic("FAT: failed to allocate root directory structure\n");
+               }
+               rootdir->fatfs = fatfs;
+
+               rootdir->max_nent = fatfs->root_size * 512 / sizeof(struct fat_dirent);
+               if(!(rootdir->ent = malloc(fatfs->root_size * 512))) {
+                       panic("FAT: failed to allocate memory for the root directory\n");
+               }
+               ptr = (char*)rootdir->ent;
+
+               num_read = 0;
+               while(num_read < fatfs->root_size) {
+                       int count = fatfs->root_size - num_read;
+                       if(count > max_sect_once) count = max_sect_once;
+
+                       read_sectors(dev, fatfs->start_sect + fatfs->root_sect + num_read, count, ptr);
+                       ptr += count * 512;
+                       num_read += count;
+               }
+
+               parse_dir_entries(rootdir);
+       }
+       rootdir->ref = 1;
+       fatfs->rootdir = rootdir;
+
+       /* assume cluster_size is a power of two */
+       fatfs->clust_mask = (fatfs->cluster_size * 512) - 1;
+       fatfs->clust_shift = 0;
+       while((1 << fatfs->clust_shift) < (fatfs->cluster_size * 512)) {
+               fatfs->clust_shift++;
+       }
+
+       /* fill generic fs structure */
+       if(!(fs = malloc(sizeof *fs))) {
+               panic("FAT: create failed to allocate memory for the filesystem structure\n");
+       }
+       fs->type = FSTYPE_FAT;
+       fs->name = fatfs->label;
+       fs->fsop = &fs_fat_ops;
+       fs->data = fatfs;
+
+
+       printf("opened %s filesystem dev: %x, start: %lld\n", typestr[fatfs->type], fatfs->dev, start);
+       if(fatfs->label[0]) {
+               printf("  volume label: %s\n", fatfs->label);
+       }
+
+       return fs;
+}
+
+static void destroy(struct filesys *fs)
+{
+       struct fatfs *fatfs = fs->data;
+       free(fatfs);
+       free(fs);
+}
+
+static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags)
+{
+       char name[MAX_NAME];
+       struct fatfs *fatfs = fs->data;
+       struct fat_dir *dir, *newdir;
+       struct fs_dirent *dent;
+       struct fat_dirent *fatdent;
+       struct fs_node *node;
+
+       if(path[0] == '/') {
+               dir = fatfs->rootdir;
+               path = fs_path_skipsep((char*)path);
+       } else {
+               if(cwdnode->fs->type != FSTYPE_FAT) {
+                       return 0;
+               }
+               dir = cwdnode->data;
+       }
+
+       while(*path) {
+               if(!dir) {
+                       /* we have more path components, yet the last one wasn't a dir */
+                       errno = ENOTDIR;
+                       return 0;
+               }
+
+               path = fs_path_next((char*)path, name, sizeof name);
+
+               if(name[0] == '.' && name[1] == 0) {
+                       continue;
+               }
+
+               if(!(dent = find_entry(dir, name))) {
+                       errno = ENOENT;
+                       return 0;
+               }
+               fatdent = dent->data;
+
+               if((fatdent->first_cluster_low | fatdent->first_cluster_high) == 0) {
+                       if(fatdent->attr == ATTR_DIR) {
+                               /* ".." entries back to the root directory seem to have a 0
+                                * cluster address as a special case
+                                */
+                               newdir = fatfs->rootdir;
+                       } else {
+                               return 0;       /* but we can't have 0-address files (right?) */
+                       }
+               } else {
+                       newdir = fatdent->attr == ATTR_DIR ? load_dir(fatfs, fatdent) : 0;
+               }
+               if(dir != fatfs->rootdir && dir != cwdnode->data) {
+                       free_dir(dir);
+               }
+               dir = newdir;
+       }
+
+
+       if(!(node = malloc(sizeof *node))) {
+               panic("FAT: open failed to allocate fs_node structure\n");
+       }
+       node->fs = fs;
+       if(dir) {
+               if(dir == fatfs->rootdir) {
+                       if(!(newdir = malloc(sizeof *newdir))) {
+                               panic("FAT: failed to allocate directory structure\n");
+                       }
+                       *newdir = *dir;
+                       dir = newdir;
+                       dir->ref = 0;
+               }
+               node->type = FSNODE_DIR;
+               node->data = dir;
+               dir->cur_ent = 0;
+               dir->ref++;
+       } else {
+               node->type = FSNODE_FILE;
+               if(!(node->data = init_file(fatfs, fatdent))) {
+                       panic("FAT: failed to allocate file entry structure\n");
+               }
+       }
+
+       return node;
+}
+
+static void close(struct fs_node *node)
+{
+       switch(node->type) {
+       case FSNODE_FILE:
+               free_file(node->data);
+               break;
+
+       case FSNODE_DIR:
+               free_dir(node->data);
+               break;
+
+       default:
+               panic("FAT: close node is not a file nor a dir\n");
+       }
+
+       free(node);
+}
+
+static long fsize(struct fs_node *node)
+{
+       struct fat_file *file;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+       file = node->data;
+       return file->ent.size_bytes;
+}
+
+static int seek(struct fs_node *node, int offs, int whence)
+{
+       struct fatfs *fatfs;
+       struct fat_file *file;
+       int64_t new_pos;
+       unsigned int cur_clust_idx, new_clust_idx;
+
+       if(node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       fatfs = node->fs->data;
+       file = node->data;
+
+       switch(whence) {
+       case FSSEEK_SET:
+               new_pos = offs;
+               break;
+
+       case FSSEEK_CUR:
+               new_pos = file->cur_pos + offs;
+               break;
+
+       case FSSEEK_END:
+               new_pos = file->ent.size_bytes + offs;
+               break;
+
+       default:
+               return -1;
+       }
+
+       if(new_pos < 0) new_pos = 0;
+
+       cur_clust_idx = file->cur_pos >> fatfs->clust_shift;
+       new_clust_idx = new_pos >> fatfs->clust_shift;
+       /* if the new position does not fall in the same cluster as the previous one
+        * re-calculate cur_clust
+        */
+       if(new_clust_idx != cur_clust_idx) {
+               if(new_clust_idx < cur_clust_idx) {
+                       file->cur_clust = find_cluster(fatfs, new_clust_idx, file->first_clust);
+               } else {
+                       file->cur_clust = find_cluster(fatfs, new_clust_idx - cur_clust_idx, file->cur_clust);
+               }
+               file->buf_valid = 0;
+       }
+       file->cur_pos = new_pos;
+       return 0;
+}
+
+static long tell(struct fs_node *node)
+{
+       struct fat_file *file;
+
+       if(!node || node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       file = node->data;
+       return file->cur_pos;
+}
+
+static int read(struct fs_node *node, void *buf, int sz)
+{
+       struct fatfs *fatfs;
+       struct fat_file *file;
+       char *bufptr = buf;
+       int num_read = 0;
+       int offs, len, buf_left, rd_left;
+       unsigned int cur_clust_idx, new_clust_idx;
+
+       if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       fatfs = node->fs->data;
+       file = node->data;
+
+       if(file->cur_clust < 0) {
+               return 0;       /* EOF */
+       }
+
+       cur_clust_idx = file->cur_pos >> fatfs->clust_shift;
+
+       while(num_read < sz) {
+               if(!file->buf_valid) {
+                       read_cluster(fatfs, file->cur_clust, file->clustbuf);
+                       file->buf_valid = 1;
+               }
+
+               offs = file->cur_pos & fatfs->clust_mask;
+               buf_left = fatfs->cluster_size * 512 - offs;
+               rd_left = sz - num_read;
+               len = buf_left < rd_left ? buf_left : rd_left;
+
+               if(file->cur_pos + len > file->ent.size_bytes) {
+                       len = file->ent.size_bytes - file->cur_pos;
+               }
+
+               memcpy(bufptr, file->clustbuf + offs, len);
+               num_read += len;
+               bufptr += len;
+
+               file->cur_pos += len;
+               if(file->cur_pos >= file->ent.size_bytes) {
+                       file->cur_clust = -1;
+                       file->buf_valid = 0;
+                       break;  /* reached EOF */
+               }
+
+               new_clust_idx = file->cur_pos >> fatfs->clust_shift;
+               if(new_clust_idx != cur_clust_idx) {
+                       file->buf_valid = 0;
+                       if((file->cur_clust = next_cluster(fatfs, file->cur_clust)) < 0) {
+                               break;  /* reached EOF */
+                       }
+                       cur_clust_idx = new_clust_idx;
+               }
+       }
+       return num_read;
+}
+
+static int write(struct fs_node *node, void *buf, int sz)
+{
+       return -1;      /* TODO */
+}
+
+static int rewinddir(struct fs_node *node)
+{
+       struct fat_dir *dir;
+
+       if(node->type != FSNODE_DIR) {
+               return -1;
+       }
+
+       dir = node->data;
+       dir->cur_ent = 0;
+       return 0;
+}
+
+static struct fs_dirent *readdir(struct fs_node *node)
+{
+       struct fat_dir *dir;
+
+       if(node->type != FSNODE_DIR) {
+               return 0;
+       }
+
+       dir = node->data;
+       if(dir->cur_ent >= dir->fsent_size) {
+               return 0;
+       }
+
+       return dir->fsent + dir->cur_ent++;
+}
+
+static int rename(struct fs_node *node, const char *name)
+{
+       return -1;      /* TODO */
+}
+
+static int remove(struct fs_node *node)
+{
+       errno = EPERM;
+       return -1;      /* TODO */
+}
+
+static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent)
+{
+       int32_t addr;
+       struct fat_dir *dir;
+       char *buf = 0;
+       int bufsz = 0;
+
+       if(dent->attr != ATTR_DIR) return 0;
+
+       addr = dent->first_cluster_low;
+       if(fs->type >= FAT32) {
+               addr |= (uint32_t)dent->first_cluster_high << 16;
+       }
+
+       do {
+               int prevsz = bufsz;
+               bufsz += fs->cluster_size * 512;
+               if(!(buf = realloc(buf, bufsz))) {
+                       panic("FAT: failed to allocate cluster buffer (%d bytes)\n", bufsz);
+               }
+
+               if(read_cluster(fs, addr, buf + prevsz) == -1) {
+                       printf("load_dir: failed to read cluster: %lu\n", (unsigned long)addr);
+                       free(buf);
+                       return 0;
+               }
+       } while((addr = next_cluster(fs, addr)) >= 0);
+
+       if(!(dir = malloc(sizeof *dir))) {
+               panic("FAT: failed to allocate directory structure\n");
+       }
+       dir->fatfs = fs;
+       dir->ent = (struct fat_dirent*)buf;
+       dir->max_nent = bufsz / sizeof *dir->ent;
+       dir->cur_ent = 0;
+       dir->ref = 0;
+
+       parse_dir_entries(dir);
+       return dir;
+}
+
+static void parse_dir_entries(struct fat_dir *dir)
+{
+       int i;
+       struct fat_dirent *dent, *prev_dent;
+       struct fs_dirent *eptr;
+       char entname[MAX_NAME];
+
+       /* create an fs_dirent array with one element for each actual entry
+        * (disregarding volume labels, and LFN entries).
+        */
+       if(!(dir->fsent = malloc(dir->max_nent * sizeof *dir->fsent))) {
+               panic("FAT: failed to allocate dirent array\n");
+       }
+       eptr = dir->fsent;
+       dent = dir->ent;
+       prev_dent = dent - 1;
+
+       for(i=0; i<dir->max_nent; i++) {
+               if(DENT_IS_NULL(dent)) break;
+
+               if(!DENT_IS_UNUSED(dent) && dent->attr != ATTR_VOLID && dent->attr != ATTR_LFN) {
+                       if(dent_filename(dent, prev_dent, entname) > 0) {
+                               if(!(eptr->name = malloc(strlen(entname) + 1))) {
+                                       panic("FAT: failed to allocate dirent name\n");
+                               }
+                               strcpy(eptr->name, entname);
+                               eptr->data = dent;
+                               eptr->type = dent->attr == ATTR_DIR ? FSNODE_DIR : FSNODE_FILE;
+                               eptr->fsize = dent->size_bytes;
+                               eptr++;
+                       }
+               }
+               if(dent->attr != ATTR_LFN) {
+                       prev_dent = dent;
+               }
+               dent++;
+       }
+       dir->fsent_size = eptr - dir->fsent;
+       dir->cur_ent = 0;
+}
+
+static void free_dir(struct fat_dir *dir)
+{
+       int i;
+       struct fat_dir *root = dir->fatfs->rootdir;
+
+       if(dir) {
+               if(--dir->ref > 0) return;
+
+               if(dir->ent != root->ent) {
+                       free(dir->ent);
+                       if(dir->fsent) {
+                               for(i=0; i<dir->fsent_size; i++) {
+                                       free(dir->fsent[i].name);
+                               }
+                               free(dir->fsent);
+                       }
+               }
+               free(dir);
+       }
+}
+
+static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent)
+{
+       struct fat_file *file;
+
+       if(!(file = calloc(1, sizeof *file))) {
+               panic("FAT: failed to allocate file structure\n");
+       }
+       if(!(file->clustbuf = malloc(fatfs->cluster_size * 512))) {
+               panic("FAT: failed to allocate file cluster buffer\n");
+       }
+       file->ent = *dent;
+       file->first_clust = dent->first_cluster_low | ((int32_t)dent->first_cluster_high << 16);
+       file->cur_clust = file->first_clust;
+       return file;
+}
+
+static void free_file(struct fat_file *file)
+{
+       if(file) {
+               free(file->clustbuf);
+               free(file);
+       }
+}
+
+static int read_sectors(int dev, uint64_t sidx, int count, void *sect)
+{
+       if(dev == -1 || dev == boot_drive_number) {
+               if(bdev_read_range(sidx, count, sect) == -1) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       printf("BUG: reading sectors from drives other than the boot drive not implemented yet\n");
+       return -1;
+}
+
+static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust)
+{
+       char *ptr = clust;
+       uint64_t saddr = (uint64_t)(addr - 2) * fatfs->cluster_size + fatfs->first_data_sect + fatfs->start_sect;
+
+       if(read_sectors(fatfs->dev, saddr, fatfs->cluster_size, ptr) == -1) {
+               return -1;
+       }
+       return 0;
+}
+
+static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf)
+{
+       int len = 0;
+       char *ptr = buf;
+       struct fat_lfnent *lfn = (struct fat_lfnent*)(dent - 1);
+
+       if(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN) {
+               /* found a long filename entry, use that */
+               do {
+                       uint16_t ustr[14], *uptr = ustr;
+                       memcpy(uptr, lfn->part1, sizeof lfn->part1);
+                       uptr += sizeof lfn->part1 / sizeof *lfn->part1;
+                       memcpy(uptr, lfn->part2, sizeof lfn->part2);
+                       uptr += sizeof lfn->part2 / sizeof *lfn->part2;
+                       memcpy(uptr, lfn->part3, sizeof lfn->part3);
+                       ustr[13] = 0;
+
+                       uptr = ustr;
+                       while(*uptr) {
+                               *ptr++ = *(char*)uptr++;
+                               len++;
+                       }
+                       *ptr = 0;
+
+                       if(uptr < ustr + 13 || (lfn->seq & 0xf0) == 0x40) break;
+                       lfn -= 1;
+               } while(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN);
+
+       } else {
+               /* regular 8.3 filename */
+               memcpy(buf, dent->name, 8);
+               buf[8] = 0;
+               clean_trailws(buf);
+               if(!buf[0]) return 0;
+
+               if(dent->name[8] && dent->name[8] != ' ') {
+                       ptr = buf + strlen(buf);
+                       *ptr++ = '.';
+                       memcpy(ptr, dent->name + 8, 3);
+                       ptr[3] = 0;
+                       clean_trailws(ptr);
+               }
+
+               len = strlen(buf);
+       }
+       return len;
+}
+
+static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name)
+{
+       int i;
+       struct fs_dirent *dent = dir->fsent;
+
+       for(i=0; i<dir->fsent_size; i++) {
+               if(strcasecmp(dent->name, name) == 0) {
+                       return dent;
+               }
+               dent++;
+       }
+       return 0;
+}
+
+static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr)
+{
+       uint32_t res = 0xffffffff;
+
+       switch(fatfs->type) {
+       case FAT12:
+               {
+                       uint32_t idx = addr + addr / 2;
+                       res = ((uint16_t*)fatfs->fat)[idx];
+
+                       if(idx & 1) {
+                               res >>= 4;              /* odd entries end up on the high 12 bits */
+                       } else {
+                               res &= 0xfff;   /* even entries end up on the low 12 bits */
+                       }
+               }
+               break;
+
+       case FAT16:
+               res = ((uint16_t*)fatfs->fat)[addr];
+               break;
+
+       case FAT32:
+       case EXFAT:
+               res = ((uint32_t*)fatfs->fat)[addr];
+               break;
+
+       default:
+               break;
+       }
+
+       return res;
+}
+
+static int32_t next_cluster(struct fatfs *fatfs, int32_t addr)
+{
+       uint32_t fatval = read_fat(fatfs, addr);
+
+       if(fatval == 0) return -1;
+
+       switch(fatfs->type) {
+       case FAT12:
+               if(fatval >= 0xff8) return -1;
+               break;
+
+       case FAT16:
+               if(fatval >= 0xfff8) return -1;
+               break;
+
+       case FAT32:
+       case EXFAT:     /* XXX ? */
+               if(fatval >= 0xffffff8) return -1;
+               break;
+
+       default:
+               break;
+       }
+
+       return fatval;
+}
+
+static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust)
+{
+       while(count-- > 0 && (clust = next_cluster(fatfs, clust)) >= 0);
+       return clust;
+}
+
+/*
+static void dbg_printdir(struct fat_dirent *dir, int max_entries)
+{
+       char name[MAX_NAME];
+       struct fat_dirent *prev = dir - 1;
+       struct fat_dirent *end = max_entries > 0 ? dir + max_entries : 0;
+
+       while(!DENT_IS_NULL(dir) && (!end || dir < end)) {
+               if(!DENT_IS_UNUSED(dir) && dir->attr != ATTR_VOLID && dir->attr != ATTR_LFN) {
+                       if(dent_filename(dir, prev, name) > 0) {
+                               printf("%s%c\n", name, dir->attr == ATTR_DIR ? '/' : ' ');
+                       }
+               }
+               if(dir->attr != ATTR_LFN) {
+                       prev = dir;
+               }
+               dir++;
+       }
+}
+*/
+
+static void clean_trailws(char *s)
+{
+       char *p;
+
+       if(!s || !*s) return;
+
+       p = s + strlen(s) - 1;
+       while(p >= s && isspace(*p)) p--;
+       p[1] = 0;
+}
diff --git a/src/fsmem.c b/src/fsmem.c
new file mode 100644 (file)
index 0000000..e4cbdd1
--- /dev/null
@@ -0,0 +1,520 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <alloca.h>
+#include "fs.h"
+#include "panic.h"
+
+#define MAX_NAME       120
+
+struct memfs_node;
+struct memfs_file;
+struct memfs_dir;
+
+struct memfs {
+       struct memfs_node *rootdir;
+};
+
+struct memfs_dir {
+       struct memfs_node *clist, *ctail;
+       struct memfs_node *cur;
+};
+
+struct odir {
+       struct memfs_dir *dir;
+       struct memfs_node *cur;
+       struct fs_dirent dent;
+};
+
+struct memfs_file {
+       char *data;
+       long size, max_size;
+};
+
+struct ofile {
+       struct memfs_file *file;
+       long cur_pos;
+};
+
+struct memfs_node {
+       union {
+               struct memfs_file file;
+               struct memfs_dir dir;
+       };
+       int type;
+       char name[MAX_NAME + 4];
+       struct memfs_node *parent;
+       struct memfs_node *next;
+       struct fs_node *fsnode; /* we need it for crossing mounts in fs_open */
+};
+
+
+static void destroy(struct filesys *fs);
+
+static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags);
+static void close(struct fs_node *node);
+static long fsize(struct fs_node *node);
+static int seek(struct fs_node *node, int offs, int whence);
+static long tell(struct fs_node *node);
+static int read(struct fs_node *node, void *buf, int sz);
+static int write(struct fs_node *node, void *buf, int sz);
+static int rewinddir(struct fs_node *node);
+static struct fs_dirent *readdir(struct fs_node *node);
+static int rename(struct fs_node *node, const char *name);
+static int remove(struct fs_node *node);
+
+static struct fs_node *create_fsnode(struct filesys *fs, struct memfs_node *n);
+
+static struct memfs_node *alloc_node(int type);
+static void free_node(struct memfs_node *node);
+
+static struct memfs_node *find_entry(struct memfs_node *dir, const char *name);
+static void add_child(struct memfs_node *dir, struct memfs_node *n);
+
+
+static struct fs_operations fs_mem_ops = {
+       destroy,
+       open, close,
+
+       fsize,
+       seek, tell,
+       read, write,
+
+       rewinddir, readdir,
+
+       rename, remove
+};
+
+
+struct filesys *fsmem_create(int dev, uint64_t start, uint64_t size)
+{
+       struct filesys *fs;
+       struct memfs *memfs;
+
+       if(dev != DEV_MEMDISK) {
+               return 0;
+       }
+
+       if(!(memfs = malloc(sizeof *memfs))) {
+               panic("MEMFS: create failed to allocate memory\n");
+       }
+       if(!(memfs->rootdir = alloc_node(FSNODE_DIR))) {
+               panic("MEMFS: failed to allocate root dir\n");
+       }
+
+       if(!(fs = malloc(sizeof *fs))) {
+               panic("MEMFS: failed to allocate memory for the filesystem structure\n");
+       }
+       fs->type = FSTYPE_MEM;
+       fs->name = 0;
+       fs->fsop = &fs_mem_ops;
+       fs->data = memfs;
+
+       return fs;
+}
+
+static void destroy(struct filesys *fs)
+{
+       struct memfs *memfs = fs->data;
+       free_node((struct memfs_node*)memfs->rootdir);
+       free(memfs);
+       free(fs);
+}
+
+static struct fs_node *open_mount(struct filesys *fs, const char *path, unsigned int flags)
+{
+       char *newpath;
+
+       newpath = alloca(strlen(path) + 2);
+       newpath[0] = '/';
+       strcpy(newpath + 1, path);
+
+       return fs->fsop->open(fs, newpath, flags);
+}
+
+#define NODE_IS_MNTPT(n)       ((n)->fsnode && (n)->fsnode->mnt)
+
+static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags)
+{
+       struct memfs_node *node, *parent;
+       struct memfs *memfs = fs->data;
+       char name[MAX_NAME + 1];
+
+       if(path[0] == '/') {
+               node = memfs->rootdir;
+               path = fs_path_skipsep((char*)path);
+       } else {
+               if(cwdnode->fs->type != FSTYPE_MEM) {
+                       return 0;
+               }
+               node = (struct memfs_node*)((struct odir*)cwdnode->data)->dir;
+       }
+       assert(node->type == FSNODE_DIR);
+
+       while(*path) {
+               if(node->type != FSNODE_DIR) {
+                       /* we have more path components, yet the last one wasn't a dir */
+                       errno = ENOTDIR;
+                       return 0;
+               }
+               /* check if it's another filesystem hanging off this directory, and if
+                * so, recursively call that open function to complete the operation
+                */
+               if(NODE_IS_MNTPT(node)) {
+                       return open_mount(node->fsnode->mnt, path, flags);
+               }
+
+               path = fs_path_next((char*)path, name, sizeof name);
+               parent = node;
+
+               if(!(node = find_entry(node, name))) {
+                       if(*path || !(flags & FSO_CREATE)) {
+                               errno = ENOENT;
+                               return 0;
+                       }
+                       /* create and add */
+                       if(!(node = alloc_node((flags & FSO_DIR) ? FSNODE_DIR : FSNODE_FILE))) {
+                               errno = ENOMEM;
+                               return 0;
+                       }
+                       strcpy(node->name, name);
+                       add_child(parent, node);
+                       return create_fsnode(fs, node);
+               }
+       }
+
+       /* we need to check for mount points here too, because the check in the loop
+        * above is not going to be reached when the mount point is the last part of
+        * the path string (for instance opendir("/mnt/foo"))
+        */
+       if(NODE_IS_MNTPT(node)) {
+               return open_mount(node->fsnode->mnt, path, flags);
+       }
+
+       if(flags & FSO_EXCL) {
+               errno = EEXIST;
+               return 0;
+       }
+       return create_fsnode(fs, node);
+}
+
+static struct fs_node *create_fsnode(struct filesys *fs, struct memfs_node *n)
+{
+       struct fs_node *fsn;
+       struct ofile *of;
+       struct odir *od;
+
+       if(!(fsn = calloc(1, sizeof *fsn))) {
+               errno = ENOMEM;
+               return 0;
+       }
+
+       if(n->type == FSNODE_FILE) {
+               if(!(of = malloc(sizeof *of))) {
+                       errno = ENOMEM;
+                       free(fsn);
+                       return 0;
+               }
+               of->file = &n->file;
+               of->cur_pos = 0;
+               fsn->data = of;
+       } else {
+               if(!(od = malloc(sizeof *od))) {
+                       errno = ENOMEM;
+                       free(fsn);
+                       return 0;
+               }
+               od->dir = &n->dir;
+               od->cur = n->dir.clist;
+               fsn->data = od;
+       }
+
+       if(!n->fsnode) {
+               n->fsnode = fsn;
+       }
+
+       fsn->fs = fs;
+       fsn->type = n->type;
+       return fsn;
+}
+
+static void close(struct fs_node *node)
+{
+       if(!node) return;
+
+       free(node->data);       /* free the copy of memfs_node allocated by create_fsnode */
+       free(node);
+}
+
+static long fsize(struct fs_node *node)
+{
+       struct ofile *of;
+       if(!node || node->type != FSNODE_FILE) {
+               return -1;
+       }
+       of = node->data;
+       return of->file->size;
+}
+
+static int seek(struct fs_node *node, int offs, int whence)
+{
+       struct ofile *of;
+       long new_pos;
+
+       if(!node || node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       of = node->data;
+
+       switch(whence) {
+       case FSSEEK_SET:
+               new_pos = offs;
+               break;
+
+       case FSSEEK_CUR:
+               new_pos = of->cur_pos + offs;
+               break;
+
+       case FSSEEK_END:
+               new_pos = of->file->size + offs;
+               break;
+
+       default:
+               return -1;
+       }
+
+       if(new_pos < 0) new_pos = 0;
+
+       of->cur_pos = new_pos;
+       return 0;
+}
+
+static long tell(struct fs_node *node)
+{
+       struct ofile *of;
+
+       if(!node || node->type != FSNODE_FILE) {
+               return -1;
+       }
+       of = node->data;
+       return of->cur_pos;
+}
+
+static int read(struct fs_node *node, void *buf, int sz)
+{
+       struct ofile *of;
+
+       if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       of = node->data;
+
+       if(sz > of->file->size - of->cur_pos) {
+               sz = of->file->size - of->cur_pos;
+       }
+       memcpy(buf, of->file->data + of->cur_pos, sz);
+       of->cur_pos += sz;
+       return sz;
+}
+
+static int write(struct fs_node *node, void *buf, int sz)
+{
+       struct ofile *of;
+       int total_sz, new_max_sz;
+       void *tmp;
+
+       if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) {
+               return -1;
+       }
+
+       of = node->data;
+       total_sz = of->cur_pos + sz;
+       if(total_sz > of->file->max_size) {
+               if(total_sz < of->file->max_size * 2) {
+                       new_max_sz = of->file->max_size * 2;
+               } else {
+                       new_max_sz = total_sz;
+               }
+               if(!(tmp = realloc(of->file->data, new_max_sz))) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               of->file->data = tmp;
+               of->file->max_size = new_max_sz;
+       }
+
+       memcpy(of->file->data + of->cur_pos, buf, sz);
+       of->cur_pos += sz;
+       if(of->cur_pos > of->file->size) of->file->size = of->cur_pos;
+       return sz;
+}
+
+static int rewinddir(struct fs_node *node)
+{
+       struct odir *od;
+
+       if(!node || node->type != FSNODE_DIR) {
+               return -1;
+       }
+
+       od = node->data;
+       od->cur = od->dir->clist;
+       return 0;
+}
+
+static struct fs_dirent *readdir(struct fs_node *node)
+{
+       struct odir *od;
+       struct memfs_node *n;
+       struct fs_dirent *fsd;
+
+       if(!node || node->type != FSNODE_DIR) {
+               return 0;
+       }
+
+       od = node->data;
+       fsd = &od->dent;
+
+       n = od->cur;
+       if(!n) return 0;
+
+       od->cur = od->cur->next;
+
+       fsd->name = n->name;
+       fsd->data = 0;
+       fsd->type = n->type;
+       fsd->fsize = n->file.size;
+
+       return fsd;
+}
+
+static int rename(struct fs_node *node, const char *name)
+{
+       struct memfs_node *n = (struct memfs_node*)((struct odir*)node->data)->dir;
+       strncpy(n->name, name, MAX_NAME);
+       n->name[MAX_NAME] = 0;
+       return 0;
+}
+
+static int remove(struct fs_node *node)
+{
+       int res = -1;
+       struct odir *od = 0;
+       struct ofile *of = 0;
+       struct memfs_node *n, *par, *prev, dummy;
+
+       if(node->type == FSNODE_DIR) {
+               od = node->data;
+               n = (struct memfs_node*)od->dir;
+
+               if(n->dir.clist) {
+                       errno = EEXIST;
+                       return -1;
+               }
+       } else {
+               of = node->data;
+               n = (struct memfs_node*)of->file;
+       }
+       par = n->parent;
+
+       if(!par) {
+               errno = EBUSY;
+               return -1;
+       }
+
+       dummy.next = par->dir.clist;
+       prev = &dummy;
+       while(prev->next) {
+               if(prev->next == n) {
+                       if(par->dir.ctail == n) {
+                               par->dir.ctail = prev;
+                       }
+                       prev->next = n->next;
+                       free_node(n);
+                       res = 0;
+                       break;
+               }
+               prev = prev->next;
+       }
+       par->dir.clist = dummy.next;
+       return res;
+}
+
+static struct memfs_node *alloc_node(int type)
+{
+       struct memfs_node *node;
+
+       if(!(node = calloc(1, sizeof *node))) {
+               return 0;
+       }
+       node->type = type;
+       return node;
+}
+
+static void free_node(struct memfs_node *node)
+{
+       if(!node) return;
+
+       switch(node->type) {
+       case FSNODE_FILE:
+               free(node->file.data);
+               break;
+
+       case FSNODE_DIR:
+               while(node->dir.clist) {
+                       struct memfs_node *n = node->dir.clist;
+                       node->dir.clist = n->next;
+                       free_node(n);
+               }
+               break;
+       }
+}
+
+static struct memfs_node *find_entry(struct memfs_node *dnode, const char *name)
+{
+       struct memfs_node *n;
+
+       if(strcmp(name, ".") == 0) return dnode;
+       if(strcmp(name, "..") == 0) return dnode->parent;
+
+       n = dnode->dir.clist;
+       while(n) {
+               if(strcasecmp(n->name, name) == 0) {
+                       return n;
+               }
+               n = n->next;
+       }
+       return 0;
+}
+
+static void add_child(struct memfs_node *dnode, struct memfs_node *n)
+{
+       if(dnode->dir.clist) {
+               dnode->dir.ctail->next = n;
+               dnode->dir.ctail = n;
+       } else {
+               dnode->dir.clist = dnode->dir.ctail = n;
+       }
+       n->parent = dnode;
+}
diff --git a/src/libc/dirent.c b/src/libc/dirent.c
new file mode 100644 (file)
index 0000000..1af59fe
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "dirent.h"
+#include "fs.h"
+
+struct DIR {
+       struct fs_node *fsn;
+       struct dirent dent;
+};
+
+DIR *opendir(const char *path)
+{
+       DIR *dir;
+       struct fs_node *node;
+
+       if(!path) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if(!(node = fs_open(path, 0))) {
+               errno = ENOENT;
+               return 0;
+       }
+       if(node->type != FSNODE_DIR) {
+               errno = ENOTDIR;
+               fs_close(node);
+               return 0;
+       }
+
+       if(!(dir = malloc(sizeof *dir))) {
+               errno = ENOMEM;
+               fs_close(node);
+               return 0;
+       }
+       dir->fsn = node;
+
+       return dir;
+}
+
+int closedir(DIR *dir)
+{
+       if(!dir) {
+               errno = EINVAL;
+               return -1;
+       }
+       fs_close(dir->fsn);
+       free(dir);
+       return 0;
+}
+
+void rewinddir(DIR *dir)
+{
+       if(!dir) {
+               errno = EINVAL;
+               return;
+       }
+       fs_rewinddir(dir->fsn);
+}
+
+struct dirent *readdir(DIR *dir)
+{
+       struct fs_dirent *fsdent;
+
+       if(!dir) {
+               errno = EINVAL;
+               return 0;
+       }
+       if(!(fsdent = fs_readdir(dir->fsn))) {
+               return 0;
+       }
+
+       strcpy(dir->dent.d_name, fsdent->name);
+       dir->dent.d_type = fsdent->type == FSNODE_DIR ? DT_DIR : DT_REG;
+       dir->dent.d_fsize = fsdent->fsize;
+       return &dir->dent;
+}
diff --git a/src/libc/dirent.h b/src/libc/dirent.h
new file mode 100644 (file)
index 0000000..7f0da64
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef DIRENT_H_
+#define DIRENT_H_
+
+typedef struct DIR DIR;
+
+enum {
+       DT_UNKNOWN = 0,
+       DT_DIR = 4,
+       DT_REG = 8
+};
+
+struct dirent {
+       char d_name[256];
+       unsigned char d_type;
+       long d_fsize;
+};
+
+DIR *opendir(const char *path);
+int closedir(DIR *dir);
+
+void rewinddir(DIR *dir);
+
+struct dirent *readdir(DIR *dir);
+
+#endif /* DIRENT_H_ */
diff --git a/src/libc/file.c b/src/libc/file.c
new file mode 100644 (file)
index 0000000..cc73f71
--- /dev/null
@@ -0,0 +1,216 @@
+#ifndef FILE_H_
+#define FILE_H_
+
+#include <stdio.h>
+#include <errno.h>
+#include "fs.h"
+#include "panic.h"
+
+enum {
+       MODE_READ = 1,
+       MODE_WRITE = 2,
+       MODE_APPEND = 4,
+       MODE_CREATE = 8,
+       MODE_TRUNCATE = 16
+};
+
+enum {
+       STATUS_EOF      = 1,
+       STATUS_ERR      = 2
+};
+
+struct FILE {
+       unsigned int mode;
+       unsigned int status;
+       struct fs_node *fsn;
+};
+
+FILE *fopen(const char *path, const char *mode)
+{
+       FILE *fp;
+       struct fs_node *node;
+       unsigned int mflags = 0;
+
+       while(*mode) {
+               int c = *mode++;
+               switch(c) {
+               case 'r':
+                       mflags |= MODE_READ;
+                       if(*mode == '+') {
+                               mflags |= MODE_WRITE;
+                               mode++;
+                       }
+                       break;
+               case 'w':
+                       mflags |= MODE_WRITE | MODE_TRUNCATE;
+                       if(*mode == '+') {
+                               mflags |= MODE_READ | MODE_CREATE;
+                               mode++;
+                       }
+                       break;
+               case 'a':
+                       mflags |= MODE_WRITE | MODE_APPEND;
+                       if(*mode == '+') {
+                               mflags |= MODE_READ | MODE_CREATE;
+                               mode++;
+                       }
+                       break;
+               case 'b':
+                       break;
+               default:
+                       errno = EINVAL;
+                       return 0;
+               }
+       }
+
+       if(!(node = fs_open(path, 0))) {
+               /* TODO: create */
+               errno = ENOENT; /* TODO */
+               return 0;
+       }
+       if(node->type != FSNODE_FILE) {
+               errno = EISDIR;
+               return 0;
+       }
+
+       if(!(fp = malloc(sizeof *fp))) {
+               errno = ENOMEM;
+               return 0;
+       }
+       fp->fsn = node;
+       fp->mode = mflags;
+       fp->status = 0;
+
+       return fp;
+}
+
+int fclose(FILE *fp)
+{
+       if(!fp) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       fs_close(fp->fsn);
+       free(fp);
+       return 0;
+}
+
+long filesize(FILE *fp)
+{
+       return fs_filesize(fp->fsn);
+}
+
+int fseek(FILE *fp, long offset, int from)
+{
+       if(!fp) {
+               errno = EINVAL;
+               return -1;
+       }
+       if(from < 0 || from > 2) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       fs_seek(fp->fsn, offset, from);
+       fp->status &= ~STATUS_EOF;
+       return 0;
+}
+
+void rewind(FILE *fp)
+{
+       fseek(fp, 0, SEEK_SET);
+}
+
+long ftell(FILE *fp)
+{
+       if(!fp) {
+               errno = EINVAL;
+               return -1;
+       }
+       return fs_tell(fp->fsn);
+}
+
+size_t fread(void *buf, size_t size, size_t count, FILE *fp)
+{
+       int res;
+       if(!fp) return 0;
+       if((res = fs_read(fp->fsn, buf, size * count)) == -1) {
+               fp->status |= STATUS_EOF;
+               return 0;
+       }
+       return res / size;
+}
+
+size_t fwrite(const void *buf, size_t size, size_t count, FILE *fp)
+{
+       int res;
+       if(!fp) return 0;
+       if(!(fp->mode & MODE_WRITE)) {
+               fp->status |= STATUS_ERR;
+               return 0;
+       }
+       if((res = fs_write(fp->fsn, (void*)buf, size * count)) == -1) {
+               return 0;
+       }
+       return res / size;
+}
+
+int fgetc(FILE *fp)
+{
+       unsigned char c;
+       if(fread(&c, 1, 1, fp) < 1) {
+               return -1;
+       }
+       return c;
+}
+
+char *fgets(char *buf, int size, FILE *fp)
+{
+       int c;
+       char *s = buf;
+
+       while(--size > 0 && (c = fgetc(fp)) >= 0) {
+               *s++ = c;
+               if(c == '\n') break;
+       }
+       *s = 0;
+       return s > buf ? buf : 0;
+}
+
+int fputc(int c, FILE *fp)
+{
+       if(fp == stdout || fp == stderr) {
+               return putchar(c);
+       }
+
+       panic("fputc on anything other than stdout/stderr not implemented yet\n");
+       return -1;
+}
+
+int fflush(FILE *fp)
+{
+       if(fp == stdout || fp == stderr) {
+               return 0;       /* do nothing */
+       }
+
+       panic("fflush on anything other than stdout/stderr not implemented yet\n");
+       return -1;
+}
+
+int feof(FILE *fp)
+{
+       return (fp->status & STATUS_EOF) != 0;
+}
+
+int ferror(FILE *fp)
+{
+       return (fp->status & STATUS_ERR) != 0;
+}
+
+void clearerr(FILE *fp)
+{
+       fp->status = 0;
+}
+
+#endif /* FILE_H_ */
diff --git a/src/libc/float.h b/src/libc/float.h
new file mode 100644 (file)
index 0000000..073bf6d
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef FLOAT_H_
+#define FLOAT_H_
+
+#define FLT_MIN        __FLT_MIN__
+#define FLT_MAX        __FLT_MAX__
+
+#endif /* FLOAT_H_ */
diff --git a/src/libc/math.c b/src/libc/math.c
new file mode 100644 (file)
index 0000000..3f3e5de
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "math.h"
+
+static double calc_pow(double x, double y, double precision);
+
+double pow(double x, double y)
+{
+       if(y == 0.0 || y == -0.0) {
+               return 1.0;
+       }
+       if(y == 1.0) {
+               return x;
+       }
+       if(y == -INFINITY) {
+               return fabs(x) < 1.0 ? INFINITY : 0.0;
+       }
+       if(y == INFINITY) {
+               return fabs(x) < 1.0 ? 0.0 : INFINITY;
+       }
+       return calc_pow(x, y, 1e-6);
+}
+
+static double calc_pow(double x, double y, double precision)
+{
+       if(y < 0.0) {
+               return 1.0 / calc_pow(x, -y, precision);
+       }
+       if(y >= 10.0) {
+               double p = calc_pow(x, y / 2.0, precision / 2.0);
+               return p * p;
+       }
+       if(y >= 1.0) {
+               return x * calc_pow(x, y - 1.0, precision);
+       }
+       if(precision >= 1) {
+               return __builtin_sqrt(x);
+       }
+       return __builtin_sqrt(calc_pow(x, y * 2.0, precision * 2.0));
+}
diff --git a/src/libc/math.h b/src/libc/math.h
new file mode 100644 (file)
index 0000000..e3b0f23
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef MATH_H_
+#define MATH_H_
+
+#define INFINITY       __builtin_inff()
+#define NAN                    __builtin_nanf
+
+#define M_PI           3.141592653589793
+
+#define sin(x)         __builtin_sin(x)
+#define cos(x)         __builtin_cos(x)
+#define tan(x)         __builtin_tan(x)
+#define fabs(x)                __builtin_fabs(x)
+#define fmod(x, y)     __builtin_fmod(x, y)
+#define sqrt(x)                __builtin_sqrt(x)
+#define atan2(y, x)    __builtin_atan2(y, x)
+
+double pow(double x, double y);
+
+#endif /* MATH_H_ */
diff --git a/src/libc/rand.c b/src/libc/rand.c
new file mode 100644 (file)
index 0000000..de159c2
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+/* random number generator, based on this description of the algorithm
+ * used by the GNU libc: https://www.mathstat.dal.ca/~selinger/random
+ */
+#include <stdlib.h>
+#include <inttypes.h>
+
+static int init_done;
+static int32_t rng[34];
+static int32_t *ptr0, *ptr1;
+
+int rand(void)
+{
+       int res;
+
+       if(!init_done) {
+               srand(1);
+       }
+
+       *ptr1 += *ptr0;
+       res = (uint32_t)*ptr1 >> 1;
+       if(++ptr0 >= rng + 34) ptr0 = rng;
+       if(++ptr1 >= rng + 34) ptr1 = rng;
+
+       return res;
+}
+
+void srand(unsigned int seed)
+{
+       int i;
+
+       init_done = 1;
+       if(seed == 0) seed = 1;
+
+       rng[0] = seed;
+       for(i=1; i<31; i++) {
+               rng[i] = (16807 * rng[i - 1]) % RAND_MAX;
+               if(rng[i] < 0) rng[i] += RAND_MAX;
+       }
+       for(i=31; i<34; i++) {
+               rng[i] = rng[i - 31];
+       }
+       ptr0 = rng + 3;
+       ptr1 = rng + 31;
+
+       for(i=34; i<344; i++) {
+               rand();
+       }
+}
diff --git a/src/libc/setjmp.c b/src/libc/setjmp.c
new file mode 100644 (file)
index 0000000..517563b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <setjmp.h>
+
+int setjmp(jmp_buf buf)
+{
+       return __builtin_setjmp(buf);
+}
+
+void longjmp(jmp_buf buf, int val)
+{
+       __builtin_longjmp(buf, 1);
+}
diff --git a/src/libc/setjmp.h b/src/libc/setjmp.h
new file mode 100644 (file)
index 0000000..c92e93d
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef SETJMP_H_
+#define SETJMP_H_
+
+typedef unsigned long jmp_buf[5];
+
+int setjmp(jmp_buf buf);
+void longjmp(jmp_buf buf, int val);
+
+#endif /* SETJMP_H_ */
diff --git a/src/libc/time.c b/src/libc/time.c
new file mode 100644 (file)
index 0000000..ecff673
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "time.h"
+#include "rtc.h"
+#include "timer.h"
+#include "config.h"
+
+#define MINSEC         60
+#define HOURSEC                (60 * MINSEC)
+#define DAYSEC         (24 * HOURSEC)
+#define YEARDAYS(x)    (is_leap_year(x) ? 366 : 365)
+
+/* 1-1-1970 was a thursday */
+#define EPOCH_WDAY     4
+
+static int is_leap_year(int yr);
+
+static int mdays[2][12] = {
+       {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+       {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
+
+static char *wday[] = {
+       "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+static char *mon[] = {
+       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+
+time_t time(time_t *tp)
+{
+       time_t res = start_time + nticks / TICK_FREQ_HZ;
+
+       if(tp) *tp = res;
+       return res;
+}
+
+char *asctime(struct tm *tm)
+{
+       static char buf[64];
+       return asctime_r(tm, buf);
+}
+
+char *asctime_r(struct tm *tm, char *buf)
+{
+       sprintf(buf, "%s %s %d %02d:%02d:%02d %d\n", wday[tm->tm_wday],
+                       mon[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min,
+                       tm->tm_sec, tm->tm_year + 1900);
+       return buf;
+}
+
+time_t mktime(struct tm *tm)
+{
+       int i, num_years = tm->tm_year - 70;
+       int year = 1970;
+       int days = day_of_year(tm->tm_year + 1900, tm->tm_mon, tm->tm_mday - 1);
+
+       /* set correct yearday */
+       tm->tm_yday = days;
+
+       for(i=0; i<num_years; i++) {
+               days += YEARDAYS(year++);
+       }
+
+       /* set wday correctly */
+       tm->tm_wday = (days + EPOCH_WDAY) % 7;
+
+       return (time_t)days * DAYSEC + tm->tm_hour * HOURSEC +
+               tm->tm_min * MINSEC + tm->tm_sec;
+}
+
+struct tm *gmtime(time_t *tp)
+{
+       static struct tm tm;
+       return gmtime_r(tp, &tm);
+}
+
+struct tm *gmtime_r(time_t *tp, struct tm *tm)
+{
+       int year, days, leap, yrdays;
+       time_t t;
+
+       year = 1970;
+       days = *tp / DAYSEC;
+       t = *tp % DAYSEC;
+
+       tm->tm_wday = (days + EPOCH_WDAY) % 7;
+
+       while(days >= (yrdays = YEARDAYS(year))) {
+               days -= yrdays;
+               year++;
+       }
+       tm->tm_year = year - 1900;
+       tm->tm_yday = days;
+
+       leap = is_leap_year(year);
+       tm->tm_mon = 0;
+       while(days >= mdays[leap][tm->tm_mon]) {
+               days -= mdays[leap][tm->tm_mon++];
+       }
+
+       tm->tm_mday = days + 1;
+
+       tm->tm_hour = t / HOURSEC;
+       t %= HOURSEC;
+       tm->tm_min = t / MINSEC;
+       tm->tm_sec = t % MINSEC;
+       return tm;
+}
+
+struct tm *localtime(time_t *tp)
+{
+       static struct tm tm;
+       return localtime_r(tp, &tm);
+}
+
+struct tm *localtime_r(time_t *tp, struct tm *tm)
+{
+       time_t t = *tp + timezone;
+       return gmtime_r(&t, tm);
+}
+
+int day_of_year(int year, int mon, int day)
+{
+       int i, yday, leap;
+
+       leap = is_leap_year(year) ? 1 : 0;
+       yday = day;
+
+       for(i=0; i<mon; i++) {
+               yday += mdays[leap][i];
+       }
+       return yday;
+}
+
+static int is_leap_year(int yr)
+{
+       /* exceptions first */
+       if(yr % 400 == 0) {
+               return 1;
+       }
+       if(yr % 100 == 0) {
+               return 0;
+       }
+       /* standard case */
+       return yr % 4 == 0;
+}
diff --git a/src/libc/time.h b/src/libc/time.h
new file mode 100644 (file)
index 0000000..2335fd8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef TIME_H_
+#define TIME_H_
+
+typedef long time_t;
+
+struct tm {
+       int tm_sec;
+       int tm_min;
+       int tm_hour;
+       int tm_mday;
+       int tm_mon;
+       int tm_year;
+       int tm_wday;
+       int tm_yday;
+       int tm_isdst;
+};
+
+#define TZOFFS(x)      ((x) * 3600)
+long timezone;
+
+time_t time(time_t *tp);
+char *asctime(struct tm *tm);
+char *asctime_r(struct tm *tm, char *buf);
+
+time_t mktime(struct tm *tm);
+struct tm *gmtime(time_t *tp);
+struct tm *gmtime_r(time_t *tp, struct tm *tm);
+struct tm *localtime(time_t *tp);
+struct tm *localtime_r(time_t *tp, struct tm *tm);
+
+/* non-standard helpers */
+int day_of_year(int year, int mon, int day);
+
+
+#endif /* TIME_H_ */
diff --git a/src/libc/unistd.c b/src/libc/unistd.c
new file mode 100644 (file)
index 0000000..1fc0542
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include <errno.h>
+#include "unistd.h"
+#include "fs.h"
+
+int chdir(const char *path)
+{
+       return fs_chdir(path);
+}
+
+char *getcwd(char *buf, int sz)
+{
+       char *cwd = fs_getcwd();
+       int len = strlen(cwd);
+       if(len + 1 > sz) {
+               errno = ERANGE;
+               return 0;
+       }
+       memcpy(buf, cwd, len + 1);
+       return buf;
+}
+
+int mkdir(const char *path, int mode)
+{
+       struct fs_node *fsn;
+
+       if(!(fsn = fs_open(path, FSO_CREATE | FSO_DIR | FSO_EXCL))) {
+               return -1;
+       }
+       fs_close(fsn);
+       return 0;
+}
+
+int rmdir(const char *path)
+{
+       struct fs_node *fsn;
+
+       if(!(fsn = fs_open(path, FSO_DIR))) {
+               return -1;
+       }
+       fs_remove(fsn);
+       fs_close(fsn);
+       return 0;
+}
diff --git a/src/libc/unistd.h b/src/libc/unistd.h
new file mode 100644 (file)
index 0000000..1ed4cf2
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef UNISTD_H_
+#define UNISTD_H_
+
+int chdir(const char *path);
+char *getcwd(char *buf, int sz);
+
+int mkdir(const char *path, int mode);
+int rmdir(const char *path);
+
+#endif /* UNISTD_H_ */
diff --git a/src/mtab.c b/src/mtab.c
new file mode 100644 (file)
index 0000000..3d5f169
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include "mtab.h"
+
+int mtab_add(struct fs_node *node, struct filesys *fs)
+{
+       struct mount *m;
+
+       if(!(m = malloc(sizeof *m))) {
+               return -1;
+       }
+       m->mpt = node;
+       m->fs = fs;
+       m->next = 0;
+
+       if(mnt_list) {
+               mnt_tail->next = m;
+               mnt_tail = m;
+       } else {
+               mnt_list = mnt_tail = m;
+       }
+       mnt_count++;
+       return 0;
+}
+
+int mtab_remove_node(struct fs_node *node)
+{
+       int res = -1;
+       struct mount dummy;
+       struct mount *prev, *m;
+
+       dummy.next = mnt_list;
+       prev = &dummy;
+
+       while(prev->next) {
+               m = prev->next;
+               if(m->mpt == node) {
+                       prev->next = m->next;
+                       free(m);
+                       res = 0;
+                       break;
+               }
+               prev = prev->next;
+       }
+       mnt_list = dummy.next;
+       return res;
+}
diff --git a/src/mtab.h b/src/mtab.h
new file mode 100644 (file)
index 0000000..c98aad2
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef MTAB_H_
+#define MTAB_H_
+
+#include "fs.h"
+
+struct mount {
+       struct fs_node *mpt;
+       struct filesys *fs;
+       struct mount *next;
+};
+struct mount *mnt_list, *mnt_tail;
+int mnt_count;
+
+int mtab_add(struct fs_node *node, struct filesys *fs);
+int mtab_remove_node(struct fs_node *node);
+
+#endif /* MTAB_H_ */
diff --git a/src/part.c b/src/part.c
new file mode 100644 (file)
index 0000000..df007ee
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "part.h"
+#include "boot.h"
+#include "bootdev.h"
+#include "ptype.h"
+
+struct part_record {
+       uint8_t stat;
+       uint8_t first_head, first_cyl, first_sect;
+       uint8_t type;
+       uint8_t last_head, last_cyl, last_sect;
+       uint32_t first_lba;
+       uint32_t nsect_lba;
+} __attribute__((packed));
+
+static int read_sector(int dev, uint64_t sidx);
+static const char *ptype_name(int type);
+
+static unsigned char sectdata[512];
+
+#define BOOTSIG_OFFS   510
+#define PTABLE_OFFS            0x1be
+
+#define BOOTSIG                        0xaa55
+
+#define IS_MBR                 (sidx == 0)
+#define IS_FIRST_EBR   (!IS_MBR && (first_ebr_offs == 0))
+
+int read_partitions(int dev, struct partition *ptab, int ptabsz)
+{
+       int i, num_rec, nparts = 0;
+       int num_bootrec = 0;
+       struct partition *part = ptab;
+       struct part_record *prec;
+       uint64_t sidx = 0;
+       uint64_t first_ebr_offs = 0;
+
+       if(ptabsz <= 0) {
+               ptab = 0;
+       }
+
+       do {
+               if(IS_FIRST_EBR) {
+                       first_ebr_offs = sidx;
+               }
+
+               if(read_sector(dev, sidx) == -1) {
+                       printf("failed to read sector %llu\n", (unsigned long long)sidx);
+                       return -1;
+               }
+               if(*(uint16_t*)(sectdata + BOOTSIG_OFFS) != BOOTSIG) {
+                       printf("invalid partitionm table, sector %llu has no magic\n", (unsigned long long)sidx);
+                       return -1;
+               }
+               prec = (struct part_record*)(sectdata + PTABLE_OFFS);
+
+               /* MBR has 4 records, EBRs have 2 */
+               num_rec = IS_MBR ? 4 : 2;
+
+               for(i=0; i<num_rec; i++) {
+                       /* ignore empty partitions in the MBR, stop on empty partitions in an EBR */
+                       if(prec[i].type == 0) {
+                               if(num_bootrec > 0) {
+                                       sidx = 0;
+                                       break;
+                               }
+                               continue;
+                       }
+
+                       /* ignore extended partitions and setup sector index to read the
+                        * next logical partition.
+                        */
+                       if(prec[i].type == PTYPE_EXT || prec[i].type == PTYPE_EXT_LBA) {
+                               /* all EBR start fields are relative to the first EBR offset */
+                               sidx = first_ebr_offs + prec[i].first_lba;
+                               continue;
+                       }
+
+                       /* found a proper partition */
+                       nparts++;
+
+                       if(ptab) {
+                               part->attr = prec[i].type;
+
+                               if(prec[i].stat & 0x80) {
+                                       part->attr |= PART_ACT_BIT;
+                               }
+                               if(IS_MBR) {
+                                       part->attr |= PART_PRIM_BIT;
+                               }
+                               part->start_sect = prec[i].first_lba + first_ebr_offs;
+                               part->size_sect = prec[i].nsect_lba;
+                               part++;
+                       }
+               }
+
+               num_bootrec++;
+       } while(sidx > 0 && (!ptab || nparts < ptabsz));
+
+       return nparts;
+}
+
+void print_partition_table(struct partition *ptab, int npart)
+{
+       int i;
+       struct partition *p = ptab;
+
+       printf("Found %d partitions\n", npart);
+       for(i=0; i<npart; i++) {
+               printf("%d%c ", i, PART_IS_ACT(p->attr) ? '*' : ' ');
+               printf("(%s) %-20s ", PART_IS_PRIM(p->attr) ? "pri" : "log", ptype_name(PART_TYPE(p->attr)));
+               printf("start: %-10llu ", (unsigned long long)p->start_sect);
+               printf("size: %-10llu\n", (unsigned long long)p->size_sect);
+               p++;
+       }
+}
+
+static int read_sector(int dev, uint64_t sidx)
+{
+       if(dev == -1 || dev == boot_drive_number) {
+               if(bdev_read_sect(sidx, sectdata) == -1) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       printf("BUG: reading partitions of drives other than the boot drive not implemented yet\n");
+       return -1;
+}
+
+static const char *ptype_name(int type)
+{
+       int i;
+
+       for(i=0; i<PTYPES_SIZE; i++) {
+               if(partypes[i].type == type) {
+                       return partypes[i].name;
+               }
+       }
+       return "unknown";
+}
diff --git a/src/part.h b/src/part.h
new file mode 100644 (file)
index 0000000..c897d5f
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef PART_H_
+#define PART_H_
+
+#include <inttypes.h>
+
+#define PART_ACT_BIT   (1 << 9)
+#define PART_PRIM_BIT  (1 << 10)
+
+#define PART_TYPE(attr)                ((attr) & 0xff)
+#define PART_IS_ACT(attr)      ((attr) & PART_ACT_BIT)
+#define PART_IS_PRIM(attr)     ((attr) & PART_PRIM_BIT)
+
+struct partition {
+       uint32_t start_sect;
+       uint32_t size_sect;
+
+       unsigned int attr;
+};
+
+int read_partitions(int dev, struct partition *ptab, int ptabsz);
+void print_partition_table(struct partition *ptab, int npart);
+
+#endif /* PART_H_ */
diff --git a/src/ptype.h b/src/ptype.h
new file mode 100644 (file)
index 0000000..4a40bfd
--- /dev/null
@@ -0,0 +1,83 @@
+#ifndef PTYPE_H_
+#define PTYPE_H_
+
+
+#define PTYPE_EXT              0x5
+#define PTYPE_EXT_LBA  0xf
+
+
+#define PTYPES_SIZE            (sizeof partypes / sizeof *partypes)
+
+struct {
+       int type;
+       const char *name;
+} partypes[] = {
+       {0, "empty"},
+       {0x01, "fat12"},
+       {0x02, "xenix root"},
+       {0x03, "xenix usr"},
+       {0x04, "fat16 (small)"},
+       {0x05, "extended"},
+       {0x06, "fat16"},
+       {0x07, "hpfs/ntfs"},
+       {0x08, "aix"},
+       {0x09, "aix bootable"},
+       {0x0a, "os/2 boot manager"},
+       {0x0b, "fat32 (chs)"},
+       {0x0c, "fat32 (lba)"},
+       {0x0e, "fat16 (lba)"},
+       {0x0f, "extended (lba)"},
+       {0x11, "hidden fat12"},
+       {0x12, "compaq diagnostics"},
+       {0x14, "hidden fat16 (small)"},
+       {0x16, "hidden fat16"},
+       {0x17, "hidden hpfs/ntfs"},
+       {0x1b, "hidden fat32"},
+       {0x1c, "hidden fat32 (lba)"},
+       {0x1d, "hidden fat16 (lba)"},
+       {0x24, "nec dos"},
+       {0x27, "windows recovery"},
+       {0x39, "plan 9"},
+       {0x3c, "partition magic"},
+       {0x4d, "qnx"},
+       {0x4e, "qnx 2nd"},
+       {0x4f, "qnx 3rd"},
+       {0x52, "cp/m"},
+       {0x63, "hurd/sysv"},
+       {0x64, "netware 286"},
+       {0x65, "netware 386"},
+       {0x80, "minix (old)"},
+       {0x81, "minix"},
+       {0x82, "linux swap/solaris"},
+       {0x83, "linux"},
+       {0x84, "windows suspend"},
+       {0x85, "linux extended"},
+       {0x86, "ntfs volume?"},
+       {0x87, "ntfs volume?"},
+       {0x88, "linux plaintext"},
+       {0x8e, "linux lvm"},
+       {0x9f, "bsd/os"},
+       {0xa0, "laptop diagnostic"},
+       {0xa5, "freebsd slice"},
+       {0xa6, "openbsd slice"},
+       {0xa7, "nextstep"},
+       {0xa8, "darwin ufs"},
+       {0xa9, "netbsd slice"},
+       {0xab, "darwin boot"},
+       {0xaf, "hfs/hfs+"},
+       {0xb7, "bsdi"},
+       {0xb8, "bsdi swap"},
+       {0xbe, "solaris boot"},
+       {0xbf, "solaris"},
+       {0xde, "dell diagnostic"},
+       {0xeb, "beos"},
+       {0xee, "gpt"},
+       {0xef, "efi (fat)"},
+       {0xf0, "linux/pa-risc boot"},
+       {0xf2, "dos secondary"},
+       {0xfb, "vmware vmfs"},
+       {0xfc, "vmware vmkcore"},
+       {0xfd, "linux raid auto"}
+};
+
+#endif /* PTYPE_H_ */
diff --git a/src/rtc.c b/src/rtc.c
new file mode 100644 (file)
index 0000000..40eca9a
--- /dev/null
+++ b/src/rtc.c
@@ -0,0 +1,123 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <time.h>
+#include <asmops.h>
+#include "rtc.h"
+
+/* CMOS I/O ports */
+#define PORT_CTL       0x70
+#define PORT_DATA      0x71
+
+/* CMOS RTC registers */
+#define REG_SEC                        0
+#define REG_ALARM_SEC  1
+#define REG_MIN                        2
+#define REG_ALARM_MIN  3
+#define REG_HOUR               4
+#define REG_ALARM_HOUR 5
+#define REG_WEEKDAY            6
+#define REG_DAY                        7
+#define REG_MONTH              8
+#define REG_YEAR               9
+#define REG_STATA              10
+#define REG_STATB              11
+#define REG_STATC              12
+#define REG_STATD              13
+
+#define STATA_BUSY     (1 << 7)
+#define STATB_24HR     (1 << 1)
+#define STATB_BIN      (1 << 2)
+
+#define HOUR_PM_BIT            (1 << 7)
+
+#define BCD_TO_BIN(x)  ((((x) >> 4) & 0xf) * 10 + ((x) & 0xf))
+
+static void read_rtc(struct tm *tm);
+static int read_reg(int reg);
+
+
+void init_rtc(void)
+{
+       struct tm tm;
+
+       read_rtc(&tm);
+       start_time = mktime(&tm);
+
+       printf("System real-time clock: %s", asctime(&tm));
+}
+
+
+static void read_rtc(struct tm *tm)
+{
+       int statb, pm;
+
+       /* wait for any clock updates to finish */
+       while(read_reg(REG_STATA) & STATA_BUSY);
+
+       tm->tm_sec = read_reg(REG_SEC);
+       tm->tm_min = read_reg(REG_MIN);
+       tm->tm_hour = read_reg(REG_HOUR);
+       tm->tm_mday = read_reg(REG_DAY);
+       tm->tm_mon = read_reg(REG_MONTH);
+       tm->tm_year = read_reg(REG_YEAR);
+
+       /* in 12hour mode, bit 7 means post-meridiem */
+       pm = tm->tm_hour & HOUR_PM_BIT;
+       tm->tm_hour &= ~HOUR_PM_BIT;
+
+       /* convert to binary if needed */
+       statb = read_reg(REG_STATB);
+       if(!(statb & STATB_BIN)) {
+               tm->tm_sec = BCD_TO_BIN(tm->tm_sec);
+               tm->tm_min = BCD_TO_BIN(tm->tm_min);
+               tm->tm_hour = BCD_TO_BIN(tm->tm_hour);
+               tm->tm_mday = BCD_TO_BIN(tm->tm_mday);
+               tm->tm_mon = BCD_TO_BIN(tm->tm_mon);
+               tm->tm_year = BCD_TO_BIN(tm->tm_year);
+       }
+
+       /* make the year an offset from 1900 */
+       if(tm->tm_year < 100) {
+               tm->tm_year += 100;
+       } else {
+               tm->tm_year -= 1900;
+       }
+
+       /* if tm_hour is in 12h mode, convert to 24h */
+       if(!(statb & STATB_24HR)) {
+               if(tm->tm_hour == 12) {
+                       tm->tm_hour = 0;
+               }
+               if(pm) {
+                       tm->tm_hour += 12;
+               }
+       }
+
+       tm->tm_mon -= 1;        /* we want months to start from 0 */
+}
+
+static int read_reg(int reg)
+{
+       unsigned char val;
+       outb(reg, PORT_CTL);
+       iodelay();
+       val = inb(PORT_DATA);
+       iodelay();
+       return val;
+}
diff --git a/src/rtc.h b/src/rtc.h
new file mode 100644 (file)
index 0000000..0d267dd
--- /dev/null
+++ b/src/rtc.h
@@ -0,0 +1,28 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef _RTC_H_
+#define _RTC_H_
+
+#include <time.h>
+
+/* the time read from rtc during init */
+time_t start_time;
+
+void init_rtc(void);
+
+#endif /* _RTC_H_ */