Merge pull request #229 from tkchia/tkchia/msdos86

Add support for compiling MS-DOS .com programs (msdos86).
This commit is contained in:
David Given 2021-04-13 12:51:43 +02:00 committed by GitHub
commit a740f476c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1672 additions and 3 deletions

1
README
View file

@ -37,6 +37,7 @@ linuxmips produces ELF executables for little-endian MIPS32r2 Linux systems
cpm produces i80 CP/M .COM files
rpi produces Raspberry Pi GPU binaries
pdpv7 produces PDP/11 V7 Unix binaries
msdos86 produces i86 MS-DOS .COM files

View file

@ -11,6 +11,7 @@ vars.plats = {
"linux68k",
"linuxppc",
"linuxmips",
"msdos86",
"osx386",
"osxppc",
"pc86",

View file

@ -15,6 +15,10 @@
#define ACKCONF_WANT_STANDARD_O 1
#endif
#ifndef ACKCONF_WANT_O_TEXT_O_BINARY
#define ACKCONF_WANT_O_TEXT_O_BINARY 0
#endif
#ifndef ACKCONF_WANT_STANDARD_SIGNALS
#define ACKCONF_WANT_STANDARD_SIGNALS 1
#endif

View file

@ -24,6 +24,7 @@ struct FILE {
#define _IOREADING 0x080
#define _IOWRITING 0x100
#define _IOAPPEND 0x200
#define _IOBINARY 0x400
#if !defined BUFSIZ
#define BUFSIZ 1024

View file

@ -24,6 +24,10 @@
O_WRONLY = 1,
O_RDWR = 2,
#if ACKCONF_WANT_O_TEXT_O_BINARY
O_TEXT = 010000,
O_BINARY = 020000,
#endif
O_CREAT = 0100,
O_TRUNC = 01000,
O_APPEND = 02000,

View file

@ -5,6 +5,10 @@
#include <stdio.h>
#include <stdlib.h>
#if ACKCONF_WANT_O_TEXT_O_BINARY
#include <unistd.h>
#include <fcntl.h>
#endif
#if ACKCONF_WANT_STDIO && ACKCONF_WANT_EMULATED_FILE
@ -38,6 +42,9 @@ FILE* fdopen(int fd, const char* mode)
switch (*mode++)
{
case 'b':
#if ACKCONF_WANT_O_TEXT_O_BINARY
flags |= _IOBINARY;
#endif
continue;
case '+':
flags |= _IOREAD | _IOWRITE;
@ -57,6 +64,17 @@ FILE* fdopen(int fd, const char* mode)
if ((flags & _IOREAD) && (flags & _IOWRITE))
flags &= ~(_IOREADING | _IOWRITING);
#if ACKCONF_WANT_O_TEXT_O_BINARY
{
/*
* FIXME: this assumes that any platform that has O_TEXT and
* O_BINARY will also have a _setmode() function.
*/
extern int _setmode(int, int);
_setmode(fd, (flags & _IOBINARY) ? O_BINARY : O_TEXT);
}
#endif
stream->_count = 0;
stream->_fd = fd;
stream->_flags = flags;

View file

@ -7,7 +7,6 @@
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#if ACKCONF_WANT_STDIO && ACKCONF_WANT_EMULATED_FILE
@ -68,6 +67,9 @@ FILE* fopen(const char* name, const char* mode)
switch (*mode++)
{
case 'b':
#if ACKCONF_WANT_O_TEXT_O_BINARY
flags |= _IOBINARY;
#endif
continue;
case '+':
rwmode = O_RDWR;
@ -80,6 +82,7 @@ FILE* fopen(const char* name, const char* mode)
break;
}
#if !ACKCONF_WANT_O_TEXT_O_BINARY
/* Perform a creat() when the file should be truncated or when
* the file is opened for writing and the open() failed.
*/
@ -87,12 +90,16 @@ FILE* fopen(const char* name, const char* mode)
|| (((fd = open(name, rwmode)) < 0)
&& (rwflags & O_CREAT)))
{
if (((fd = creat(name, PMODE)) > 0) && flags | _IOREAD)
if (((fd = creat(name, PMODE)) >= 0) && (flags & _IOREAD))
{
(void)close(fd);
fd = open(name, rwmode);
}
}
#else
rwflags |= (flags & _IOBINARY) ? O_BINARY : O_TEXT;
fd = open(name, rwmode | rwflags, PMODE);
#endif
if (fd < 0)
return (FILE*)NULL;

View file

@ -50,6 +50,9 @@ FILE* freopen(const char* name, const char* mode, FILE* stream)
switch (*mode++)
{
case 'b':
#if ACKCONF_WANT_O_TEXT_O_BINARY
flags |= _IOBINARY;
#endif
continue;
case '+':
rwmode = O_RDWR;
@ -62,16 +65,21 @@ FILE* freopen(const char* name, const char* mode, FILE* stream)
break;
}
#if !ACKCONF_WANT_O_TEXT_O_BINARY
if ((rwflags & O_TRUNC)
|| (((fd = open(name, rwmode)) < 0)
&& (rwflags & O_CREAT)))
{
if (((fd = creat(name, PMODE)) < 0) && flags | _IOREAD)
if (((fd = creat(name, PMODE)) >= 0) && (flags & _IOREAD))
{
(void)close(fd);
fd = open(name, rwmode);
}
}
#else
rwflags |= (flags & _IOBINARY) ? O_BINARY : O_TEXT;
fd = open(name, rwmode | rwflags, PMODE);
#endif
if (fd < 0)
{

145
plat/msdos86/boot.s Normal file
View file

@ -0,0 +1,145 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
begtext:
! Make sure we are running under MS-DOS 2 or above.
!
! While at it, also remember the actual DOS version, so that we know
! whether DOS gives us the program's name in the environment
! segment. (DOS 3+ does; DOS 2.x does not.)
movb ah, 0x30
int 0x21
cmpb al, 2
xchg bx, ax
jc bad_sys
! Clear BSS.
mov di, begbss
mov cx, endbss+1
sub cx, di
shr cx, 1
xor ax, ax
cld
rep stosw
! Get the size of the environment variables plus (if present) the
! program name. Also count the number of environment variables.
mov es, (0x002C)
xor di, di
! ax = 0 from above
cwd ! dx = count of env. vars.
mov cx, -1
scasb ! handle special case of empty env.
jz is_empty_env
size_env:
inc dx
repnz scasb
scasb
jnz size_env
is_empty_env:
cmpb bl, 2
jz no_argv0
scasw
repnz scasb
no_argv0:
! Copy out the environment variables and (possibly) program name
! onto the stack.
mov si, di
dec si
and si, -2
std
copy_env:
test si, si
eseg lodsw
push ax
jnz copy_env
mov cx, sp
! Reset DF and es properly.
cld
push ss
pop es
! Reserve space for argc and the argv and envp pointers on the
! stack. These will be passed to __m_a_i_n later.
sub sp, 6
mov ax, sp
! Build up argc, argv[], and envp[].
push ax ! output buffer for argc, argv, envp
push bx ! MS-DOS version
push cx ! env. string data
push dx ! count of env. vars.
mov ax, 0x0080
push ax ! raw command line
call __sys_initmain
add sp, 10
! Bail out if something went wrong.
test ax, ax
jnz no_room
! argc, argv, and envp are now at the stack top. Now go.
call __m_a_i_n
add sp, 6
push ax
call _exit
bad_sys:
mov dx, bad_sys_msg
dos_msg:
movb ah, 9
int 0x21
ret
no_room:
mov dx, no_room_msg
call dos_msg
movb al, -1
jmp al_exit
! Exit.
.define __exit
.extern __exit
.define EXIT
.extern EXIT
__exit:
EXIT:
pop bx
pop ax
al_exit:
movb ah, 0x4c
int 0x21
! Define symbols at the beginning of our various segments, so that we can find
! them. (Except .text, which has already been done.)
.define begtext, begdata, begbss
.sect .data; begdata:
.sect .rom; begrom:
.sect .bss; begbss:
.sect .rom
! Some text messages.
bad_sys_msg: .ascii 'Bad DOS$'
no_room_msg: .ascii 'No room$'
! Some magic data. All EM systems need these.
.define .trppc, .ignmask, _errno
.comm .trppc, 4
.comm .ignmask, 4
.comm _errno, 4

View file

@ -0,0 +1,25 @@
include("plat/build.lua")
ackfile {
name = "boot",
srcs = { "./boot.s" },
vars = { plat = "msdos86" }
}
build_plat_libs {
name = "libs",
arch = "i86",
plat = "msdos86",
}
installable {
name = "pkg",
map = {
"+tools",
"+libs",
"./include+pkg",
["$(PLATIND)/msdos86/boot.o"] = "+boot",
["$(PLATIND)/msdos86/libsys.a"] = "./libsys+lib",
}
}

View file

@ -0,0 +1,21 @@
include("plat/build.lua")
build_as {
name = "as",
arch = "i86",
}
build_ncg {
name = "ncg",
arch = "i86",
}
return installable {
name = "tools",
map = {
["$(PLATDEP)/msdos86/as"] = "+as",
["$(PLATDEP)/msdos86/ncg"] = "+ncg",
["$(PLATIND)/descr/msdos86"] = "./descr",
"util/opt+pkg",
}
}

88
plat/msdos86/descr Normal file
View file

@ -0,0 +1,88 @@
# $Source$
# $State$
# $Revision$
var w=2
var wa=2
var p=2
var pa=2
var s=2
var sa=2
var l=4
var la=2
var f=4
var fa=2
var d=8
var da=2
var x=8
var xa=2
var ARCH=i86
var PLATFORM=msdos86
var PLATFORMDIR={EM}/share/ack/{PLATFORM}
var CPP_F=-D__MSDOS__ -D__DOS__ -D_DOS
var ALIGN=-a0:2 -a1:2 -a2:2 -a3:2
var MACHOPT_F=-m8
var EGO_PLAT_FLAGS=-M{EM}/share/ack/ego/{ARCH}.descr
# Override the setting in fe so that files compiled for this platform can see
# the platform-specific headers.
var C_INCLUDES=-I{PLATFORMDIR}/include -I{EM}/share/ack/include/ansi
# These will be overridden under the small (separate I/D) memory model.
var SEPID=-b0:0x100
var LOD=aslod
name be
from .m.g
to .s
program {EM}/lib/ack/{PLATFORM}/ncg
args <
stdout
need .e
end
name as
from .s.so
to .o
program {EM}/lib/ack/{PLATFORM}/as
args - -o > <
prep cond
end
name led
from .o.a
to .out
program {EM}/lib/ack/em_led
mapflag -l* LNAME={PLATFORMDIR}/lib*
mapflag -i SEPID=-b1:0
mapflag -fp FLOATS={EM}/{ILIB}fp
args {ALIGN} {SEPID} \
({RTS}:.b=-u _i_main) \
(.e:{HEAD}={PLATFORMDIR}/boot.o) \
({RTS}:.ocm.bas.b={PLATFORMDIR}/c-ansi.o) \
({RTS}:.c={PLATFORMDIR}/c-ansi.o) \
({RTS}:.mod={PLATFORMDIR}/modula2.o) \
({RTS}:.p={PLATFORMDIR}/pascal.o) \
-o > < \
(.p:{TAIL}={PLATFORMDIR}/libpascal.a) \
(.b:{TAIL}={PLATFORMDIR}/libb.a) \
(.bas:{TAIL}={PLATFORMDIR}/libbasic.a) \
(.mod:{TAIL}={PLATFORMDIR}/libmodula2.a) \
(.ocm:{TAIL}={PLATFORMDIR}/liboccam.a) \
(.ocm.bas.mod.b.c.p:{TAIL}={PLATFORMDIR}/libc.a) \
{FLOATS?} \
(.e:{TAIL}={PLATFORMDIR}/libem.a \
{PLATFORMDIR}/libsys.a \
{PLATFORMDIR}/libc.a \
{PLATFORMDIR}/libem.a \
{PLATFORMDIR}/libend.a)
linker
end
name cv
from .out
to .exe
mapflag -i LOD=amzlod
program {EM}/bin/{LOD}
args < >
outfile msdos86.exe
end

View file

@ -0,0 +1,11 @@
/* $Source$
* $State$
* $Revision$
*/
#ifndef _ACK_PLAT_H
#define _ACK_PLAT_H
#define ACKCONF_WANT_O_TEXT_O_BINARY 1
#endif

View file

@ -0,0 +1,24 @@
include("plat/build.lua")
headermap = {}
packagemap = {}
local function addheader(h)
headermap[h] = "./"..h
packagemap["$(PLATIND)/msdos86/include/"..h] = "./"..h
end
addheader("ack/plat.h")
addheader("sys/types.h")
acklibrary {
name = "headers",
hdrs = headermap
}
installable {
name = "pkg",
map = packagemap
}

View file

@ -0,0 +1,9 @@
#ifndef _SYS_TYPES_H
#define _SYS_TYPES_H
typedef long pid_t;
typedef int mode_t;
typedef long time_t;
typedef long suseconds_t;
#endif

View file

@ -0,0 +1,19 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .bss
! This data block is used to store information about the current line number
! and file.
.define hol0
.comm hol0, 8

59
plat/msdos86/libsys/brk.c Normal file
View file

@ -0,0 +1,59 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include "libsys.h"
#define STACK_BUFFER 128 /* number of bytes to leave for stack */
extern char _end[1];
static char* current = _end;
int brk(void* newend)
{
/* This variable is used to figure out the current stack pointer,
* by taking its address. */
char dummy;
char* p = newend;
if ((p > (&dummy - STACK_BUFFER)) ||
(p < _end))
{
errno = ENOMEM;
return -1;
}
current = p;
return 0;
}
void* sbrk(int increment)
{
char* old;
char* new;
if (increment == 0)
return current;
old = current;
new = old + increment;
if ((increment > 0) && (new <= old))
goto out_of_memory;
else if ((increment < 0) && (new >= old))
goto out_of_memory;
if (brk(new) < 0)
goto out_of_memory;
return old;
out_of_memory:
errno = ENOMEM;
return OUT_OF_MEMORY;
}

View file

@ -0,0 +1,15 @@
acklibrary {
name = "lib",
srcs = {
"./*.c",
"./*.s",
},
deps = {
"lang/cem/libcc.ansi/headers+headers",
"plat/msdos86/include+headers",
},
vars = {
plat = "msdos86"
}
}

View file

@ -0,0 +1,23 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Close a file.
.define _close
_close:
mov bx, sp
mov bx, 2(bx)
movb ah, 0x3E
int 0x21
jmp .sys_zret

View file

@ -0,0 +1,14 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include "libsys.h"
int creat(const char* path, int mode)
{
return open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
}

View file

@ -0,0 +1,28 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
#define D(e) .define e; e
.sect .data
! Define various ACK error numbers. Note that these are *not* ANSI C
! errnos, and are used for different purposes.
D(ERANGE) = 1
D(ESET) = 2
D(EIDIVZ) = 6
D(EHEAP) = 17
D(EILLINS) = 18
D(EODDZ) = 19
D(ECASE) = 20
D(EBADMON) = 25

View file

@ -0,0 +1,27 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Get the current process identifier. For MS-DOS, use the Program Segment
! Prefix (PSP) segment as the PID.
!
! (Note that pid_t is a 32-bit type. This is to allow for PSP addresses
! above 0x7FFF:0.)
.define _getpid
_getpid:
movb ah, 0x51
int 0x21
xchg bx, ax
xor dx, dx
ret

View file

@ -0,0 +1,92 @@
/* $Source$
* $State$
* $Revision$
*/
/*
* Derived from dos-gettimeofday.c for newlib-ia16 which was written for the
* gcc-ia16 toolchain.
*
* Copyright (c) 2017--2021 TK Chia
*
* The authors hereby grant permission to use, copy, modify, distribute,
* and license this software and its documentation for any purpose, provided
* that existing copyright notices are retained in all copies and that this
* notice is included verbatim in any distributions. No written agreement,
* license, or royalty fee is required for any of the authorized uses.
* Modifications to this software may be copyrighted by their authors
* and need not follow the licensing terms described here, provided that
* the new terms are clearly indicated on the first page of each file where
* they apply.
*/
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdbool.h>
#include "libsys.h"
extern long _timezone;
extern int _daylight;
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
static volatile bool tzinited = false;
struct _dosdate dosdt, olddt;
struct _dostime dostm;
suseconds_t usec;
struct tm tm;
time_t tim;
if (!tzinited)
{
tzset();
tzinited = true;
}
memset(&tm, 0, sizeof tm);
_sys_getdate(&dosdt);
do
{
uint8_t hour;
_sys_gettime(&dostm);
tm.tm_hour = hour = dostm.hour;
tm.tm_min = dostm.minute;
tm.tm_sec = dostm.second;
usec = dostm.hsecond * 10000UL;
/* If the time is close to midnight, read the date again to
* check for midnight crossover. If crossover happens,
* repeat until it stops. */
if (hour)
break;
olddt = dosdt;
_sys_getdate(&dosdt);
} while (dosdt.day != olddt.day);
tm.tm_year = dosdt.year - 1900;
tm.tm_mon = dosdt.month - 1;
tm.tm_mday = dosdt.day;
tm.tm_isdst = -1;
tim = mktime(&tm);
if (tim == -1)
return -1;
if (tv)
{
tv->tv_sec = tim;
tv->tv_usec = usec;
}
if (tz)
{
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}
return 0;
}

View file

@ -0,0 +1,37 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Say whether a given file descriptor number refers to a terminal.
.define _isatty
_isatty:
mov bx, sp
mov bx, 2(bx)
mov ax, 0x4400
int 0x21
jc error
testb dl, dl
jz not_tty
mov ax, 1
ret
not_tty:
mov (_errno), 25 ! ENOTTY
not_tty_2:
xor ax, ax
ret
error:
push ax
call __sys_seterrno
pop cx
jmp not_tty_2

View file

@ -0,0 +1,14 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
int kill(pid_t pid, int sig)
{
errno = EINVAL;
return -1;
}

View file

@ -0,0 +1,74 @@
/* $Source$
* $State$
* $Revision$
*/
#ifndef LIBSYS_H
#define LIBSYS_H
#include <limits.h>
#include <stdint.h>
#include <sys/types.h>
#define OUT_OF_MEMORY ((void *)-1) /* sbrk returns this on failure */
/*
* Data structure for remembering whether each file descriptor is open in
* text mode (O_TEXT) or binary mode (O_BINARY).
*
* Currently this is just a simple linked list, where each linked list node
* records the modes for 16 file descriptors, in bit vector form (0: text,
* 1: binary). In addition, each node also remembers, for each of its file
* descriptors, whether an end-of-file (^Z) character was encountered when
* reading from it in text mode.
*
* List nodes are allocated using sbrk() and never freed.
*
* This scheme should be fast and light enough, as the number of open file
* descriptors is expected to be small.
*
* FIXME: the code for updating this structure is not exactly thread-safe.
*/
typedef uint16_t _fdvec_t;
struct _fdmodes {
struct _fdmodes *next;
int begfd;
_fdvec_t modevec, eofvec;
};
extern struct _fdmodes _sys_fdmodes;
#define _FDVECMASK (sizeof(_fdvec_t) * CHAR_BIT - 1)
/* Structures for getting MS-DOS's idea of the current date and time. */
struct _dosdate {
uint8_t day;
uint8_t month;
uint16_t year;
};
struct _dostime {
uint8_t minute;
uint8_t hour;
uint8_t hsecond;
uint8_t second;
};
extern int _sys_getmode(int);
extern int _sys_setmode(int, int);
extern int _sys_iseof(int);
extern int _sys_seteof(int, int);
extern ssize_t _sys_rawread(int, char *, size_t);
extern ssize_t _sys_rawwrite(int, const char *, size_t);
extern int _sys_isopen(int);
extern int _sys_isreadyr(int);
extern int _sys_exists(const char *);
extern int _sys_rawcreat(const char *, unsigned);
extern int _sys_rawopen(const char *, int);
extern off_t _sys_rawlseek(int, off_t, int);
extern void _sys_getdate(struct _dosdate *);
extern void _sys_gettime(struct _dostime *);
#endif

View file

@ -0,0 +1,26 @@
/* $Source$
* $State$
* $Revision$
*/
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include "libsys.h"
off_t lseek(int fd, off_t offset, int whence)
{
off_t newoff;
if (whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END)
{
errno = EINVAL;
return -1L;
}
newoff = _sys_rawlseek(fd, offset, whence);
if (newoff != -1L)
_sys_seteof(fd, 0);
return newoff;
}

101
plat/msdos86/libsys/open.c Normal file
View file

@ -0,0 +1,101 @@
/* $Source$
* $State$
* $Revision$
*/
/*
* Derived from dos-open.c for newlib-ia16 which was written for the
* gcc-ia16 toolchain.
*
* Copyright (c) 2018 Bart Oldeman
* Copyright (c) 2019--2021 TK Chia
*
* The authors hereby grant permission to use, copy, modify, distribute,
* and license this software and its documentation for any purpose, provided
* that existing copyright notices are retained in all copies and that this
* notice is included verbatim in any distributions. No written agreement,
* license, or royalty fee is required for any of the authorized uses.
* Modifications to this software may be copyrighted by their authors
* and need not follow the licensing terms described here, provided that
* the new terms are clearly indicated on the first page of each file where
* they apply.
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "libsys.h"
int open(const char* pathname, int flags, ...)
{
int fd = -1, binmode;
off_t ret;
if ((flags & ~(O_ACCMODE | O_CREAT | O_TRUNC | O_APPEND
| O_TEXT | O_BINARY)))
{
/* bail out if there are unknown flags */
errno = EINVAL;
return -1;
}
binmode = flags & (O_TEXT | O_BINARY);
if (binmode != O_TEXT && binmode != O_BINARY && binmode != 0)
{
/* bail out if both O_TEXT and O_BINARY are specified (!) */
errno = EINVAL;
return -1;
}
if ((flags & O_CREAT))
{
/* special but common case O_WRONLY | O_CREAT | O_TRUNC
* should be handled by only calling _sys_rawcreat */
int fileexists = 0;
if (!(flags & O_TRUNC))
fileexists = _sys_exists(pathname);
if (!fileexists)
{
va_list ap;
mode_t mode;
va_start(ap, flags);
mode = va_arg(ap, mode_t);
va_end(ap);
fd = _sys_rawcreat(pathname, mode & 0200 ? 0 : 1);
if (fd != -1)
{
if ((flags & O_ACCMODE) == O_WRONLY)
return fd;
close(fd);
fd = -1;
}
}
}
/* try to open file with mode */
if (fd == -1)
fd = _sys_rawopen(pathname, flags & O_ACCMODE);
if (fd == -1)
return fd;
ret = 0;
/* handle O_TRUNC and O_APPEND */
if ((flags & O_TRUNC))
ret = _sys_rawwrite(fd, NULL, 0);
else if ((flags & O_APPEND))
ret = _sys_rawlseek(fd, 0L, SEEK_END);
if (ret == -1)
{
close(fd);
return -1;
}
/* handle O_TEXT and O_BINARY, and reset "end of file encountered" */
_sys_setmode(fd, binmode == O_BINARY ? O_BINARY : O_TEXT);
_sys_seteof(fd, 0);
return fd;
}

View file

@ -0,0 +1,77 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "libsys.h"
ssize_t read(int fd, void* buffer, size_t count)
{
char *p, *q;
ssize_t r, tot;
size_t left;
int eof = 0;
if (!count || _sys_getmode(fd) == O_BINARY)
return _sys_rawread(fd, buffer, count);
if (_sys_iseof(fd))
return 0;
/*
* If the file descriptor is an O_TEXT fd, read from it, and then
* remove CRs from the input. After removing CRs, if the buffer is
* not filled and the fd still has bytes left to read, then keep
* reading, and keep removing CRs.
*
* Also handle end-of-file indicators (^Z). If we see one, move the
* file pointer to just before the ^Z. Also set an internal flag
* for the fd so that we do not try to read any further (until e.g.
* a seek happens).
*/
p = buffer;
tot = 0;
do
{
r = _sys_rawread(fd, p, count - (p - (char *)buffer));
if (r <= 0)
return tot;
q = memchr(p, 0x1a, (size_t)r);
if (q)
{
_sys_rawlseek(fd, (off_t)(q - p) - r, SEEK_CUR);
r = q - p;
eof = 1;
_sys_seteof(fd, 1);
}
q = memchr(p, '\r', (size_t)r);
if (!q)
return tot + r;
tot += q - p;
left = r - (q + 1 - p);
p = q;
++q;
while (left)
{
char c = *q++;
if (c != '\r')
{
*p++ = c;
++tot;
}
--left;
}
} while (tot < count && !eof && _sys_isreadyr(fd));
return tot;
}

View file

@ -0,0 +1,23 @@
/* $Source$
* $State$
* $Revision$
*/
#include <errno.h>
#include <fcntl.h>
#include "libsys.h"
int _setmode(int fd, int mode)
{
if (mode != O_TEXT && mode != O_BINARY)
{
errno = EINVAL;
return -1;
}
if (!_sys_isopen(fd))
{
errno = EBADF;
return -1;
}
return _sys_setmode(fd, mode);
}

View file

@ -0,0 +1,15 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include "libsys.h"
sighandler_t signal(int signum, sighandler_t handler)
{
return SIG_DFL;
}

View file

@ -0,0 +1,25 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Say whether a file exists with the given name.
.define __sys_exists
__sys_exists:
mov bx, sp
mov dx, 2(bx)
mov ax, 0x4300
int 0x21
sbb ax, ax
inc ax
ret

View file

@ -0,0 +1,13 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include "libsys.h"
/*
* By default, consider all file descriptors, including those for stdin (0),
* stdout (1), and stderr (2), as being open in text mode.
*/
struct _fdmodes _sys_fdmodes = { NULL, 0, 0 };

View file

@ -0,0 +1,25 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Get the current system date from MS-DOS.
.define __sys_getdate
__sys_getdate:
movb ah, 0x2a
int 0x21
mov bx, sp
mov bx, 2(bx)
mov (bx), dx
mov 2(bx), cx
ret

View file

@ -0,0 +1,27 @@
/* $Source$
* $State$
* $Revision$
*/
#include <fcntl.h>
#include "libsys.h"
/*
* Say whether a particular fd is currently open in text or binary mode.
* Assume that the fd is valid. Return O_TEXT or O_BINARY.
*/
int _sys_getmode(int fd)
{
int reqbegfd = fd & ~_FDVECMASK;
struct _fdmodes *p = &_sys_fdmodes;
_fdvec_t mask;
while (p->begfd != reqbegfd)
{
p = p->next;
if (!p)
return O_TEXT;
}
mask = (_fdvec_t)1 << (fd & _FDVECMASK);
return (p->modevec & mask) ? O_BINARY : O_TEXT;
}

View file

@ -0,0 +1,25 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Get the current system time from MS-DOS.
.define __sys_gettime
__sys_gettime:
movb ah, 0x2c
int 0x21
mov bx, sp
mov bx, 2(bx)
mov (bx), cx
mov 2(bx), dx
ret

View file

@ -0,0 +1,111 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libsys.h"
struct for_main {
int argc;
char **argv;
char **envp;
};
/*
* Marshal the command line and environment variable data provided by
* MS-DOS, into the standard format expected by a C main(...) routine. The
* environment variable data has been copied to the near data segment.
*
* Return zero if everything went well, non-zero otherwise.
*/
int _sys_initmain(char *arg_data, size_t n_env_vars, char *env_data,
unsigned msdos_ver, struct for_main *out)
{
char **argv, **envp;
char c, *p, **pp;
unsigned char n;
/*
* Allocate space for argv[] for the maximum possible number of
* arguments. We can shrink this later.
*/
if ((argv = sbrk(65 * sizeof(char *))) == OUT_OF_MEMORY)
return -1;
out->argv = argv;
/* Sanity-check the original command line. */
p = arg_data;
n = (unsigned char)*p;
if (n > 0x7E || p[1 + n] != '\r')
return -1;
/* Initialize argv[0] to an empty string first. */
*p = 0;
argv[0] = p;
/*
* Split the command line string into separate arguments.
* TODO: handle double-quoting, backslashes, etc.
*/
pp = argv + 1;
do
{
do
c = *++p;
while (c == ' ' || c == '\t');
if (c != '\r')
{
*pp++ = p;
do
c = *++p;
while (c != ' ' && c != '\t' && c != '\r');
*p = 0;
}
} while (c != '\r');
/* Wrap up argv[]. Compute argc. Free up unneeded space. */
out->argc = pp - argv;
*pp++ = NULL;
if (brk(pp) != 0)
return -1;
/* Allocate space for envp[]. */
if ((envp = sbrk((n_env_vars + 1) * sizeof(char *))) == OUT_OF_MEMORY)
return -1;
out->envp = envp;
/*
* Populate envp[]. The environment data is simply a series of ASCIIZ
* strings, one after another, terminated by an empty string.
*/
pp = envp;
p = env_data;
while (*p)
{
*pp++ = p;
while (*++p);
++p;
}
*pp = NULL;
/*
* If there is a program name (MS-DOS version is 3 or above), point
* argv[0] to it.
*
* p points to the zero byte just before the count of strings (a
* shortword) following the environment variables, so advance p by 3
* bytes to get at the program name.
*/
if ((msdos_ver & 0x00ff) >= 3)
{
p += 3;
argv[0] = p;
}
/* We are done. */
return 0;
}

View file

@ -0,0 +1,27 @@
/* $Source$
* $State$
* $Revision$
*/
#include <fcntl.h>
#include "libsys.h"
/*
* Say whether a particular fd is currently open in text mode and has just
* hit an MS-DOS end-of-file character (^Z).
*/
int _sys_iseof(int fd)
{
int reqbegfd = fd & ~_FDVECMASK;
struct _fdmodes *p = &_sys_fdmodes;
_fdvec_t mask;
while (p->begfd != reqbegfd)
{
p = p->next;
if (!p)
return 0;
}
mask = (_fdvec_t)1 << (fd & _FDVECMASK);
return p->eofvec & mask;
}

View file

@ -0,0 +1,26 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Say whether a given file descriptor number refers to a valid open file
! descriptor.
.define __sys_isopen
__sys_isopen:
mov bx, sp
mov bx, 2(bx)
mov ax, 0x4400
int 0x21
sbb ax, ax
inc ax
ret

View file

@ -0,0 +1,28 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Say whether a file descriptor is ready for input, i.e. reading from the
! fd will immediately return something.
.define __sys_isreadyr
__sys_isreadyr:
mov bx, sp
mov ax, 0x4406
mov bx, 2(bx)
int 0x21
jnc ok
movb al, 0
ok:
cbw
ret

View file

@ -0,0 +1,24 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Create or truncate a file.
.define __sys_rawcreat
__sys_rawcreat:
movb ah, 0x3c
mov bx, sp
mov dx, 2(bx)
mov cx, 4(bx)
int 0x21
jmp .sys_axret

View file

@ -0,0 +1,26 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Move the current file position for a file descriptor.
.define __sys_rawlseek
__sys_rawlseek:
movb ah, 0x42
mov bx, sp
mov dx, 4(bx)
mov cx, 6(bx)
movb al, 8(bx)
mov bx, 2(bx)
int 0x21
jmp .sys_dxaxret

View file

@ -0,0 +1,24 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Open an existing file.
.define __sys_rawopen
__sys_rawopen:
movb ah, 0x3d
mov bx, sp
mov dx, 2(bx)
movb al, 4(bx)
int 0x21
jmp .sys_axret

View file

@ -0,0 +1,33 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Read/write bytes to/to a file descriptor. These routines do not do any
! translation between CRLF and LF line endings.
!
! Note that, for MS-DOS, a raw "write" request of zero bytes will truncate
! (or extend) the file to the current file position.
.define __sys_rawread
__sys_rawread:
movb ah, 0x3f
.data1 0x3d ! eat up the next instruction
.define __sys_rawwrite
__sys_rawwrite:
movb ah, 0x40
mov bx, sp
mov dx, 4(bx)
mov cx, 6(bx)
mov bx, 2(bx)
int 0x21
jmp .sys_axret

View file

@ -0,0 +1,42 @@
/* $Source$
* $State$
* $Revision$
*/
#include <fcntl.h>
#include "libsys.h"
/*
* Set or reset the "end-of-file encountered" indicator for a particular fd.
* Assume that the fd is valid, and is being read from in text mode.
*/
int _sys_seteof(int fd, int eof)
{
int reqbegfd = fd & ~_FDVECMASK;
struct _fdmodes *p = &_sys_fdmodes;
_fdvec_t mask;
while (p->begfd != reqbegfd)
{
p = p->next;
if (!p)
{
if (!eof)
return 0;
if ((p = sbrk(sizeof(struct _fdmodes)))
== OUT_OF_MEMORY)
return -1;
p->next = _sys_fdmodes.next;
p->begfd = reqbegfd;
p->modevec = p->eofvec = 0;
_sys_fdmodes.next = p;
break;
}
}
mask = (_fdvec_t)1 << (fd & _FDVECMASK);
if (eof)
p->eofvec |= mask;
else
p->eofvec &= ~mask;
return 0;
}

View file

@ -0,0 +1,45 @@
/* $Source$
* $State$
* $Revision$
*/
#include <errno.h>
static const signed char err_map[] =
{
0, /* 0x00 no error */
EINVAL, /* 0x01 function number invalid */
ENOENT, /* 0x02 file not found */
ENOENT, /* 0x03 path not found */
EMFILE, /* 0x04 too many open files */
EACCES, /* 0x05 access denied */
EBADF, /* 0x06 invalid handle */
EFAULT, /* 0x07 mem. control block destroyed */
ENOMEM, /* 0x08 insufficient memory */
EFAULT, /* 0x09 memory block address invalid */
E2BIG, /* 0x0A environment invalid */
ENOEXEC, /* 0x0B format invalid */
EINVAL, /* 0x0C access code invalid */
EINVAL, /* 0x0D data invalid */
ENOEXEC, /* 0x0E (?) fixup overflow */
ENODEV, /* 0x0F invalid drive */
EBUSY, /* 0x10 attempt to remove curr. dir. */
EXDEV, /* 0x11 not same device */
ENOENT, /* 0x12 no more files */
EIO, /* 0x13 disk write-protected */
EIO, /* 0x14 unknown unit */
ENXIO, /* 0x15 drive not ready */
};
/*
* Map an MS-DOS 2+ system error code to an `errno' value and store that in
* `errno'. Return a longword -1.
*/
long _sys_seterrno(unsigned dos_err)
{
if (dos_err < sizeof(err_map) / sizeof(err_map[0]))
errno = err_map[dos_err];
else
errno = EIO;
return -1;
}

View file

@ -0,0 +1,55 @@
/* $Source$
* $State$
* $Revision$
*/
#include <fcntl.h>
#include "libsys.h"
/*
* Set text/binary mode for a particular fd. Assume that the fd is valid,
* and that the mode is either O_TEXT or O_BINARY.
*
* Return the previous mode (either O_TEXT or O_BINARY) on success, -1 (with
* errno set) on error.
*/
int _sys_setmode(int fd, int mode)
{
int reqbegfd = fd & ~_FDVECMASK;
struct _fdmodes *p = &_sys_fdmodes;
_fdvec_t mask;
while (p->begfd != reqbegfd)
{
p = p->next;
if (!p)
{
if (mode == O_TEXT)
return O_TEXT;
if ((p = sbrk(sizeof(struct _fdmodes)))
== OUT_OF_MEMORY)
return -1;
p->next = _sys_fdmodes.next;
p->begfd = reqbegfd;
p->modevec = p->eofvec = 0;
_sys_fdmodes.next = p;
break;
}
}
mask = (_fdvec_t)1 << (fd & _FDVECMASK);
if (mode == O_BINARY)
{
if (p->modevec & mask)
return O_BINARY;
p->modevec |= mask;
p->eofvec &= ~mask;
return O_TEXT;
}
else
{
if (!(p->modevec & mask))
return O_TEXT;
p->modevec &= ~mask;
return O_BINARY;
}
}

View file

@ -0,0 +1,41 @@
#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! .sys_zret: if the carry flag is set, then set `errno' from the DOS error
! code in ax, and return a shortword -1. If the carry flag is clear, just
! return a shortword zero.
!
! .sys_axret: if the carry flag is set, then set `errno' from the DOS error
! code in ax, and return a shortword -1. If the carry flag is clear, just
! return ax as a return value.
!
! .sys_dxaxret: same as .sys_axret, but return a longword -1 or dx:ax as a
! return value.
.extern .sys_zret
.extern .sys_axret
.extern .sys_dxaxret
.sys_zret:
jc error
xor ax, ax
no_error:
ret
.sys_axret:
.sys_dxaxret:
jnc no_error
error:
push ax
call __sys_seterrno
pop cx
ret

View file

@ -0,0 +1,61 @@
/* $Source$
* $State$
* $Revision$
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "libsys.h"
ssize_t write(int fd, void* buffer, size_t count)
{
static const char crlf[2] = "\r\n";
int i;
char *p, *q;
size_t left, n;
ssize_t r, tot;
if (!count)
return 0;
if (_sys_getmode(fd) == O_BINARY)
return _sys_rawwrite(fd, buffer, count);
/* If the file descriptor is an O_TEXT fd, translate LFs to CRLFs. */
p = buffer;
left = count;
tot = 0;
while (left)
{
q = memchr(p, '\n', left);
if (!q)
return _sys_rawwrite(fd, p, left);
n = q - p;
if (n)
{
r = _sys_rawwrite(fd, p, n);
if (r <= 0)
return tot ? tot : r;
tot += r;
p += r;
left -= r;
if (r != n)
break;
}
r = _sys_rawwrite(fd, crlf, sizeof crlf);
if (r != 2)
{
if (r > 0)
r = 0;
return tot ? tot : r;
}
++tot;
++p;
--left;
}
return tot;
}