| git.druid.rocks | index | druid520 | kaboom | src/ | user/ | stdio.nsc |
src/user/stdio.nsc
/*
* minimal buffered stdio -- no FILE*, no fopen (kaboom's fd's are
* already just small integers, see fd.nsc), and no printf: nsc has no
* varargs at all (no va_list, no `...` parameters anywhere in the
* language), so a real variadic printf isn't something this toolchain
* can express. what IS worth having, and what every hand-rolled
* sys_write-per-line coreutil here has been missing: putchar/puts that
* actually buffer (one sys_write per flush instead of one per line),
* and signed/hex decimal conversion to go with lib.nsc's existing
* unsigned-only u64_to_dec.
*/
include "syscalls.nsh";
include "lib.nsh";
include "mem.nsh";
global i8 stdio_outbuf[256];
global u64 stdio_outlen;
global void
stdio_flush(void)
{
if(stdio_outlen > (u64)0)
{
sys_write(1, &stdio_outbuf[0], stdio_outlen);
stdio_outlen = (u64)0;
}
}
/* flushes on a newline, not just a full buffer -- so line-oriented
* output still appears promptly instead of sitting unflushed until
* either 256 bytes accumulate or the program happens to call
* stdio_flush itself (crt.s's _start has no atexit hook to do that
* automatically -- see its own note on why exiting is just returning
* -- so a program that writes less than a full line and never
* newlines has to flush explicitly). */
global void
putchar(u8 ch)
{
stdio_outbuf[stdio_outlen] = (i8)ch;
stdio_outlen = stdio_outlen + (u64)1;
if(stdio_outlen >= (u64)256 || ch == (u8)10)
{
stdio_flush();
}
}
global void
puts(ptr s)
{
u64 i;
u8 ch;
i = 0;
while(1)
{
ch = ptr_byte_at(s, i);
if(ch == (u8)0)
{
return;
}
putchar(ch);
i = i + (u64)1;
}
}
/* unbuffered, arbitrary-fd counterpart to puts -- for stderr, and for
* any literal string write that doesn't want stdout's buffering.
* lib.nsc's my_strlen does the length computation, so no call site
* needs to hand-count a literal's byte length (and get it wrong --
* this project has hit that exact off-by-one bug, repeatedly, in
* hand-counted sys_write calls) ever again. */
global void
fputs(i32 fd, ptr s)
{
sys_write(fd, s, my_strlen(s));
}
/* prints "<prefix>: permission denied\n" if the most recently failed
* syscall THIS process made really was a permission problem
* (sys_errno -- the userspace half of kaboom_errno/sys_doerror,
* idt.nsc/kfs.nsc), or "<prefix>: <generic_reason>\n" otherwise (not
* found, already exists, a directory that isn't empty, ...). klogs
* already gets the real reason on the kernel side; this is what lets
* a command's OWN user-visible message be equally honest instead of
* one fixed string regardless of why something actually failed --
* call it right after the failing syscall, before making another one
* (sys_errno reflects only the most recent call). */
global void
print_err(i32 fd, ptr prefix, ptr generic_reason)
{
fputs(fd, prefix);
fputs(fd, ": ");
if(sys_errno() == (i64)1)
{
fputs(fd, "permission denied\n");
}
else
{
fputs(fd, generic_reason);
fputs(fd, "\n");
}
}
/* signed counterpart to lib.nsc's u64_to_dec -- same digit-extraction
* shape, plus a leading '-' and a safe magnitude computation that
* never negates an i64 directly (negating i64::min overflows; folding
* to two's-complement magnitude in u64 space via ~val+1 doesn't). */
global u64
i64_to_dec(i64 val, ptr buf)
{
i8 tmp[20];
i32 i;
u64 mag;
u64 n;
u64 j;
u64 off;
off = 0;
if(val < (i64)0)
{
mem_byte_set(buf, 0, (u8)45); /* '-' */
off = 1;
mag = ~(u64)val + (u64)1;
}
else
{
mag = (u64)val;
}
i = 20;
if(mag == (u64)0)
{
i = i - 1;
tmp[i] = (i8)48;
}
while(mag > (u64)0)
{
i = i - 1;
tmp[i] = (i8)(48 + (i32)(mag % (u64)10));
mag = mag / (u64)10;
}
n = (u64)20 - (u64)i;
j = 0;
while(j < n)
{
mem_byte_set(buf, off + j, (u8)tmp[(u64)i + j]);
j = j + (u64)1;
}
return off + n;
}
/* lowercase hex, no "0x" prefix (callers wanting one can puts it
* themselves first, same "no adornment" convention u64_to_dec uses). */
global u64
u64_to_hex(u64 val, ptr buf)
{
i8 tmp[16];
i32 i;
u64 v;
u64 d;
u64 n;
u64 j;
i = 16;
v = val;
if(v == (u64)0)
{
i = i - 1;
tmp[i] = (i8)48;
}
while(v > (u64)0)
{
d = v % (u64)16;
i = i - 1;
if(d < (u64)10)
{
tmp[i] = (i8)(48 + (i32)d);
}
else
{
tmp[i] = (i8)(97 + (i32)(d - (u64)10));
}
v = v / (u64)16;
}
n = (u64)16 - (u64)i;
j = 0;
while(j < n)
{
mem_byte_set(buf, j, (u8)tmp[(u64)i + j]);
j = j + (u64)1;
}
return n;
}
/* prints a number straight to buffered stdout -- the "u64_to_dec into
* a local buffer, then sys_write that buffer" pattern info.nsc,
* date.nsc and ed.nsc all independently wrote out by hand before this
* existed, centralized once instead of duplicated three more times. */
global void
put_udec(u64 val)
{
i8 buf[20];
u64 len;
u64 i;
len = u64_to_dec(val, &buf[0]);
i = 0;
while(i < len)
{
putchar((u8)buf[i]);
i = i + (u64)1;
}
}
global void
put_idec(i64 val)
{
i8 buf[20];
u64 len;
u64 i;
len = i64_to_dec(val, &buf[0]);
i = 0;
while(i < len)
{
putchar((u8)buf[i]);
i = i + (u64)1;
}
}
/* reads an open file fd's ENTIRE content into one freshly sys_alloc'd
* buffer, sized to the real file via sys_fsize (the userspace side of
* the same "size the buffer to the actual file, never a fixed guess"
* rule fd.nsc's fd_buf_need follows kernel-side) -- no 63488-byte (or
* any other) cap, up to whatever the kernel itself could open.
* *len_out gets the byte count; the buffer has 8 spare bytes past it
* (fd_read's own word-sized stores can touch the last partial word)
* and is nul-terminated at len, so a caller parsing it as text (sh's
* run_script) always finds a terminator after the last line. returns
* (ptr)0 -- never a partial buffer -- if the size can't be read, the
* alloc fails, or fewer than the full size bytes come back, so a
* caller that goes on to destroy something on success (mv's sys_rm of
* the source) can trust success to mean "all of it". the fd is left
* open; closing it is the caller's job, same as after sys_read. */
global ptr
read_all(i32 fd, ptr len_out)
{
i64 size;
i64 got;
i64 n;
ptr buf;
size = sys_fsize(fd);
if(size < 0)
{
return (ptr)0;
}
buf = sys_alloc((u64)size + (u64)8);
if(buf == (ptr)0)
{
return (ptr)0;
}
got = 0;
while(got < size)
{
n = sys_read(fd, buf + (u64)got, (u64)(size - got));
if(n <= 0)
{
return (ptr)0;
}
got = got + n;
}
mem_byte_set(buf, (u64)size, (u8)0);
*len_out = size;
return buf;
}