/* * stat — POSIX stat() поверх метаданных ESTEX. * * stat(path, &st) -> open(O_RDONLY) + fstat() + close. Работает для * любого обычного файла; для директорий — fallback * через F_FIRST (и перебор "*.*" для "."/".."). */ #include #include #include #include #include #include #include #include /* Конвертация 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; }