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:
2026-07-06 16:09:29 +03:00
parent 48d552bf3a
commit 057dd615ba
11 changed files with 361 additions and 58 deletions
+32 -49
View File
@@ -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;
}