/*
 * fstat — POSIX fstat() поверх метаданных ESTEX.
 *
 *   fstat(fd, &st) -> ESTEX GET_D_T ($17) для mtime + lseek/SEEK_END
 *                     для размера.
 *
 * Sprinter / DSS не хранит POSIX owner/group/inode, поэтому mode
 * синтезируется минимальный (S_IFREG | rw для пользователя).
 */

#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdint.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 при ошибке.
 *
 * Пишет прямо в *out через `ex (sp), hl` (обмен сохранённого out с
 * HL=час:мин после RST) — статический скретч не нужен.
 * `out->dow` не трогается (GET_D_T его не возвращает). */
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)
{
    /* Размер — через lseek-трюк. */
    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;

    /* Дата/время — через 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;
}
