libc: квирки DSS — возврат WRITE и лимит манипуляторов; тесты fdmax/fbench
- ESTEX WRITE ($14) на успехе возвращает DE=0, а НЕ счётчик записанного (вопреки докам; solid-c в своём fflush тоже отключил сравнение по счётчику) — write() теперь судит по CF/A: CF=0&A=0 → n, CF=0&A!=0 → ENOSPC/-1 - DSS выдаёт 8 манипуляторов (fd 2..9; fd 1 держит шелл под запущенный exe), а 9-й OPEN не возвращает 06h — ВЕШАЕТ систему; предохранитель _fd_guard: счётчик в open()/close(), отказ EMFILE без захода в DSS - tests/fdmax — эмпирика лимита (8 хендлов, затем EMFILE=6); tests/fbench — бенчмарк буферизации (floor 512-байтными read, оценка небуферизованного по 1-байтным, fgetc/fgets/fputc) - filetest расширен: raw-probe возврата write, сценарий r+ (чтение-запись-чтение с инвалидацией буфера), ungetc, fprintf Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* _fd_count — счётчик открытых манипуляторов DSS (только данные,
|
||||
* см. _fd_guard.h). Пишут open() и close().
|
||||
*/
|
||||
|
||||
#include "_fd_guard.h"
|
||||
|
||||
uint8_t _fd_open_count;
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* _fd_guard.h — предохранитель от исчерпания манипуляторов DSS
|
||||
* (НЕ публичный заголовок).
|
||||
*
|
||||
* Факт (MAME, DSS 1.71.57, тест fdmax 2026-07-06): DSS выдаёт максимум
|
||||
* 8 манипуляторов (fd 2..9), а 9-й OPEN не возвращает ошибку 06h
|
||||
* "Too many open files" — он ВЕШАЕТ систему. Поэтому libc считает
|
||||
* открытые манипуляторы сама и отказывает с EMFILE, не доводя до DSS.
|
||||
*
|
||||
* Счётчик в _fd_count.c: инкремент в open() (creat идёт через него),
|
||||
* декремент в close() на успешном пути.
|
||||
*/
|
||||
#ifndef _FD_GUARD_H
|
||||
#define _FD_GUARD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define _FD_OPEN_MAX 8
|
||||
|
||||
extern uint8_t _fd_open_count;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* close — закрыть файловый хендл ESTEX CLOSE ($12).
|
||||
* Возвращает 0 или -1 + errno. На успехе декрементирует счётчик
|
||||
* открытых манипуляторов (_fd_guard.h — защита от зависания DSS).
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include "_fd_guard.h"
|
||||
|
||||
int close(int fd) __naked
|
||||
{
|
||||
(void)fd;
|
||||
__asm
|
||||
push ix
|
||||
ld a, l
|
||||
ld c, #0x12 ; ESTEX CLOSE
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _oclose_err
|
||||
;; успех — минус один открытый манипулятор
|
||||
ld hl, #__fd_open_count
|
||||
dec (hl)
|
||||
ld de, #0
|
||||
ret
|
||||
_oclose_err:
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
+32
-49
@@ -1,33 +1,33 @@
|
||||
/*
|
||||
* open / creat / close — ESTEX file-handle primitives.
|
||||
* open — открыть/создать файл по POSIX-флагам.
|
||||
*
|
||||
* The interesting part is the open() flag state machine. We expose POSIX
|
||||
* flag bits (O_RDONLY/O_WRONLY/O_RDWR + O_CREAT/O_EXCL/O_TRUNC/O_APPEND)
|
||||
* and dispatch onto the three ESTEX entry points:
|
||||
* Машина состояний флагов: экспонируем POSIX-биты (O_RDONLY/O_WRONLY/
|
||||
* O_RDWR + O_CREAT/O_EXCL/O_TRUNC/O_APPEND) и диспетчеризуем на три
|
||||
* входа ESTEX:
|
||||
*
|
||||
* $11 OPEN — open existing file
|
||||
* $0A CREATE — create (truncate if exists), return new handle
|
||||
* $0B CREATE_NEW — create only if file does not exist
|
||||
* $11 OPEN — открыть существующий файл
|
||||
* $0A CREATE — создать (усечь, если существует), вернуть хендл
|
||||
* $0B CREATE_NEW — создать, только если файла нет
|
||||
*
|
||||
* Three private __naked wrappers do the raw RST 10h calls; the public
|
||||
* open() / creat() / close() are plain C orchestrators.
|
||||
*
|
||||
* IX is saved across every RST 10h. Failures set errno and return -1.
|
||||
* Три приватных __naked-обёртки делают сырые RST 10h; публичный open()
|
||||
* — обычный C-оркестратор. IX сохраняется через каждый RST.
|
||||
* При ошибке errno установлен, возврат -1.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include "_fd_guard.h"
|
||||
|
||||
/* ---- raw ESTEX wrappers ---------------------------------------------- */
|
||||
/* ---- сырые ESTEX-обёртки ---------------------------------------------- */
|
||||
|
||||
/* ESTEX $11 OPEN: A=mode (1=R, 2=W, 0=R/W), HL=path → A=handle, CF=err. */
|
||||
/* ESTEX $11 OPEN: A=режим (1=R, 2=W, 0=R/W), HL=путь → A=хендл, CF=err. */
|
||||
static int _estex_open_raw(const char *path, int posix_mode) __naked
|
||||
{
|
||||
(void)path; (void)posix_mode;
|
||||
__asm
|
||||
push ix
|
||||
;; HL = path, DE = posix_mode. Translate to ESTEX numbering.
|
||||
;; HL = путь, DE = posix_mode. Перевести в нумерацию ESTEX.
|
||||
ld a, e
|
||||
and a, #0x03
|
||||
ld c, #1
|
||||
@@ -53,14 +53,14 @@ static int _estex_open_raw(const char *path, int posix_mode) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ESTEX $0A CREATE: A=attr, HL=path → A=handle, CF=err.
|
||||
* Truncates an existing file. */
|
||||
/* ESTEX $0A CREATE: A=атрибут, HL=путь → A=хендл, CF=err.
|
||||
* Усекает существующий файл. */
|
||||
static int _estex_create_raw(const char *path) __naked
|
||||
{
|
||||
(void)path;
|
||||
__asm
|
||||
push ix
|
||||
xor a, a ; A = 0 (normal attribute)
|
||||
xor a, a ; A = 0 (обычный атрибут)
|
||||
ld c, #0x0A
|
||||
rst #0x10
|
||||
pop ix
|
||||
@@ -75,8 +75,8 @@ static int _estex_create_raw(const char *path) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ESTEX $0B CREATE_NEW: A=attr, HL=path → A=handle, CF=err.
|
||||
* Fails (errno=EEXIST) if file already exists. */
|
||||
/* ESTEX $0B CREATE_NEW: A=атрибут, HL=путь → A=хендл, CF=err.
|
||||
* Падает (errno=EEXIST), если файл уже существует. */
|
||||
static int _estex_create_new_raw(const char *path) __naked
|
||||
{
|
||||
(void)path;
|
||||
@@ -97,21 +97,28 @@ static int _estex_create_new_raw(const char *path) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ---- public surface --------------------------------------------------- */
|
||||
/* ---- публичная поверхность --------------------------------------------- */
|
||||
|
||||
int open(const char *path, int flags)
|
||||
{
|
||||
int fd;
|
||||
|
||||
/* Предохранитель: 9-й OPEN вешает DSS (см. _fd_guard.h) —
|
||||
* отказать самим, не доводя до syscall. */
|
||||
if (_fd_open_count >= _FD_OPEN_MAX) {
|
||||
errno = EMFILE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flags & O_CREAT) {
|
||||
if (flags & O_EXCL) {
|
||||
/* Must not already exist. */
|
||||
/* Не должен существовать. */
|
||||
fd = _estex_create_new_raw(path);
|
||||
} else if (flags & O_TRUNC) {
|
||||
/* Always create or truncate. */
|
||||
/* Всегда создать или усечь. */
|
||||
fd = _estex_create_raw(path);
|
||||
} else {
|
||||
/* Open if it exists, otherwise create. */
|
||||
/* Открыть, если есть, иначе создать. */
|
||||
fd = _estex_open_raw(path, flags);
|
||||
if (fd < 0 && errno == ENOENT) {
|
||||
fd = _estex_create_raw(path);
|
||||
@@ -124,35 +131,11 @@ int open(const char *path, int flags)
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
_fd_open_count++;
|
||||
|
||||
if (flags & O_APPEND) {
|
||||
/* Position at end of file so future writes append. */
|
||||
/* Встать на конец файла — дальнейшие записи дописывают. */
|
||||
(void)lseek(fd, 0L, SEEK_END);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int creat(const char *path, int mode)
|
||||
{
|
||||
(void)mode; /* Sprinter has no per-file permission bits */
|
||||
return open(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
}
|
||||
|
||||
int close(int fd) __naked
|
||||
{
|
||||
(void)fd;
|
||||
__asm
|
||||
push ix
|
||||
ld a, l
|
||||
ld c, #0x12
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _oclose_err
|
||||
ld de, #0
|
||||
ret
|
||||
_oclose_err:
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* write — блочная запись в файловый хендл ESTEX.
|
||||
*
|
||||
* ESTEX WRITE ($14): A=handle, HL=буфер, DE=число байт
|
||||
* → CF=err с кодом в A.
|
||||
*
|
||||
* КВИРК (подтверждён в MAME, DSS 1.71.57, 2026-07-06): вопреки докам
|
||||
* («DE = реальное количество записанных байт») на успехе DE = 0.
|
||||
* Достоверный признак полной записи — A = 0 при CF = 0 (как у READ,
|
||||
* где A=0 «всё», A=FFh «меньше»); solid-c в своём fflush так же
|
||||
* закомментировал сравнение по DE и проверяет A. Поэтому:
|
||||
* CF=1 → errno из A, возврат -1;
|
||||
* CF=0, A=0 → всё записано, возврат n (запрошенного);
|
||||
* CF=0, A!=0 → частичная запись (диск полон), реальный счётчик
|
||||
* неизвестен → errno=ENOSPC, возврат -1.
|
||||
*
|
||||
* ABI-паттерн идентичен read() — см. read.c.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
int write(int fd, const void *buf, size_t n) __naked
|
||||
{
|
||||
(void)fd; (void)buf; (void)n;
|
||||
__asm
|
||||
pop iy ; IY = адрес возврата
|
||||
pop bc ; BC = n (стековый аргумент)
|
||||
ld a, l ; A = handle
|
||||
ex de, hl ; HL = buf
|
||||
ld d, b
|
||||
ld e, c ; DE = n
|
||||
push bc ; n нужен после RST (всё клобберится)
|
||||
push ix
|
||||
push iy ; сохранить адрес возврата через RST
|
||||
ld c, #0x14 ; ESTEX WRITE
|
||||
rst #0x10
|
||||
pop iy
|
||||
pop ix
|
||||
pop bc ; BC = n
|
||||
jr c, _write_err
|
||||
or a, a ; A=0 — всё записано (DE недостоверен)
|
||||
jr nz, _write_short
|
||||
ld d, b
|
||||
ld e, c ; вернуть запрошенное n
|
||||
jp (iy)
|
||||
_write_short:
|
||||
ld a, #10 ; ENOSPC (DSS 0Ah No free space)
|
||||
_write_err:
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
jp (iy)
|
||||
__endasm;
|
||||
}
|
||||
Reference in New Issue
Block a user