Linux shell written in Odin.
package linux

import "core:c"
import "core:strings"
import "core:os"
import "core:fmt"

foreign import libc "system:c"

pid_t :: #type u32;
uid_t :: #type u32;
gid_t :: #type u32;

passwd :: struct {
	pw_name: cstring,
	pw_passwd: cstring,
	pw_uid: uid_t,
	pw_gid: gid_t,
	pw_gecos: cstring,
	pw_dir: cstring,
	pw_shell: cstring,
}

DIR :: opaque [0]byte;
ino_t :: distinct c.ulong;
off_t :: distinct c.long;

// NOTE: The only fields in the dirent structure that are mandated by POSIX.1 are d_name and d_ino.  The other fields are unstandardized, and not present on all systems.
// See Notes in `man 3 readdir` for more info about structure and size of structure.
dirent :: struct { // TODO: Make this a raw version and another struct that's easier to use.
	d_ino: ino_t, // Inode number of the file
	d_off: off_t,
	d_reclen: c.ushort, // Size in bytes of the returned record
	d_type: c.uchar, // File type

	d_name: [256]c.char, // Name of file, null-terminated, can exceede 256 chars (in which case d_reclen exceedes the size of this struct)
}

// -- termios stuff --
cc_t :: distinct c.uchar;
speed_t :: distinct c.uint;
tcflag_t :: distinct c.uint;

NCCS :: 32;
termios :: struct {
	c_iflag: tcflag_t,  // Input modes
	c_oflag: tcflag_t,  // Output modes
	c_cflag: tcflag_t,  // Control modes
	c_lflag: tcflag_t,  // Local modes
	c_line: cc_t,
	c_cc: [NCCS]cc_t,    // Special characters
	c_ispeed: speed_t,  // Input speed
	c_ospeed: speed_t   // Output speed
}

/* c_cc characters */
VINTR :: 0;
VQUIT :: 1;
VERASE :: 2;
VKILL :: 3;
VEOF :: 4;
VTIME :: 5;
VMIN :: 6;
VSWTC :: 7;
VSTART :: 8;
VSTOP :: 9;
VSUSP :: 10;
VEOL :: 11;
VREPRINT :: 12;
VDISCARD :: 13;
VWERASE :: 14;
VLNEXT :: 15;
VEOL2 :: 16;

/* c_iflag bits */
IGNBRK: tcflag_t : 0000001;
BRKINT: tcflag_t : 0000002;
IGNPAR: tcflag_t : 0000004;
PARMRK: tcflag_t : 0000010;
INPCK: tcflag_t : 0000020;
ISTRIP: tcflag_t : 0000040;
INLCR: tcflag_t : 0000100;
IGNCR: tcflag_t : 0000200;
ICRNL: tcflag_t : 0000400;
IUCLC: tcflag_t : 0001000;
IXON: tcflag_t : 0002000;
IXANY: tcflag_t : 0004000;
IXOFF: tcflag_t : 0010000;
IMAXBEL :: 0020000;
IUTF8 :: 0040000;

/* c_oflag bits */
OPOST :: 0000001;
OLCUC :: 0000002;
ONLCR :: 0000004;
OCRNL :: 0000010;
ONOCR :: 0000020;
ONLRET :: 0000040;
OFILL :: 0000100;
OFDEL :: 0000200;
/*#if defined __USE_MISC || defined __USE_XOPEN
# define NLDLY        0000400
# define   NL0        0000000
# define   NL1        0000400
# define CRDLY        0003000
# define   CR0        0000000
# define   CR1        0001000
# define   CR2        0002000
# define   CR3        0003000
# define TABDLY        0014000
# define   TAB0        0000000
# define   TAB1        0004000
# define   TAB2        0010000
# define   TAB3        0014000
# define BSDLY        0020000
# define   BS0        0000000
# define   BS1        0020000
# define FFDLY        0100000
# define   FF0        0000000
# define   FF1        0100000
#endif*/
VTDLY :: 0040000;
VT0 :: 0000000;
VT1 :: 0040000;
/*#ifdef __USE_MISC
# define XTABS        0014000
#endif*/

/* c_cflag bit meaning */
/*#ifdef __USE_MISC
# define CBAUD        0010017
#endif*/
B0 :: 0000000;                /* hang up */
B50 :: 0000001;
B75 :: 0000002;
B110 :: 0000003;
B134 :: 0000004;
B150 :: 0000005;
B200 :: 0000006;
B300 :: 0000007;
B600 :: 0000010;
B1200 :: 0000011;
B1800 :: 0000012;
B2400 :: 0000013;
B4800 :: 0000014;
B9600 :: 0000015;
B19200 :: 0000016;
B38400 :: 0000017;
// #ifdef __USE_MISC
// # define EXTA B19200
// # define EXTB B38400
// #endif
CSIZE :: 0000060;
CS5 :: 0000000;
CS6 :: 0000020;
CS7 :: 0000040;
CS8 :: 0000060;
CSTOPB :: 0000100;
CREAD :: 0000200;
PARENB :: 0000400;
PARODD :: 0001000;
HUPCL :: 0002000;
CLOCAL :: 0004000;
// #ifdef __USE_MISC
// # define CBAUDEX 0010000
// #endif
B57600 :: 0010001;
B115200 :: 0010002;
B230400 :: 0010003;
B460800 :: 0010004;
B500000 :: 0010005;
B576000 :: 0010006;
B921600 :: 0010007;
B1000000 :: 0010010;
B1152000 :: 0010011;
B1500000 :: 0010012;
B2000000 :: 0010013;
B2500000 :: 0010014;
B3000000 :: 0010015;
B3500000 :: 0010016;
B4000000 :: 0010017;
__MAX_BAUD :: B4000000;
// #ifdef __USE_MISC
// # define CIBAUD          002003600000                /* input baud rate (not used) */
// # define CMSPAR   010000000000                /* mark or space (stick) parity */
// # define CRTSCTS  020000000000                /* flow control */
// #endif

/* c_lflag bits */
ISIG :: 0000001;
ICANON: tcflag_t : 0000002;
// #if defined __USE_MISC || (defined __USE_XOPEN && !defined __USE_XOPEN2K)
// # define XCASE        0000004
// #endif
ECHO: tcflag_t : 0000010;
ECHOE :: 0000020;
ECHOK :: 0000040;
ECHONL :: 0000100;
NOFLSH :: 0000200;
TOSTOP :: 0000400;
/*#ifdef __USE_MISC
# define ECHOCTL 0001000
# define ECHOPRT 0002000
# define ECHOKE         0004000
# define FLUSHO         0010000
# define PENDIN         0040000
#endif*/
IEXTEN :: 0100000;
/*#ifdef __USE_MISC
# define EXTPROC 0200000
#endif*/

TCSANOW :: 0;
TCSADRAIN :: 1;
TCSAFLUSH :: 2;

// -- ioctl --
winsize :: struct {
	ws_row: c.ushort,
	ws_col: c.ushort,
	ws_xpixel: c.ushort,
	ws_ypixel: c.ushort,
}

TIOCGWINSZ :: 21523;

// wait & waitpid Options
WNOHANG :: 1;    // Return immediately if no child has exited
WUNTRACED :: 2;  // Also return if a child has stopped (but not traced via ptrace).
WCONTINUED :: 8; // Also return if a stopped child has been resumed by delivery of SIGCONT

WIFEXITED :: inline proc(status: int) -> bool {
	return (((status) & 0x7f) == 0);
}

WIFSIGNALED :: inline proc(status: int) -> bool {
	return (((byte) (((status) & 0x7f) + 1) >> 1) > 0);
}

// Signal Handling

sighandler_t :: #type proc "c" (signum: c.int);
SIG_IGN : uintptr : 1;
SIG_DFL : uintptr : 0;
SIG_ERR : uintptr : ~uintptr(0); // -1

/* ISO C99 signals.  */
SIGINT :: 2;        /* Interactive attention signal.  */
SIGILL :: 4;        /* Illegal instruction.  */
SIGABRT :: 6;        /* Abnormal termination.  */
SIGFPE :: 8;        /* Erroneous arithmetic operation.  */
SIGSEGV :: 11;        /* Invalid access to storage.  */
SIGTERM :: 15;        /* Termination request.  */
/* Historical signals specified by POSIX. */
SIGHUP :: 1;        /* Hangup.  */
SIGQUIT :: 3;        /* Quit.  */
SIGTRAP :: 5;        /* Trace/breakpoint trap.  */
SIGKILL :: 9;        /* Killed.  */
// SIGBUS :: 10;        /* Bus error.  */
// SIGSYS :: 12;        /* Bad system call.  */
SIGPIPE :: 13;        /* Broken pipe.  */
SIGALRM :: 14;        /* Alarm clock.  */
/* New(er) POSIX signals (1003.1-2008, 1003.1-2013).  */
// SIGURG :: 16;        /* Urgent data is available at a socket.  */
// SIGSTOP :: 17;        /* Stop, unblockable.  */
// SIGTSTP :: 18;        /* Keyboard stop.  */
// SIGCONT :: 19;        /* Continue.  */
SIGCHLD_ISO :: 20;        /* Child terminated or stopped.  */
SIGTTIN :: 21;        /* Background read from control terminal.  */
SIGTTOU :: 22;        /* Background write to control terminal.  */
SIGPOLL_ISO :: 23;        /* Pollable event occurred (System V).  */
SIGXCPU :: 24;        /* CPU time limit exceeded.  */
SIGXFSZ :: 25;        /* File size limit exceeded.  */
SIGVTALRM :: 26;        /* Virtual timer expired.  */
SIGPROF :: 27;        /* Profiling timer expired.  */
// SIGUSR1 :: 30;        /* User-defined signal 1.  */
// SIGUSR2 :: 31;        /* User-defined signal 2.  */
/* Nonstandard signals found in all modern POSIX systems
   (including both BSD and Linux).  */
SIGWINCH :: 28;        /* Window size change (4.3 BSD, Sun).  */
/* Archaic names for compatibility.  */
SIGIO :: SIGPOLL_ISO;        /* I/O now possible (4.2 BSD).  */
SIGIOT :: SIGABRT;        /* IOT instruction, abort() on a PDP-11.  */
SIGCLD :: SIGCHLD_ISO;        /* Old System V name */

// Signal Adjustment for Linux
SIGSTKFLT :: 16;        /* Stack fault (obsolete).  */
SIGPWR :: 30;        /* Power failure imminent.  */
SIGBUS :: 7;
SIGUSR1 :: 10;
SIGUSR2 :: 12;
SIGCHLD :: 17;
SIGCONT :: 18;
SIGSTOP :: 19;
SIGTSTP :: 20; /* terminal stop - Ctrl+Z */
SIGURG :: 23;
SIGPOLL :: 29;
SIGSYS :: 31;

foreign libc {
	@(link_name="getuid") getuid :: proc() -> uid_t ---;
	@(link_name="getpwnam") _unix_getpwnam :: proc(name: cstring) -> ^passwd ---;
	@(link_name="getpwuid") getpwuid :: proc(uid: uid_t) -> ^passwd ---;
	@(link_name="readlink") _unix_readlink :: proc(pathname: cstring, buf: cstring, bufsiz: c.size_t) -> c.ssize_t ---;

	@(link_name="opendir") _unix_opendir :: proc(name: cstring) -> ^DIR ---; // TODO: Returns ^DIR (which is defined as __dirstream in dirent.h)
	@(link_name="readdir") _unix_readdir :: proc(dirp: ^DIR) -> ^dirent ---;
	@(link_name="closedir") _unix_closedir :: proc(dirp: ^DIR) -> c.int ---;

	@(link_name="setenv") _unix_setenv :: proc(name: cstring, value: cstring, overwrite: c.int) -> c.int ---;
	@(link_name="unsetenv") _unix_unsetenv :: proc(name: cstring) -> c.int ---;
	@(link_name="secure_getenv") _unix_secure_getenv :: proc(name: cstring) -> cstring ---; // NOTE: GNU-specific

	@(link_name="tcgetattr") _unix_tcgetattr :: proc(fd: os.Handle, termios_p: ^termios) -> c.int ---;
	@(link_name="tcsetattr") _unix_tcsetattr :: proc(fd: os.Handle, optional_actions: c.int, termios_p: ^termios) -> c.int ---;

	@(link_name="ioctl") _unix_ioctl :: proc(fd: os.Handle, request: c.ulong, argp: rawptr) -> c.int ---;

	@(link_name="fork") fork :: proc() -> pid_t ---;
	@(link_name="execvp") _unix_execvp :: proc(file: cstring, argv: ^cstring) -> c.int ---;
	@(link_name="waitpid") _unix_waitpid :: proc(pid: pid_t, wstatus: ^c.int, options: c.int) -> pid_t ---;

	@(link_name="signal") _unix_signal :: proc(signum: c.int, handler: c.uintptr_t) -> c.uintptr_t ---;
}

getpwnam :: proc(name: string) -> ^passwd {
	cstr := strings.clone_to_cstring(name);
	defer delete(cstr);
	return _unix_getpwnam(cstr);
}

readlink :: proc(pathname: string, buf: []u8) -> (int, os.Errno) {
	cstr_pathname := strings.clone_to_cstring(pathname);
	defer delete(cstr_pathname);
	bytes_written := _unix_readlink(cstr_pathname, cstring(#no_bounds_check &buf[0]), c.size_t(len(buf)));
	if bytes_written == -1 {
		return -1, os.Errno(os.get_last_error());
	}
	return int(bytes_written), os.ERROR_NONE;
}

opendir :: proc(name: string) -> (^DIR, os.Errno) {
	cstr := strings.clone_to_cstring(name);
	defer delete(cstr);
	result := _unix_opendir(cstr);
	if result == nil {
		return nil, os.Errno(os.get_last_error());
	}
	return result, os.ERROR_NONE;
}

readdir :: proc(dirp: ^DIR) -> (^dirent, os.Errno) {
	previous := os.Errno(os.get_last_error());

	result := _unix_readdir(dirp);
	err := os.Errno(os.get_last_error());

	if result == nil && previous != err { // If nil and errno changed, err occured
		return nil, err;
	} else if result == nil { // If errno not changed, end of directory stream
		return nil, os.ERROR_NONE;
	}

	return result, os.ERROR_NONE;
}

closedir :: proc(dirp: ^DIR) -> os.Errno {
	result := _unix_closedir(dirp);
	if result == 0 {
		return os.ERROR_NONE;
	} else {
		return os.Errno(os.get_last_error());
	}
}

setenv :: proc(name: string, value: string, overwrite: bool) -> os.Errno {
	name_str := strings.clone_to_cstring(name);
	defer delete(name_str);
	value_str := strings.clone_to_cstring(value);
	defer delete(value_str);
	result := _unix_setenv(name_str, value_str, overwrite ? 1 : 0);
	if result == -1 {
		return os.Errno(os.get_last_error());
	}

	return os.ERROR_NONE;
}

unsetenv :: proc(name: string) -> os.Errno {
	name_str := strings.clone_to_cstring(name);
	defer delete(name_str);
	result := _unix_unsetenv(name_str);
	if result == -1 {
		return os.Errno(os.get_last_error());
	}

	return os.ERROR_NONE;
}

// NOTE: GNU-specific
secure_getenv :: proc(name: string) -> (string, bool) {
	path_str := strings.clone_to_cstring(name);
	defer delete(path_str);
	cstr := _unix_secure_getenv(path_str);
	if cstr == nil {
		return "", false;
	}
	return string(cstr), true;
}

tcgetattr :: proc(fd: os.Handle, termios_p: ^termios) -> os.Errno {
	result := _unix_tcgetattr(fd, termios_p);
	if result == -1 {
		return os.Errno(os.get_last_error());
	}

	return os.ERROR_NONE;
}

tcsetattr :: proc(fd: os.Handle, optional_actions: int, termios_p: ^termios) -> os.Errno {
	result := _unix_tcsetattr(fd, c.int(optional_actions), termios_p);
	if result == -1 {
		return os.Errno(os.get_last_error());
	}

	return os.ERROR_NONE;
}

ioctl :: proc(fd: os.Handle, request: u64, argp: rawptr) -> (int, os.Errno) {
	result := _unix_ioctl(fd, c.ulong(request), argp);
	if result == -1 {
		return -1, os.Errno(os.get_last_error());
	}

	return int(result), os.ERROR_NONE;
}

/*execvp :: proc(file: string, args: []string) -> os.Errno {
	file_str := strings.clone_to_cstring(file);
	defer delete(file_str);

	result := _unix_execvp(file_str, );
	if result == -1 {
		return os.Errno(os.get_last_error());
	}

	return os.ERROR_NONE;
}*/

waitpid :: proc(pid: pid_t, wstatus: ^int, options: int) -> pid_t {
	return _unix_waitpid(pid, cast(^c.int) wstatus, c.int(options));
}

signal_handler :: proc(signum: int, handler: sighandler_t) -> (uintptr, os.Errno) {
	result := _unix_signal(c.int(signum), c.uintptr_t(uintptr(rawptr(handler))));
	if c.uintptr_t(result) == c.uintptr_t(SIG_ERR) {
		return uintptr(result), os.Errno(os.get_last_error());
	}

	return uintptr(result), os.ERROR_NONE;
}

signal_int :: proc(signum: int, handler: uintptr) -> (uintptr, os.Errno) {
	result := _unix_signal(c.int(signum), c.uintptr_t(handler));
	if result == SIG_ERR {
		return uintptr(result), os.Errno(os.get_last_error());
	}

	return uintptr(result), os.ERROR_NONE;
}

signal :: proc{signal_handler, signal_int};

// TODO: Doesn't work on any BSDs or MacOS
// TODO: Look at 'realpath'
get_executable_path :: proc() -> string {
	pathname :: "/proc/self/exe";
	page_size := os.get_page_size();
	buf := make([dynamic]u8, page_size);

	// Credit for this loop technique: Tetralux
	for {
		bytes_written, error := readlink(pathname, buf[:]);
		if error == os.ERROR_NONE {
			resize(&buf, bytes_written);
			return string(buf[:]);
		}
		if error != os.ERANGE {
			return "";
		}
		resize(&buf, len(buf)+page_size);
	}
	unreachable();
}

get_path_separator :: proc() -> rune {
	return '/';
}