Files
snark13 c71e249a4e Add full compiler toolchain, libc, examples and reference docs
First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.

What's in here:
  bin/sprinter-cc        — driver script invoking SDCC + linker + mkexe
  libc/                  — Sprinter-specific libc layer over ESTEX/BIOS
                           (conio, gfx, io, mem, stdio + headers)
  runtime/               — crt0 variants (default/small/banked/minimal)
                           + heap + bank trampolines
  toolchain/             — mkexe (SprintEXE packer, C + tests)
  examples/              — 30 demo programs (gfx, file I/O, env, time, …)
  lib/Makefile           — builds the libc archive (sprinter.lib)
  docs/                  — converted Sprinter manuals + asm reference samples
  third_party/           — solid-c reference compiler dump + sdcc setup script
  release_docs/          — packaging / release notes

gitignore overhaul:
  • Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
    and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
    on macOS APFS).  Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
  • Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
    INPUT fixtures, not build outputs.
  • Collapse the duplicated SDCC/C/Sdcc sections into one section per
    concern (build outputs / vendored / OS-junk).
  • Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 16:13:21 +03:00

191 lines
6.1 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* stat.c — POSIX stat() and fstat() over ESTEX metadata.
*
* fstat(fd, &st) -> ESTEX GET_D_T ($17) for mtime + lseek/SEEK_END
* for size.
* stat(path, &st) -> open(O_RDONLY) + fstat() + close. This works
* for any regular file; directory paths fail at
* open() — F_FIRST-based stat had unreliable
* semantics for exact filenames.
*
* Sprinter / DSS doesn't track POSIX owner/group/inode, so we synth a
* minimal mode (S_IFREG | rw user perm).
*/
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <dir.h>
/* ESTEX GET_D_T: A=fd, C=$17 → D=day, E=month, IX=year, H=hour, L=min,
* B=sec. CF=1 + A=errcode on failure.
*
* Writes directly into *out using `ex (sp), hl` to swap the saved out
* pointer with HL=hour:min after RST — no static scratch needed.
* `out->dow` is left untouched (GET_D_T doesn't return it). */
static int get_dt_for_handle(int fd, datetime_t *out) __naked
{
(void)fd; (void)out;
__asm
;; __sdcccall(1): fd in HL (low byte), out in DE.
push ix ; save caller IX
push de ; stash out pointer
ld a, l ; A = fd
ld c, #0x17 ; ESTEX GET_D_T
rst #0x10
jr c, _gdt_err
;; D=day E=month IX=year H=hour L=min B=sec
ex (sp), hl ; TOS<->HL: HL=out, TOS=hour:min
ld (hl), d ; +0 day
inc hl
ld (hl), e ; +1 month
inc hl
push ix ; year onto stack
pop de ; DE = year
ld (hl), e ; +2 year low
inc hl
ld (hl), d ; +3 year high
inc hl
pop de ; D=hour E=min (from earlier ex (sp))
ld (hl), d ; +4 hour
inc hl
ld (hl), e ; +5 min
inc hl
ld (hl), b ; +6 sec (+7 dow left untouched)
pop ix ; restore caller IX
ld de, #0
ret
_gdt_err:
pop hl ; discard stashed out pointer
pop ix ; restore caller IX
call __errno_set
ld de, #-1
ret
__endasm;
}
int fstat(int fd, struct stat *buf)
{
/* Size via lseek trick. */
long cur = lseek(fd, 0L, SEEK_CUR);
if (cur < 0) return -1;
long end = lseek(fd, 0L, SEEK_END);
if (end < 0) return -1;
(void)lseek(fd, cur, SEEK_SET);
buf->st_size = (uint32_t)end;
/* Date/time via ESTEX. */
datetime_t ft;
if (get_dt_for_handle(fd, &ft) < 0) return -1;
{
struct tm tm;
tm.tm_sec = ft.second;
tm.tm_min = ft.minute;
tm.tm_hour = ft.hour;
tm.tm_mday = ft.day;
tm.tm_mon = (unsigned char)(ft.month - 1);
tm.tm_year = (int)ft.year - 1900;
tm.tm_isdst = 0;
tm.tm_hundredth = 0;
buf->st_mtime = mktime(&tm);
}
buf->st_mode = S_IFREG | S_IRUSR | S_IWUSR;
return 0;
}
/* Convert ESTEX DOS-style date+time to time_t epoch (used by stat()
* when going through F_FIRST for directory entries). */
static time_t dos_to_epoch(uint16_t date, uint16_t dtime)
{
struct tm tm;
tm.tm_sec = (unsigned char)((dtime & 0x1F) << 1);
tm.tm_min = (unsigned char)((dtime >> 5) & 0x3F);
tm.tm_hour = (unsigned char)((dtime >> 11) & 0x1F);
tm.tm_mday = (unsigned char)(date & 0x1F);
tm.tm_mon = (unsigned char)(((date >> 5) & 0x0F) - 1);
tm.tm_year = (int)((date >> 9) & 0x7F) + 80; /* DOS year base = 1980 */
tm.tm_isdst = 0;
tm.tm_hundredth = 0;
return mktime(&tm);
}
/* Returns 1 if path is "." or "..", else 0. Reads at most 3 bytes;
* NULL-safe. ~28 bytes / 34117 T-states depending on input. */
static char is_dot_or_dotdot(const char *path) __naked
{
(void)path;
__asm
ld a, h
or a, l
jr Z, _idd_fail ; NULL 0
ld a, (hl)
sub a, #0x2E
jr NZ, _idd_fail ; path[0] != '.'
inc hl
ld a, (hl)
or a, a
jr Z, _idd_ok ; ".\0" 1
sub a, #0x2E
jr NZ, _idd_fail ; path[1] not '.' and not '\0'
inc hl
ld a, (hl)
or a, a
jr Z, _idd_ok ; "..\0" 1
_idd_fail:
xor a, a
ret
_idd_ok:
ld a, #1
ret
__endasm;
}
int stat(const char *path, struct stat *buf)
{
/* Regular file: open + fstat. */
int fd = open(path, O_RDONLY);
if (fd >= 0) {
int r = fstat(fd, buf);
close(fd);
return r;
}
int saved = errno;
/* Try ffirst directly — works for ordinary subdirectories. */
ffblk_t ffb;
if (ffirst(path, &ffb, FA_DIREC) == 0 && (ffb.found_attr & FA_DIREC)) {
buf->st_size = ffb.size;
buf->st_mtime = dos_to_epoch(ffb.date, ffb.time);
buf->st_mode = S_IFDIR | S_IRWXU;
return 0;
}
/* Verified 2026-05-29: ESTEX F_FIRST rejects bare "." and ".." with
* EINAME (16), same as open(). But they DO appear in the "*.*"
* directory listing with FA_DIREC. Iterate to find them. */
if (is_dot_or_dotdot(path)) {
if (ffirst("*.*", &ffb, FA_DIREC) == 0) {
do {
if (strcmp(ffb.found_name, path) == 0) {
buf->st_size = ffb.size;
buf->st_mtime = dos_to_epoch(ffb.date, ffb.time);
buf->st_mode = S_IFDIR | S_IRWXU;
return 0;
}
} while (fnext(&ffb) == 0);
}
/* Last-resort synthetic entry — FS variant didn't expose them. */
buf->st_mode = S_IFDIR | S_IRWXU;
buf->st_size = 0;
buf->st_mtime = 0;
return 0;
}
errno = saved;
return -1;
}