do not edit — generated by btf.
git.druid.rocksindexdruid520kaboomsrc/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;
}
powered by btf.