4a081501d8
- bios/conio/env/errno/gfx/io/mem/mouse/stdio/stdlib/string/sys/time/ video разложены по модулям: общие статики и helpers — в internal _-модулях (_conio.h/_mouse.h/_gfx.h/_palette.h/_atexit.h/_time.h) - lib/Makefile: LIBC_C = wildcard libc/*/*.c — гранулярность файлов = гранулярность DCE линкера - эффект _CODE: gfx_text 6986→2568 Б, gfx_mous −1745, gfx_demo/d16 −542; ранее timedir −3270, ls −3098, stattest −2995 - комментарии оставшихся модулей переведены на русский; puts: убран мёртвый pchars; videomode_raw разложен на get/set - docs/libc-split-asm-cases.md — правила asm-связок между модулями; docs/libc-roadmap.md — план этапа - восстановлен examples/mdview/SAMPLE.MD (нужен make floppy) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
3.6 KiB
C
109 lines
3.6 KiB
C
/*
|
||
* stat — POSIX stat() поверх метаданных ESTEX.
|
||
*
|
||
* stat(path, &st) -> open(O_RDONLY) + fstat() + close. Работает для
|
||
* любого обычного файла; для директорий — fallback
|
||
* через F_FIRST (и перебор "*.*" для "."/"..").
|
||
*/
|
||
|
||
#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>
|
||
|
||
/* Конвертация DOS-формата даты+времени ESTEX в time_t (используется
|
||
* на пути через F_FIRST для записей директорий). */
|
||
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);
|
||
}
|
||
|
||
/* Возвращает 1, если path — "." или "..", иначе 0. Читает максимум
|
||
* 3 байта; NULL-безопасно. ~28 байт / 34–117 тактов от входа. */
|
||
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)
|
||
{
|
||
/* Обычный файл: open + fstat. */
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd >= 0) {
|
||
int r = fstat(fd, buf);
|
||
close(fd);
|
||
return r;
|
||
}
|
||
int saved = errno;
|
||
|
||
/* Пробуем ffirst напрямую — работает для обычных подпапок. */
|
||
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;
|
||
}
|
||
|
||
/* Проверено 2026-05-29: ESTEX F_FIRST отвергает голые "." и ".."
|
||
* с EINAME (16), как и open(). Но в листинге "*.*" они ЕСТЬ с
|
||
* FA_DIREC — ищем перебором. */
|
||
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);
|
||
}
|
||
/* Крайний случай — синтетическая запись: ФС их не отдала. */
|
||
buf->st_mode = S_IFDIR | S_IRWXU;
|
||
buf->st_size = 0;
|
||
buf->st_mtime = 0;
|
||
return 0;
|
||
}
|
||
|
||
errno = saved;
|
||
return -1;
|
||
}
|