ADR 0077 — Console.flush(), Directory.space(path), and what diskio needs: IoError.StorageFull, File.openAppend, File.modified, File.setPermissions
- Status: Accepted by the owner, 2026-11-06 ("While we wait, fix CeeSafe gap you found"). Raised by the CosmoStore port (
CeeSafeApps/cosmostore/CEESAFE_GAPS.mdG24 and G25). - Scope: type-system.md §26.3 and §26.10, lib/std
host.cee, the hosted runtime, the checker'sConsolemembers, the interpreter, guide/builtins.md, the corpus.
1. Context
- G25. Standard output is buffered by the C library when it is not a terminal. A program killed with SIGKILL loses what it wrote last, and CeeSafe has no way to flush except writing to standard error, which flushes standard output first. CosmoStore's soak test lost every line of its killed runs until it moved them to standard error.
- G24. A storage engine must stop writing before the volume is full. CeeSafe cannot ask a volume how much room it has (
statvfs,GetDiskFreeSpaceExW), so CosmoStore's ADR-0006 takes a configured capacity instead.
2. Decision
Console.flush()writes what standard output holds buffered and returnsvoid.- It is hosted only: in freestanding code it is N0293, as
Process.captureis. - It is lib/std's
HostServices.flush, over the runtime'sceesafe_host_console_flush(fflush(stdout)). The checker routes it as it routesFile.kind. - Standard error is unbuffered and needs no flush.
AsyncConsolewrites to the descriptor directly, so it needs none either. - The interpreter writes its buffered output.
- It is hosted only: in freestanding code it is N0293, as
Directory.space(path), in lib/std, returnsResult<VolumeSpace, IoError>.VolumeSpace { total, free, available }gives the volume's bytes for the file system that holdspath:totalis its size;freeis what is unused;availableis what an unprivileged process may use. It is at mostfree, since root may have a reserve.
On POSIX it is
statvfs, with fragment size ×f_blocks,f_bfreeandf_bavail. On Windows it isGetDiskFreeSpaceExA(available, total, free). A missing path isNotFound. The interpreter has no volume of its own to describe and answersIoError.Unsupported, so that a run stays reproducible.
2a. Amendment (2026-11-06): what diskio needs
The owner then asked for the mail server's diskio package (CeeSafeLibs) to leave C. Its session said that diskio "stays in C because CeeSafe lacks a portable errno accessor and packages can't pick sources per OS". Neither is needed once lib/std covers diskio's calls, because lib/std is portable already. What lib/std still lacked:
IoError.StorageFull, "No space left on device", appended afterHostNotFoundso earlier variants keep their tags. It coversENOSPC,EDQUOTwhere the system has it, and Windows'ERROR_DISK_FULLandERROR_HANDLE_DISK_FULL. diskio'sFileError.NoSpaceneeded a portable errno for this.- Source change: a
matchthat names the first twenty variants and has no_no longer coversIoError(N0256), as with ADR 0044 and ADR 0060.
- Source change: a
File.openAppend(path): write-only, created if missing (mode 0666 & ~umask), every write at the end (O_APPEND;_O_APPENDon Windows). This is mode 5 ofceesafe_file_open. The interpreter's model writes at the file's end each time.File.modified(path):Result<UnixTime, IoError>, the last modification time with nanoseconds where the system keeps them (stat;_stat64on Windows).File.setPermissions(path, mode):chmodof the low 12 bits. On Windows it sets the read-only attribute when the owner may not write, and does nothing else. It is how a mail store keeps its files 0640 and its directories 0750 under any umask. It is called on the new, still empty file, so the file never holds data others could read. A process-wide umask setter was rejected: diskio's calls run onAsync.blockingworker threads, and the umask would race.- The interpreter answers
Unsupportedfor 5 and 6, since the stage-0 compiler's library can do neither.
Tested: a 2 MB disk image filled with writeAt reports IoError.StorageFull, and Directory.space then says the volume is nearly full. A file given 0o640 shows mode 640 (stat). Appends from two openings land in order.
3. Tests
- run-pass
console-flush(output written before a flush) andvolume-space(available <= free <= total, a missing path isNotFound). - A check by hand that a flushed line survives SIGKILL with standard output redirected.