/*
 * bsp - BrainDead Script Processor for initrd
 *
 * this falls under GNU GPL v2.
 *
 * written in y2k,y2k++,y2k++++ by Michal Moskal <malekith@pld.org.pl>
 *
 * $Id: main.c,v 1.27 2003/08/07 19:34:30 malekith Exp $
 *
 */

#undef __OPTIMIZE__
#include "setup.h"	
#include <sys/types.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <stdlib.h>
#include <asm/errno.h>

int init_module(void*,size_t,char*);
void sprintf(char*,char*,...);

#define VERSION "0.3.1"
#define TEXT_VERSION "memento"

#define GREEN "\e[32m"
#define BOLD "\e[1m"
#define NOB "\e[21m"
#define CYAN "\e[36m"
#define RESET "\e[0m"
#define GRAY "\e[2m"
#define BLUE "\e[34m"
#define RED "\e[31m"

extern int main(int argc, char **argv);
char custom_path[MAX_NAME];
int insmod_exec(int argc, char **argv);
int pivot_root(char *new_root, char *old_root);
pid_t xfork();

void puts(char *s)
{
	char *p;
	for (p = s; *p; p++);
	write(2, s, p - s);
}

char buf[MAX_SIZE];

void split(char *s, char **argv)
{
	int delim;
	char *a, *dst;
	
	while (*s) {
		while (*s == ' ' || *s == '\t')
			s++;

		dst = a = s;
		while (*s && *s != ' ' && *s != '\t') {
			if (*s == '\'' || *s == '"') {
				delim = *s++;
				while (*s && *s != delim)
					*dst++ = *s++;
				if (*s == delim)
					s++;
			} else {
				*dst++ = *s++;
			}
		}

		if (*dst) {
			if (s == dst)
				s++;
			*dst++ = 0;
		}

		*argv++ = a;
	}

	*argv = 0;
}

int do_echo(int argc, char **argv)
{
	(void)argc;
	argv++;
	puts(CYAN BOLD);
	while (*argv) {
		puts(*argv);
		puts(" ");
		argv++;
	}
	puts(RESET "\n");
	return 0;
}

int do_append(int argc, char **argv)
{
	int fd;
	
	if (argc < 3)
		puts(RED BOLD "USAGE: append FILENAME STRING...\n" RESET);
	else {
		argv++;
		fd = open(*argv, O_WRONLY|O_APPEND|O_CREAT, 0600);
		if (fd == -1) {
			puts(RED BOLD "cannot open target file: ");
			puts(*argv);
			puts("\n" RESET);
		} else {
			argv++;
			while (*argv) {
				write(fd, *argv, strlen(*argv));
				if (argv[1])
					write(fd, " ", 1);
				else
					write(fd, "\n", 1);
				argv++;
			}
			close(fd);
			return 0;
		}
	}
	
	return 1;
}
int mknod(char*patchname,int mode,int dev);
int mkdir(char*patchname,int mode);

#define	S_IFCHR	0020000	/* Character device.  */
#define	S_IFBLK	0060000	/* Block device.  */
int do_mknod(int argc, char **argv)
{
	int err;
	
	if (argc != 5 || 
	    (argv[2][0] != 'b' && argv[2][0] != 'c'))
		puts(RED BOLD "USAGE: mknod NAME b|c MAJOR MINOR\n" RESET);
	else {
		err = mknod(argv[1], 
		            (argv[2][0] == 'b' ? S_IFBLK : S_IFCHR) | 0600, 
		            (atoi(argv[3]) << 8) | atoi(argv[4]));
		if (err == -1)
			puts(RED BOLD "mknod failed\n" RESET);
		else
			return 0;
	}
	
	return 1;
}

int do_rm(int argc, char **argv)
{
	int err;
	
	if (argc != 2)
		puts(RED BOLD "USAGE: rm FILENAME\n" RESET);
	else {
		err = unlink(argv[1]);
		if (errno == -1 && errno == EISDIR)
			err = rmdir(argv[1]);

		if (err == -1 && errno != ENOENT)
			puts(RED BOLD "unlink failed\n" RESET);
		else
			return 0;
	}
	
	return 1;
}

int do_mkdir(int argc, char **argv)
{
	int err;
	
	if (argc != 2)
		puts(RED BOLD "USAGE: mkdir DIRNAME\n" RESET);
	else {
		err = mkdir(argv[1], 0700);

		if (err == -1)
			puts(RED BOLD "mkdir failed\n" RESET);
		else
			return 0;
	}
	
	return 1;
}

int do_pivot_root(int argc, char **argv)
{
	if (argc != 3) {
		puts(RED BOLD "USAGE: pivot_root NEW_ROOT PUT_OLD\n" RESET);
	} else {
		chdir(argv[1]);
		if (pivot_root(argv[1], argv[2])) {
			puts(RED "pivot root new=" BOLD); 
			puts(argv[1]); 
			puts(NOB " old=" BOLD);
			puts(argv[2]); 
			puts(NOB " FAILED.\n" RESET);
		} else {
			/* just in case... : */
			chdir("/");
			return 0;
		}
	}
	return 1;
}

int do_mount(int argc, char **argv)
{
	int ro = 0;
	
	if (argv[1] && strcmp(argv[1], "-r") == 0)
		argv++, argc--, ro++;
		
	if (argc != 4) {
		puts(RED BOLD "USAGE: mount [-r] DEV DIR FS\n" RESET);
	} else {
		if (mount(argv[1], argv[2], argv[3], ro ? 1 : 0, NULL)) {
			puts(RED "mounting " BOLD); 
			puts(argv[1]); 
			puts(NOB " on " BOLD);
			puts(argv[2]); 
			puts(NOB " FAILED.\n" RESET);
		} else
			return 0;
	}
	return 1;
}

/* From linux/fs.h */
//#define BLKFLSBUF  _IO(0x12,97)	/* flush buffer cache */

int do_freeramdisk(int argc, char **argv) 
{
	int fd;

	if (argc != 2) {
		puts(RED BOLD "USAGE: freeramdisk DEVICE\n" RESET);
	} else {
		fd = open(argv[1], O_RDWR);
		if (fd == -1) {
			puts(RED "opening " BOLD); 
			puts(argv[1]); 
			puts(NOB " FAILED.\n" RESET);
		} else {
			if (ioctl(fd, BLKFLSBUF) < 0) {
				puts(RED "ioctl on " BOLD); 
				puts(argv[1]); 
				puts(NOB " FAILED.\n" RESET);
				close(fd);
			} else {
				close(fd);
				return 0;
			}
		}
	}

	return 1;
}

int do_close_console(int argc, char **argv)
{
	(void)argc;
	(void)argv;
	close(0);
	close(1);
	close(2);
	return 0;
}

int open_console()
{
	open("/dev/console", O_RDWR);
	open("/dev/console", O_RDWR);
	return open("/dev/console", O_RDWR) < 0;
}

int do_reopen_console(int argc, char **argv)
{
	do_close_console(argc, argv);
	return open_console();
}

int do_umount(int argc, char **argv)
{
	if (argc != 2) {
		puts(RED BOLD "USAGE: umount DEV|DIR\n" RESET);
	} else {
		if (umount(argv[1])) {
			puts(RED "unmounting " BOLD); 
			puts(argv[1]); 
			puts(NOB " FAILED.\n" RESET);
		} else {
			return 0;
		}
	}
	return 1;
}

int do_usleep(int argc, char **argv)
{
	if (argc != 2) {
		puts(RED BOLD "USAGE: usleep SEC\n" RESET);
	} else {
		usleep(atoi(argv[1]));
	}
	return 0;
}

int do_raid_oops(int argc, char **argv)
{
	puts(RED BOLD "Raid-less version!\n" RESET);
	return 1;
}

int do_sleep(int argc, char **argv)
{
	if (argc != 2) {
		puts(RED BOLD "USAGE: sleep SEC\n" RESET);
	} else {
		usleep(atoi(argv[1]) * 1000000);
	}
	return 0;
}

int do_cd(int argc, char **argv)
{
	if (argc != 2) {
		puts(RED BOLD "USAGE: cd DIR\n" RESET);
	} else {
		if (chdir(argv[1])) {
			puts(RED "cd " BOLD); 
			puts(argv[1]); 
			puts(NOB " FAILED.\n" RESET);
		} else
			return 0;
	}
	return 1;
}

/* Read entire named file to a buffer in memory. Assume file is no longer
 * then max_len - 1. Return block and it's size or error condition.
 *   -1 for some unspecified error
 *   1  for file-too-long
 *   0  OK
 */
static int read_file_lim(char *filename, char **pres, int *plen, int max_len)
{
	char *buf;
	int len = 0, n;
	int fd;

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		return -1;

	buf = malloc(max_len);
	if (buf == 0)
		return -1;
	
	while (len < max_len) {
		n = read(fd, buf + len, max_len - len);
		if (n == 0)
			break;
		if (n < 0) {
			free(buf);
			close(fd);
			return -1;
		}
		len += n;
	}

	if (len >= max_len) {
		free(buf);
		close(fd);
		return 1;
	}

	close(fd);

	*plen = len;
	*pres = buf;

	return 0;
}

/* Like above but no length limit, and print error message and exit in 
 * case of error. */
static int read_file(char *filename, char **pres, int *plen)
{
	int len = 1024;

	for (;;) {
		switch (read_file_lim(filename, pres, plen, len)) {
		case 0:
			return 0;
		case 1:
			len *= 4;
			break;
		default:
			puts(RED "can't read " BOLD); 
			puts(filename); 
			puts(NOB "!\n" RESET);
			exit(1);
		}
	}
}

int do_insmod_ng(int argc, char *argv[])
{
	int fd;
	long int ret;
	int len;
	char *filename, *options = "";
	char *buf;
	char err_buf[200];

	filename = argv[1];
	if (!filename) {
		puts(RED BOLD "USAGE: insmod_ng /path/module.o [OPTIONS]\n" RESET);
		exit(1);
	}

	if (argv[2])
		options = argv[2];

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		puts(RED "can't open " BOLD); 
		puts(filename); 
		puts(NOB "!\n" RESET);
		exit(1);
	}
	close(fd);

	read_file(filename, &buf, &len);

	ret = init_module(buf, len, options);
	if (ret != 0) {
		char *err;
		switch (errno) {
		case ENOEXEC:
			err = "Invalid module format";
			break;
		case ENOENT:
			err = "Unknown symbol in module";
			break;
		case ESRCH:
			err = "Module has wrong symbol version";
			break;
		case EINVAL:
			err = "Invalid parameters";
			break;
		default:
			err = "unknown error";
			break;
		}

		sprintf(err_buf, RED "error loading: %s: %s (errno = %d, ret = %d)\n" RESET,
		        filename, err, (int)errno, (int)ret);
		puts(err_buf); 
		exit(1);
	}
	
	exit(0);
}

char concat_buf[MAX_NAME];
char *concat(const char *s1, const char *s2)
{
	char *p;

	p = concat_buf;
	while (*s1)
		*p++ = *s1++;
	*p++ = '/';
	while (*s2)
		*p++ = *s2++;
	*p = 0;

	return concat_buf;
}

void execve_helper(char **argv)
{
	char *env[] = INIT_ENV;
	char *p[] = SEARCH_PATH;
	int i; 

	if (**argv == '.' || **argv == '/')
		execve(argv[0], argv, env);
	else 
		for (i = 0; p[i]; i++) 
			execve(concat(p[i], argv[0]), argv, env);
}

int do_exec(int argc, char **argv)
{
	if (argc < 2) {
		puts(RED BOLD "USAGE: exec prog...\n" RESET);
	} else {
		execve_helper(++argv);
		puts(RED "exec " BOLD); 
		puts(argv[0]); 
		puts(NOB " FAILED.\n" RESET);
	}
	return 1;
}

int do_modpath(int argc, char **argv)
{
	if (argc < 2)
		return 1;
	strcpy(custom_path, argv[1]);
	return 0;
}

struct bltin {
	const char *name;
	int fork;
	int (*exec)(int argc, char **argv);
};

int raidstart_main (int argc, char *argv[]);

struct bltin bltins[] = {
	{ "echo", 0, do_echo },
	{ "insmod", 1, insmod_exec },
	{ "modpath", 0, do_modpath },
	{ "sleep", 0, do_sleep },
	{ "usleep", 0, do_usleep },
	{ "raidstart", 1, raidstart_main },
	{ "mount", 0, do_mount },
	{ "umount", 0, do_umount },
	{ "pivot_root", 0, do_pivot_root },
	{ "append", 0, do_append },
	{ "rm", 0, do_rm },
	{ "mknod", 0, do_mknod },
	{ "cd", 0, do_cd },
	{ "exec", 0, do_exec },
	{ "mkdir", 0, do_mkdir },
	{ "reopen_console", 0, do_reopen_console },
	{ "close_console", 0, do_close_console },
	{ "freeramdisk", 0, do_freeramdisk },
	{ "insmod_ng", 1, do_insmod_ng },
	{ 0, 0, 0 }
};

int can_read(const char *path)
{
	int fd;

	fd = open(path, 0, 0);
	if (fd == -1)
		return 0;
	close(fd);
	return 1;
}

int insmod_exec(int argc, char **argv)
{
	int i;
	char *mod_search[] = MOD_SEARCH_PATH;

	if (can_read(argv[1]))
		return main(argc, argv);
		
 	if (*custom_path && can_read(concat(custom_path,argv[1])))
 		goto ok;
 	
 	for (i = 0; mod_search[i]; i++)
 		if (can_read(concat(mod_search[i], argv[1]))) 
 			goto ok;
 	
 	puts(RED BOLD "cannot find module ");
 	puts(argv[1]);
 	puts(RESET "\n");
	return -1;

ok:
	argv[1] = concat_buf;
	return main(argc, argv);
}

int find_bultin(char **argv)
{
	int i;

	for (i = 0; bltins[i].name; i++)
		if (strcmp(bltins[i].name, argv[0])==0)
			break;
	if (bltins[i].name == 0)
		i = -1;
	return i;
}

int try_exec(char **argv)
{
	int pid, s;
	int i; 

	i = find_bultin(argv);

	if (i == -1 || bltins[i].fork) {
		pid = xfork();
		if (pid == -1) {
			puts(RED BOLD "fork() failed\n" RESET);
			exit(2);
		}

		if (pid) {
			wait4(pid, &s, 0, 0);
			return s / 256;
		}
	}

	if (i != -1) {
		pid = 0;
		while (argv[pid])
			pid++;
		pid = bltins[i].exec(pid, argv);
		if (bltins[i].fork)
			exit(pid);
		else
			return pid;
	}

	execve_helper(argv);
	
	exit(127);
}

void exec(char *s)
{
	char *argv[MAX_ARGV];
	int r;
	
	split(s, argv);

	r = try_exec(argv);

	if (r) {
		puts(RED BOLD "execution of ");
		puts(argv[0]);
		puts(" FAILED\n" RESET);
	}
}

void execute_line(char *s)
{
	while (*s == ' ' || *s == '\t')
		s++;
	if (*s == '#')
		return;
		
	if (*s == '@')
		s++;
	else if (*s == 0)
		return;
	else if (*s == '>') {
		s++;
		while (*s == ' ' || *s == '\t')
			s++;
		puts(CYAN BOLD);
		puts(s);
		puts(RESET "\n");
		return;
	} else {
		puts(CYAN BOLD "+ " NOB);
		puts(s);
		puts(RESET "\n");
	}

	exec(s);
}

void parse(char *s)
{
	char *e;
	
	while (*s) {
		for (e = s; *e; e++)
			if (*e == '\n')
				break;
		if (*e)
			*e++ = 0;

		execute_line(s);
		s = e;
	}
}

void xstart()
{
	int fd = -1;
	int i;
	char *p[] = STARTUP_PATH;
	
	puts(
	     GREEN BOLD "B" NOB "raindamaged " BOLD "S" NOB "cript " 
	     BOLD "P" NOB "rocessor for initrd v"
	     BOLD VERSION NOB " - [" BOLD TEXT_VERSION NOB "]\n"
	     /* and a bit advertise :) */
	     "powered by " BOLD "PLD Linux " NOB 
	     "(http://"BOLD"www.pld.org.pl"NOB")\n"
	     RESET);


	for (i = 0; p[i]; i++) {
	/*
		puts(GREEN "Trying " BOLD);
		puts(p[i]);
		puts(NOB " ... ");
		*/
		fd = open(p[i], 0, 0);
		
		if (fd == -1) {
		/*	puts("nope.\n" RESET); */
		} else {
/*			puts(BOLD "yap " NOB "!\n" RESET);*/
			break;
		}
	}
	
	if (fd == -1) {
		puts(RED BOLD "opening startup file failed\n" RESET);
		exit(1);
	}

	read(fd, buf, sizeof(buf));
	close(fd);

	puts("\n");
	
	parse(buf);
	
	puts("\n");
	
	puts(RESET GREEN BOLD "M" NOB "ay the " BOLD "penguin" 
	     NOB " be with you.\n" RESET);
	exit(0);
}
