From 50dca8b954c623b100e62f62cad0a2ab9b70e608 Mon Sep 17 00:00:00 2001
From: David Given <dg@cowlark.com>
Date: Sun, 16 Jun 2019 19:04:17 +0200
Subject: [PATCH] First at-least-slightly working version of the CP/M
 read/write stuff. Not as bad as I expected, but far too big.

---
 build.lua                                 |  32 +++---
 lang/cem/libcc.ansi/headers/ack/emufile.h |   2 +
 plat/cpm/descr                            |   1 +
 plat/cpm/include/ack/plat.h               |  14 ++-
 plat/cpm/include/cpm.h                    |  18 ++--
 plat/cpm/libsys/build.lua                 |  38 +++----
 plat/cpm/libsys/close.c                   |  31 ++++--
 plat/cpm/libsys/cpm_read_random_safe.c    |  10 ++
 plat/cpm/libsys/cpmsys.h                  |  22 ++++
 plat/cpm/libsys/fcb.c                     |  70 +++++++++++++
 plat/cpm/libsys/fclose.c                  |  20 ----
 plat/cpm/libsys/fd.c                      |  19 ++++
 plat/cpm/libsys/feof.c                    |   9 --
 plat/cpm/libsys/ferror.c                  |   8 --
 plat/cpm/libsys/fflush.c                  |  12 ---
 plat/cpm/libsys/getc.c                    |  36 -------
 plat/cpm/libsys/lseek.c                   |  28 ++++--
 plat/cpm/libsys/open.c                    |  59 +++++++++--
 plat/cpm/libsys/putc.c                    |  17 ----
 plat/cpm/libsys/read.c                    |  94 +++++++++++++++--
 plat/cpm/libsys/write.c                   | 117 ++++++++++++++++++----
 21 files changed, 464 insertions(+), 193 deletions(-)
 create mode 100644 plat/cpm/libsys/cpm_read_random_safe.c
 create mode 100644 plat/cpm/libsys/cpmsys.h
 delete mode 100644 plat/cpm/libsys/fclose.c
 create mode 100644 plat/cpm/libsys/fd.c
 delete mode 100644 plat/cpm/libsys/feof.c
 delete mode 100644 plat/cpm/libsys/ferror.c
 delete mode 100644 plat/cpm/libsys/fflush.c
 delete mode 100644 plat/cpm/libsys/getc.c
 delete mode 100644 plat/cpm/libsys/putc.c

diff --git a/build.lua b/build.lua
index ccd113f1a..14110e4f5 100644
--- a/build.lua
+++ b/build.lua
@@ -7,24 +7,24 @@ vars.ackcflags = {
 vars.ackldflags = {}
 vars.plats = {
 	"cpm",
-	"linux386",
-	"linux68k",
-	"linuxppc",
-	"linuxmips",
-	"osx386",
-	"osxppc",
-	"pc86",
-	"rpi",
-	"pdpv7",
-	"em22",
+--	"linux386",
+--	"linux68k",
+--	"linuxppc",
+--	"linuxmips",
+--	"osx386",
+--	"osxppc",
+--	"pc86",
+--	"rpi",
+--	"pdpv7",
+--	"em22",
 }
 vars.plats_with_tests = {
-	"cpm",
-	"linux68k",
-	"linux386",
-	"linuxppc",
-	"linuxmips",
-	"pc86",
+--	"cpm",
+--	"linux68k",
+--	"linux386",
+--	"linuxppc",
+--	"linuxmips",
+--	"pc86",
 }
 
 installable {
diff --git a/lang/cem/libcc.ansi/headers/ack/emufile.h b/lang/cem/libcc.ansi/headers/ack/emufile.h
index 186630e56..06a1ffcec 100644
--- a/lang/cem/libcc.ansi/headers/ack/emufile.h
+++ b/lang/cem/libcc.ansi/headers/ack/emufile.h
@@ -25,7 +25,9 @@ struct FILE {
 #define _IOWRITING	0x100
 #define	_IOAPPEND	0x200
 
+#if !defined BUFSIZ
 #define	BUFSIZ      1024
+#endif
 
 #define	FOPEN_MAX	20
 extern FILE	*__iotab[FOPEN_MAX];
diff --git a/plat/cpm/descr b/plat/cpm/descr
index e01f2c389..c023fc45c 100644
--- a/plat/cpm/descr
+++ b/plat/cpm/descr
@@ -77,6 +77,7 @@ name led
 		(.e:{TAIL}={PLATFORMDIR}/libem.a \
 		           {PLATFORMDIR}/libsys.a \
 				   {PLATFORMDIR}/libc.a \
+		           {PLATFORMDIR}/libsys.a \
 				   {PLATFORMDIR}/libem.a \
 		           {PLATFORMDIR}/libend.a)
 	linker
diff --git a/plat/cpm/include/ack/plat.h b/plat/cpm/include/ack/plat.h
index 6e1bac6ae..d6270fe5c 100644
--- a/plat/cpm/include/ack/plat.h
+++ b/plat/cpm/include/ack/plat.h
@@ -6,17 +6,21 @@
 #ifndef _ACK_PLAT_H
 #define _ACK_PLAT_H
 
+/* The 8080 code generator doesn't do floating point. */
+
+#define ACKCONF_WANT_STDIO_FLOAT 0
+
 /* We're providing a time() system call rather than wanting a wrapper around
  * gettimeofday() in the libc. */
  
 #define ACKCONF_WANT_EMULATED_TIME 0
 
-/* CP/M's underlying file abstraction is weird and doesn't map well onto
- * file descriptors. Disable the standard FILE* mechanism in favour of our
- * own.
- */
+/* Processes? CP/M? Hahahaha... */
 
-#define ACKCONF_WANT_EMULATED_FILE 0
 #define ACKCONF_WANT_EMULATED_POPEN 0
 
+/* We have a very small address space, so override the default buffer size. */
+
+#define BUFSIZ 256
+
 #endif
diff --git a/plat/cpm/include/cpm.h b/plat/cpm/include/cpm.h
index c7288856b..3ad97b183 100644
--- a/plat/cpm/include/cpm.h
+++ b/plat/cpm/include/cpm.h
@@ -67,10 +67,19 @@ extern uint8_t* cpm_ramtop;
 extern uint8_t cpm_cmdlinelen;
 extern char cpm_cmdline[0x7f];
 
+/* Special: parses a filename into an FCB. Returns the user code (if any).
+ * Warning: cannot fail (because CP/M filespecs are incredibly lax). */
+
+extern uint8_t cpm_parse_filename(FCB* fcb, const char* filename);
+
 /* Special: if the CCP hasn't been overwritten, returns to it; otherwise does
  * a warmboot. */
 extern void cpm_exit(void);
 
+/* Special: equivalent to cpm_read_random() except if you read unwritten data
+ * 0 is returned (and the buffer contains garbage). */
+extern uint8_t cpm_read_random_safe(FCB* fcb);
+
 /* Extends cpm_ramtop over the CCP, for a little extra space. */
 extern void cpm_overwrite_ccp(void);
 
@@ -114,12 +123,7 @@ extern void cpm_overwrite_ccp(void);
 /* 37 */ extern uint8_t cpm_reset_drives(uint16_t drive_bitmap);
 /* 40 */ extern uint8_t cpm_write_random_filled(FCB* fcb);
 
-/* File descriptor emulation */
-
-struct FCBE
-{
-    FCB fcb; /* drive 0 means the console */
-    uint8_t user;
-};
+#define cpm_get_user() cpm_get_set_user(0xff)
+#define cpm_set_user(u) cpm_get_set_user(u)
 
 #endif
diff --git a/plat/cpm/libsys/build.lua b/plat/cpm/libsys/build.lua
index 9091a6917..070290326 100644
--- a/plat/cpm/libsys/build.lua
+++ b/plat/cpm/libsys/build.lua
@@ -47,28 +47,28 @@ local bdos_calls = {
 
 local trap_calls = {
     "EARRAY",
+    "EBADGTO",
+    "EBADLAE",
+    "EBADLIN",
+    "EBADMON",
+    "EBADPC",
+    "EBADPTR",
+    "ECASE",
+    "ECONV",
+    "EFDIVZ",
+    "EFOVFL",
+    "EFUND",
+    "EFUNFL",
+    "EHEAP",
+    "EIDIVZ",
+    "EILLINS",
+    "EIOVFL",
+    "EIUND",
+    "EMEMFLT",
+    "EODDZ",
     "ERANGE",
     "ESET",
-    "EIOVFL",
-    "EFOVFL",
-    "EFUNFL",
-    "EIDIVZ",
-    "EFDIVZ",
-    "EIUND",
-    "EFUND",
-    "ECONV",
     "ESTACK",
-    "EHEAP",
-    "EILLINS",
-    "EODDZ",
-    "ECASE",
-    "EMEMFLT",
-    "EBADPTR",
-    "EBADPC",
-    "EBADLAE",
-    "EBADMON",
-    "EBADLIN",
-    "EBADGTO",
     "EUNIMPL",
 }
 
diff --git a/plat/cpm/libsys/close.c b/plat/cpm/libsys/close.c
index 1c570029b..be5ae0ec4 100644
--- a/plat/cpm/libsys/close.c
+++ b/plat/cpm/libsys/close.c
@@ -1,14 +1,31 @@
-/* $Source$
- * $State$
- * $Revision$
- */
-
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <cpm.h>
+#include "cpmsys.h"
 
 int close(int fd)
 {
-	errno = EBADF;
-	return -1;
+    struct FCBE* fcbe = &__fd[fd];
+    int result = 0;
+    uint8_t olduser;
+
+    __init_file_descriptors();
+    if (fcbe->fcb.f[0])
+    {
+        /* There's an actual filename here, so assume it's an open file. */
+
+        olduser = cpm_get_user();
+        cpm_set_user(fcbe->user);
+        if (cpm_close_file(&fcbe->fcb) == 0xff)
+        {
+            errno = EIO;
+            result = -1;
+        }
+        cpm_set_user(olduser);
+    }
+
+    memset(fcbe, 0, sizeof(struct FCBE));
+    return result;
 }
+
diff --git a/plat/cpm/libsys/cpm_read_random_safe.c b/plat/cpm/libsys/cpm_read_random_safe.c
new file mode 100644
index 000000000..1c5838bd6
--- /dev/null
+++ b/plat/cpm/libsys/cpm_read_random_safe.c
@@ -0,0 +1,10 @@
+#include <string.h>
+#include <cpm.h>
+
+uint8_t cpm_read_random_safe(FCB* fcb)
+{
+    uint8_t r = cpm_read_random(fcb);
+    if ((r == 1) || (r == 4))
+        r = 0;
+    return r;
+}
diff --git a/plat/cpm/libsys/cpmsys.h b/plat/cpm/libsys/cpmsys.h
new file mode 100644
index 000000000..d659f9e9f
--- /dev/null
+++ b/plat/cpm/libsys/cpmsys.h
@@ -0,0 +1,22 @@
+#ifndef CPMSYS_H
+#define CPMSYS_H
+
+/* File descriptor emulation */
+
+struct FCBE
+{
+    uint16_t length; /* number of records */
+    FCB fcb; /* drive 0 means the console, f[0] == 0 means unallocated */
+    uint8_t user;
+    uint8_t offset; /* into current sector */
+};
+
+#define NUM_FILE_DESCRIPTORS 8
+extern struct FCBE __fd[NUM_FILE_DESCRIPTORS];
+extern uint8_t __transfer_buffer[128];
+
+extern void __init_file_descriptors(void);
+
+#define SECTOR_ALIGNED(s) (((s) & 0x7f) == 0)
+
+#endif
diff --git a/plat/cpm/libsys/fcb.c b/plat/cpm/libsys/fcb.c
index e8059de3e..0f98b5a4f 100644
--- a/plat/cpm/libsys/fcb.c
+++ b/plat/cpm/libsys/fcb.c
@@ -1,3 +1,73 @@
 #include <stdio.h>
 #include <cpm.h>
+#include <string.h>
+#include <ctype.h>
+#include "cpmsys.h"
+
+uint8_t cpm_parse_filename(FCB* fcb, const char* filename)
+{
+    uint8_t user = cpm_get_user();
+
+    memset(fcb, 0, sizeof(FCB));
+    memset(fcb->f, ' ', sizeof(fcb->f));
+
+    if (strchr(filename, ':'))
+    {
+        char c = *filename++;
+        if (isdigit(c))
+        {
+            user = c - '0';
+            c = *filename++;
+            if (isdigit(c))
+            {
+                user = (user*10) + (c - '0');
+                c = *filename++;
+            }
+        }
+        c = toupper(c);
+        if (isalpha(c))
+        {
+            fcb->dr = c - '@';
+            c = *filename++;
+        }
+
+        while (c != ':')
+            c = *filename++;
+    }
+
+    /* Read filename part. */
+
+    {
+        uint8_t i = 8;
+        uint8_t* p = &fcb->f[0];
+        while (i--)
+        {
+            char c = toupper(*filename++);
+            if (c == '.')
+                break;
+            if (!c)
+                goto exit;
+            *p++ = c;
+        }
+    }
+
+    /* Read extension part. */
+
+    {
+        uint8_t i = 3;
+        uint8_t* p = &fcb->f[8];
+        while (i--)
+        {
+            char c = toupper(*filename++);
+            if (!c)
+                goto exit;
+            *p++ = c;
+        }
+    }
+
+exit:
+    if (fcb->dr == 0)
+        fcb->dr = cpm_get_current_drive() + 1;
+    return user;
+}
 
diff --git a/plat/cpm/libsys/fclose.c b/plat/cpm/libsys/fclose.c
deleted file mode 100644
index abae8a6c0..000000000
--- a/plat/cpm/libsys/fclose.c
+++ /dev/null
@@ -1,20 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-int fclose(FILE* stream)
-{
-    if (stream == CPM_FAKE_FILE)
-        return 0;
-    
-    if (fflush(stream))
-        return;
-
-    cpm_get_set_user(stream->user);
-    if (cpm_close_file(&stream->fcb) == 0xff)
-    {
-        errno = EIO;
-        return -1;
-    }
-    return 0;
-}
diff --git a/plat/cpm/libsys/fd.c b/plat/cpm/libsys/fd.c
new file mode 100644
index 000000000..e98c9d7b2
--- /dev/null
+++ b/plat/cpm/libsys/fd.c
@@ -0,0 +1,19 @@
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <cpm.h>
+#include "cpmsys.h"
+
+struct FCBE __fd[NUM_FILE_DESCRIPTORS];
+uint8_t __transfer_buffer[128];
+
+void __init_file_descriptors(void)
+{
+    static uint8_t initialised = 0;
+    if (!initialised)
+    {
+        /* Mark stdin, stdout, stderr as being open files. */
+        __fd[0].fcb.f[0] = __fd[1].fcb.f[0] = __fd[2].fcb.f[0] = ' ';
+        initialised = 1;
+    }
+}
diff --git a/plat/cpm/libsys/feof.c b/plat/cpm/libsys/feof.c
deleted file mode 100644
index a04a76a78..000000000
--- a/plat/cpm/libsys/feof.c
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-int cpm_feof(FILE* stream)
-{
-    return 0;
-}
-
diff --git a/plat/cpm/libsys/ferror.c b/plat/cpm/libsys/ferror.c
deleted file mode 100644
index b279c8cec..000000000
--- a/plat/cpm/libsys/ferror.c
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-int cpm_ferror(FILE* stream)
-{
-    return 0;
-}
diff --git a/plat/cpm/libsys/fflush.c b/plat/cpm/libsys/fflush.c
deleted file mode 100644
index 664488467..000000000
--- a/plat/cpm/libsys/fflush.c
+++ /dev/null
@@ -1,12 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-int fflush(FILE* stream)
-{
-    if (stream == CPM_FAKE_FILE)
-        return 0;
-    
-    errno = EBADF;
-    return -1;
-}
\ No newline at end of file
diff --git a/plat/cpm/libsys/getc.c b/plat/cpm/libsys/getc.c
deleted file mode 100644
index bb322fc04..000000000
--- a/plat/cpm/libsys/getc.c
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-struct buffer
-{
-    uint8_t mx;
-    uint8_t nc;
-    uint8_t c[128];
-};
-
-static struct buffer buffer;
-static uint8_t pos;
-
-int read_from_console(void)
-{
-    while (pos == buffer.nc)
-    {
-        /* Refill buffer. */
-        buffer.mx = sizeof(buffer.c);
-        buffer.nc = 0;
-        cpm_readline((uint8_t*) &buffer);
-        pos = 0;
-    }
-
-    return buffer.c[pos++];
-}
-
-int cpm_getc(FILE* stream)
-{
-    if (stream == CPM_FAKE_FILE)
-        return read_from_console();
-
-    errno = EBADF;
-    return -1;
-}
diff --git a/plat/cpm/libsys/lseek.c b/plat/cpm/libsys/lseek.c
index ecbc4b520..ddc38df03 100644
--- a/plat/cpm/libsys/lseek.c
+++ b/plat/cpm/libsys/lseek.c
@@ -1,14 +1,28 @@
-/* $Source$
- * $State$
- * $Revision$
- */
-
+#include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <sys/types.h>
 #include <unistd.h>
+#include <cpm.h>
+#include "cpmsys.h"
 
 off_t lseek(int fd, off_t offset, int whence)
 {
-	errno = EINVAL;
-	return -1;
+    struct FCBE* fcbe = &__fd[fd];
+
+    __init_file_descriptors();
+    if (fcbe->fcb.dr == 0)
+    {
+        /* Can't seek the console. */
+        return 0;
+    }
+
+    if (whence == SEEK_END)
+        offset += fcbe->length<<7;
+    if (whence == SEEK_CUR)
+        offset += (U16(fcbe->fcb.r)<<7) | fcbe->offset;
+    
+    U16(fcbe->fcb.r) = offset>>7;
+    fcbe->offset = offset & 0x7f;
+    return 0;
 }
diff --git a/plat/cpm/libsys/open.c b/plat/cpm/libsys/open.c
index f3522eae5..82345fb55 100644
--- a/plat/cpm/libsys/open.c
+++ b/plat/cpm/libsys/open.c
@@ -1,14 +1,61 @@
-/* $Source$
- * $State$
- * $Revision$
- */
-
+#include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <cpm.h>
+#include "cpmsys.h"
 
 int open(const char* path, int access, ...)
 {
-	errno = EACCES;
+	uint8_t fd = 0;
+    struct FCBE* fcbe = &__fd[0];
+	uint8_t olduser;
+	
+    __init_file_descriptors();
+	while (fd != NUM_FILE_DESCRIPTORS)
+	{
+		if (fcbe->fcb.f[0] == 0)
+			break;
+		fd++;
+		fcbe++;
+	}
+	if (fd == NUM_FILE_DESCRIPTORS)
+	{
+		errno = EMFILE;
+		return -1;
+	}
+
+	fcbe->user = cpm_parse_filename(&fcbe->fcb, path);
+
+	olduser = cpm_get_user();
+	cpm_set_user(fcbe->user);
+
+	if (access & O_TRUNC)
+	{
+		cpm_delete_file(&fcbe->fcb);
+		access |= O_CREAT;
+	}
+	if (access & O_CREAT)
+	{
+		if (cpm_make_file(&fcbe->fcb) == 0xff)
+			goto eio;
+	}
+	else
+	{
+		if (cpm_open_file(&fcbe->fcb) == 0xff)
+			goto eio;
+	}
+
+	cpm_seek_to_end(&fcbe->fcb);
+	fcbe->length = U16(fcbe->fcb.r);
+	if (!(access & O_APPEND))
+		U16(fcbe->fcb.r) = 0;
+	return fd;
+
+eio:
+	fcbe->fcb.f[0] = 0;
+	errno = EIO;
 	return -1;
 }
+
+
diff --git a/plat/cpm/libsys/putc.c b/plat/cpm/libsys/putc.c
deleted file mode 100644
index f78027d97..000000000
--- a/plat/cpm/libsys/putc.c
+++ /dev/null
@@ -1,17 +0,0 @@
-#include <stdio.h>
-#include <errno.h>
-#include <cpm.h>
-
-int cpm_putc(int c, FILE* stream)
-{
-    if (stream == CPM_FAKE_FILE)
-    {
-        cpm_conout(c);
-        if (c == '\n')
-            cpm_conout(c);
-        return 0;
-    }
-
-    errno = EBADF;
-    return -1;
-}
diff --git a/plat/cpm/libsys/read.c b/plat/cpm/libsys/read.c
index 952995af8..1d64faaf6 100644
--- a/plat/cpm/libsys/read.c
+++ b/plat/cpm/libsys/read.c
@@ -7,18 +7,100 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <string.h>
 #include <cpm.h>
+#include "cpmsys.h"
 
 ssize_t read(int fd, void* buffer, size_t count)
 {
-	short save;
-	unsigned char before_n;
+	uint8_t* bbuffer = buffer;
+	struct FCBE* fcbe = &__fd[fd];
+	uint8_t olduser;
+	ssize_t result;
 
-	if (fd != 0)
+    __init_file_descriptors();
+	if (fcbe->fcb.dr == 0)
 	{
-		errno = EBADF;
-		return -1;
+		/* Read from the console. */
+
+		if (count == 0)
+			return 0;
+		*(uint8_t*)buffer = cpm_conin();
+		return 1;
 	}
 
-	return fread(buffer, 1, count, stdin);
+	olduser = cpm_get_user();
+	cpm_set_user(fcbe->user);
+
+	if (U16(fcbe->fcb.r) >= fcbe->length)
+		goto done;
+	if (fcbe->offset || !SECTOR_ALIGNED(count))
+	{
+		uint8_t delta;
+
+		/* We need to read bytes until we're at a sector boundary. */
+
+		cpm_set_dma(__transfer_buffer);
+		if (cpm_read_random_safe(&fcbe->fcb) != 0)
+			goto eio;
+
+		/* Copy enough bytes to reach the end of the sector. */
+
+		delta = 128 - fcbe->offset;
+		if (delta > count)
+			delta = count;
+		memcpy(bbuffer, __transfer_buffer+fcbe->offset, delta);
+		fcbe->offset += delta;
+		count -= delta;
+		bbuffer += delta;
+
+		/* If we've read enough bytes, advance to the next sector. */
+
+		if (fcbe->offset == 128)
+		{
+			U16(fcbe->fcb.r)++;
+			fcbe->offset = 0;
+		}
+	}
+
+	while (count >= 128)
+	{
+		if (U16(fcbe->fcb.r) >= fcbe->length)
+			goto done;
+
+		/* Read entire sectors directly into the destination buffer. */
+
+		cpm_set_dma(bbuffer);
+		if (cpm_read_random_safe(&fcbe->fcb) != 0)
+			goto eio;
+		count -= 128;
+		bbuffer += 128;
+		U16(fcbe->fcb.r)++;
+	}
+
+	if (count != 0)
+	{
+		if (U16(fcbe->fcb.r) >= fcbe->length)
+			goto done;
+
+		/* There's some trailing data to read. */
+
+		cpm_set_dma(__transfer_buffer);
+		if (cpm_read_random_safe(&fcbe->fcb) != 0)
+			goto eio;
+
+		memcpy(bbuffer, __transfer_buffer, count);
+		fcbe->offset = count;
+	}
+
+done:
+	result = bbuffer - (uint8_t*)buffer;
+exit:
+	cpm_set_user(olduser);
+	return result;
+
+eio:
+	errno = EIO;
+	result = -1;
+	goto exit;
 }
diff --git a/plat/cpm/libsys/write.c b/plat/cpm/libsys/write.c
index 768e9b2eb..384425c08 100644
--- a/plat/cpm/libsys/write.c
+++ b/plat/cpm/libsys/write.c
@@ -6,7 +6,9 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <string.h>
 #include <cpm.h>
+#include "cpmsys.h"
 
 void _sys_write_tty(char c)
 {
@@ -17,24 +19,103 @@ void _sys_write_tty(char c)
 
 ssize_t write(int fd, void* buffer, size_t count)
 {
-	int i;
-	char* p = buffer;
-	
-	/* We're only allowed to write to fd 0, 1 or 2. */
-	
-	if ((fd < 0) || (fd > 2))
+	uint8_t* bbuffer = buffer;
+	struct FCBE* fcbe = &__fd[fd];
+	uint8_t olduser;
+	uint16_t result;
+
+    __init_file_descriptors();
+	if (fcbe->fcb.dr == 0)
 	{
-		errno = EBADF;
-		return -1;
+		/* Write to the console. */
+
+		size_t i = count;
+		while (i--)
+			_sys_write_tty(*bbuffer++);
+		return count;
 	}
-	
-	/* Write all data. */
-	
-	i = count;
-	while (i--)
-		_sys_write_tty(*p++);
-	
-	/* No failures. */
-	
-	return count;
+
+	olduser = cpm_get_user();
+	cpm_set_user(fcbe->user);
+
+	if (fcbe->offset || !SECTOR_ALIGNED(count))
+	{
+		uint8_t delta;
+
+		/* We're not at a sector boundary, so we need to do a
+		 * read/modify/write of the initial fragment. */
+
+		cpm_set_dma(__transfer_buffer);
+		if (cpm_read_random_safe(&fcbe->fcb) != 0)
+			goto eio;
+
+		/* Copy enough bytes to reach the end of the sector. */
+
+		delta = 128 - fcbe->offset;
+		if (delta > count)
+			delta = count;
+		memcpy(__transfer_buffer+fcbe->offset, bbuffer, delta);
+		fcbe->offset += delta;
+		count -= delta;
+		bbuffer += delta;
+
+		/* Write back. */
+
+		if (cpm_write_random(&fcbe->fcb) != 0)
+			goto eio;
+
+		/* If we've written enough bytes, advance to the next sector. */
+
+		if (fcbe->offset == 128)
+		{
+			U16(fcbe->fcb.r)++;
+			fcbe->offset = 0;
+		}
+	}
+
+	while (count >= 128)
+	{
+		/* Write entire sectors directly from the source buffer. */
+
+		cpm_set_dma(bbuffer);
+		if (cpm_write_random(&fcbe->fcb) != 0)
+			goto eio;
+		count -= 128;
+		bbuffer += 128;
+		U16(fcbe->fcb.r)++;
+	}
+
+	if (count != 0)
+	{
+		/* There's some trailing data to write. We need another
+		 * read/modify/write cycle. */
+
+		cpm_set_dma(__transfer_buffer);
+		if (cpm_read_random_safe(&fcbe->fcb) != 0)
+			goto eio;
+
+		memcpy(__transfer_buffer, bbuffer, count);
+
+		if (cpm_write_random(&fcbe->fcb) != 0)
+			goto eio;
+
+		fcbe->offset = count;
+	}
+
+	if (U16(fcbe->fcb.r) >= fcbe->length)
+	{
+		fcbe->length = U16(fcbe->fcb.r);
+		if (fcbe->offset != 0)
+			fcbe->length++;
+	}
+
+	result = bbuffer - (uint8_t*)buffer;
+exit:
+	cpm_set_user(olduser);
+	return result;
+
+eio:
+	errno = EIO;
+	result = -1;
+	goto exit;
 }