do not edit — generated by btf.
git.druid.rocksindexdruid520kaboomsrc/user/sh.nsc

src/user/sh.nsc


include "syscalls.nsh";
include "lib.nsh";
include "string.nsh";
include "mem.nsh";
include "stdio.nsh";
 
/* minimalized csh+ash: one builtin set (cd/pwd/exit), everything else
 * exec'd via kfs's flat-ish namespace + the cwd model kfs_cd/kfs_pwd
 * already track kernel-side. no quoting, no escaping, no pipes/redirs,
 * no job control -- splits on bare spaces only, same "ship the real
 * minimal version" pattern as every other program here. */
void
do_pwd(void)
{
	i8 buf[256];
	u64 len;
 
	len = sys_pwd(&buf[0]);
	sys_write(1, &buf[0], len); /* dynamic content, not a literal -- stays sys_write */
	putchar((u8)10);
	stdio_flush();
}
 
/* splits line (null-terminated, in place -- either the interactive
 * input buffer below or one line of a running script) on bare spaces
 * and runs it exactly like a typed command: the builtins (cd/pwd/
 * exit), or sys_exec otherwise. shared by both the interactive loop
 * and run_script so a script behaves identically to typing the same
 * lines by hand -- there's no separate "script dialect" here at all.
 * returns 0 if this line was "exit" (the caller should stop), 1
 * otherwise. */
i32
run_line(ptr line)
{
	ptr words[16];
	i32 nwords;
	u64 i;
	i64 result;
 
	nwords = 0;
	i = 0;
	while(ptr_byte_at(line, i) != (u8)0)
	{
		while(ptr_byte_at(line, i) == (u8)32)
		{
			i = i + (u64)1;
		}
		if(ptr_byte_at(line, i) != (u8)0)
		{
			if(nwords < 16)
			{
				words[nwords] = line + i;
				nwords = nwords + 1;
			}
			while(ptr_byte_at(line, i) != (u8)32 && ptr_byte_at(line, i) != (u8)0)
			{
				i = i + (u64)1;
			}
			if(ptr_byte_at(line, i) == (u8)32)
			{
				mem_byte_set(line, i, (u8)0);
				i = i + (u64)1;
			}
		}
	}
 
	if(nwords == 0)
	{
		return 1; /* empty line: nothing to do */
	}
	if(strcmp(words[0], "exit") == 0)
	{
		return 0;
	}
	if(strcmp(words[0], "cd") == 0)
	{
		if(nwords >= 2)
		{
			if(sys_cd(words[1], strlen(words[1])) == 0)
			{
				print_err(2, "cd", "no such directory");
			}
		}
		else
		{
			sys_cd("/", (u64)1);
		}
		return 1;
	}
	if(strcmp(words[0], "pwd") == 0)
	{
		do_pwd();
		return 1;
	}
 
	result = sys_exec(words[0], strlen(words[0]), (i64)nwords, (ptr)&words[0]);
	if(result == (i64)(-1))
	{
		/* NOT print_err/sys_errno here, deliberately: exec's own -1 is
		 * already documented as ambiguous (idt.nsc's own comment on
		 * syscall 7) -- a real exec failure and a child program that
		 * legitimately returned -1 as its own exit code are
		 * indistinguishable from this return value alone, so
		 * sys_errno's value at this point can't be trusted to mean
		 * "why sys_exec itself failed" either (a child that ran fine
		 * and made its OWN syscalls would have left sys_errno at
		 * whatever ITS last syscall set it to, not sys_exec's). */
		fputs(2, "sh: command not found\n");
	}
	return 1;
}
 
/* runs a script file one command per line -- blank lines and lines
 * starting with '#' (the shebang line itself included, if present)
 * are skipped, everything else runs through run_line exactly as if it
 * had been typed interactively. no pipes, no redirection, no control
 * flow -- "super simple, just commands," which is genuinely the whole
 * feature. this is what a shebang script actually runs under: the
 * kernel's own sys_exec (exec.nsc) notices a file starting with "#!"
 * isn't a real elf and re-execs whatever interpreter the line names
 * (in practice, always sh, kaboom's only one) with the script's path
 * as argv[1] -- which is exactly the argc>=2 case main() below checks
 * for. typing "sh somescript" directly at the prompt reaches the same
 * place a different way: "sh" is a perfectly ordinary, real elf, so
 * sys_exec loads and runs it like any other program (into a fresh
 * frame under sh's own window, since this sh is still live there --
 * see exec.nsc's sys_exec), and IT sees argc>=2. */
void
run_script(ptr path, u64 path_len)
{
	ptr buf;
	i64 n;
	i32 fd;
	u64 i;
	u64 line_start;
	u8 ch;
 
	fd = sys_open(path, path_len);
	if(fd < 0)
	{
		print_err(2, "sh", "script not found");
		return;
	}
	/* read_all: the whole script regardless of size, nul-terminated
	 * after the last byte (the trailing-line case below relies on
	 * that). */
	buf = read_all(fd, &n);
	sys_close(fd);
	if(buf == (ptr)0)
	{
		print_err(2, "sh", "script read failed");
		return;
	}
 
	i = 0;
	line_start = 0;
	while((i64)i < n)
	{
		ch = ptr_byte_at(buf, i);
		if(ch == (u8)10)
		{
			mem_byte_set(buf, i, (u8)0);
			if(ptr_byte_at(buf, line_start) != (u8)35) /* '#' */
			{
				if(run_line(buf + line_start) == 0)
				{
					return; /* "exit" */
				}
			}
			line_start = i + (u64)1;
		}
		i = i + (u64)1;
	}
	/* a trailing line with no final newline still runs. */
	if(line_start < (u64)n && ptr_byte_at(buf, line_start) != (u8)35)
	{
		run_line(buf + line_start);
	}
}
 
global i32
main(i32 argc, ptr argv)
{
	i8 line[256];
	i64 linelen;
	ptr path;
 
	if(argc >= 2)
	{
		/* invoked with a script path (see run_script's own note on the
		 * two ways this happens) -- run it to completion and exit,
		 * same as a real "sh script.sh" would, instead of ever
		 * entering the interactive loop below. */
		path = argv_get(argv, 1);
		run_script(path, strlen(path));
		return 0;
	}
 
	sys_vga_clear(); /* vga and serial both, see idt.nsc's syscall 16 -- a
	                  * clean screen for the interactive prompt, not for
	                  * script mode (which already returned above). */
	while(1)
	{
		fputs(1, "$ ");
 
		/* at most 255 bytes, leaving line[255] for the terminator.
		 * sys_read(0) only ever stops short of its limit at a newline,
		 * so a full 255 bytes with no newline means the line is longer
		 * than the buffer: the rest is read and thrown away up to the
		 * real newline, and the whole line is rejected. before, that
		 * rest stayed queued and ran as its own separate command on
		 * the next read, and the terminator went to line[256], one
		 * byte past the buffer. running a truncated prefix instead
		 * wouldn't be safe either ("rm longname" cut short can name a
		 * different file). */
		linelen = sys_read(0, &line[0], (u64)255);
		if(linelen <= (i64)0)
		{
			line[0] = 0;
		}
		else if(ptr_byte_at(&line[0], (u64)(linelen - (i64)1)) == (u8)10)
		{
			line[linelen - (i64)1] = 0;
		}
		else
		{
			while(linelen == (i64)255 && ptr_byte_at(&line[0], (u64)254) != (u8)10)
			{
				linelen = sys_read(0, &line[0], (u64)255);
				if(linelen <= (i64)0)
				{
					linelen = 0; /* eof/error: nothing left to discard */
				}
				else if(ptr_byte_at(&line[0], (u64)(linelen - (i64)1)) == (u8)10)
				{
					linelen = 0; /* reached the real end of the line */
				}
			}
			line[0] = 0;
			fputs(2, "sh: line too long, ignored\n");
		}
 
		if(run_line(&line[0]) == 0)
		{
			return 0;
		}
	}
	return 0;
}
powered by btf.