/*
 * Guillaume Cottenceau (gc@mandrakesoft.com)
 *
 * Copyright 2000 MandrakeSoft
 *
 * This software may be freely redistributed under the terms of the GNU
 * public license.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * Portions from Erik Troan (ewt@redhat.com)
 *
 * Copyright 1996 Red Hat Software 
 *
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>
#include <bzlib.h>
#include <sys/mount.h>
#include <sys/poll.h>
#include "stage1.h"
#include "log.h"
#include "mount.h"
#include "frontend.h"
#include "automatic.h"
#include <errno.h>
#include <linux/loop.h>
#include "tools.h"
#include "modules.h"


static struct param_elem params[50];
static int param_number = 0;
static int splashfd = -1;
static int splashcount = 0;
static int splashstep = 0;

void process_cmdline(void)
{
	char buf[512];
	int fd, size, i;
	
	log_message("opening /proc/cmdline... ");
	
	if ((fd = open("/proc/cmdline", O_RDONLY)) == -1)
		fatal_error("could not open /proc/cmdline");
	
	size = read(fd, buf, sizeof(buf));
	buf[size-1] = '\0'; // -1 to eat the \n
	close(fd);

	log_message("\t%s", buf);

	i = 0;
	while (buf[i] != '\0') {
		char *name, *value = NULL;
		int j = i;
		while (buf[i] != ' ' && buf[i] != '=' && buf[i] != '\0')
			i++;
		if (i == j) {
			i++;
			continue;
		}
		name = memdup(&buf[j], i-j + 1);
		name[i-j] = '\0';

		if (buf[i] == '=') {
			int k = i+1;
			i++;
			while (buf[i] != ' ' && buf[i] != '\0')
				i++;
			value = memdup(&buf[k], i-k + 1);
			value[i-k] = '\0';
		}

		params[param_number].name = name;
		params[param_number].value = value;
		param_number++;
		if (!strcmp(name, "expert")) set_param(MODE_EXPERT);
		if (!strcmp(name, "changedisk")) set_param(MODE_CHANGEDISK);
		if (!strcmp(name, "updatemodules")) set_param(MODE_UPDATEMODULES);
		if (!strcmp(name, "rescue")) set_param(MODE_RESCUE);
		if (!strcmp(name, "live")) set_param(MODE_LIVE);
		if (!strcmp(name, "stagename")) set_param(MODE_STAGENAME);
		if (!strcmp(name, "lowmem")) set_param(MODE_LOWMEM);
		if (!strcmp(name, "automatic")) {
			set_param(MODE_AUTOMATIC);
			grab_automatic_params(value);
		}
		if (buf[i] == '\0')
			break;
		i++;
	}
	
	log_message("\tgot %d args", param_number);
}


int stage1_mode = 0;

int get_param(int i)
{
#ifdef SPAWN_INTERACTIVE
	static int fd = 0;
	char buf[5000];
	char * ptr;
	int nb;

	if (fd <= 0) {
		fd = open(interactive_fifo, O_RDONLY);
		if (fd == -1)
			return (stage1_mode & i);
		fcntl(fd, F_SETFL, O_NONBLOCK);
	}

	if (fd > 0) {
		if ((nb = read(fd, buf, sizeof(buf))) > 0) {
			buf[nb] = '\0';
			ptr = buf;
			while ((ptr = strstr(ptr, "+ "))) {
				if (!strncmp(ptr+2, "expert", 6)) set_param(MODE_EXPERT);
				if (!strncmp(ptr+2, "rescue", 6)) set_param(MODE_RESCUE);
				ptr++;
			}
			ptr = buf;
			while ((ptr = strstr(ptr, "- "))) {
				if (!strncmp(ptr+2, "expert", 6)) unset_param(MODE_EXPERT);
				if (!strncmp(ptr+2, "rescue", 6)) unset_param(MODE_RESCUE);
				ptr++;
			}
		}
	}
#endif

	return (stage1_mode & i);
}

char * get_param_valued(char *param_name)
{
	int i;
	for (i = 0; i < param_number ; i++)
		if (!strcmp(params[i].name, param_name))
			return params[i].value;

	return NULL;
}

void set_param_valued(char *param_name, char *param_value)
{
	params[param_number].name = param_name;
	params[param_number].value = param_value;
	param_number++;
}

void set_param(int i)
{
	stage1_mode |= i;
	if (i == MODE_RESCUE) {
		set_param_valued("stagename", "rescue");
		set_param(MODE_STAGENAME);
	}
}

void unset_param(int i)
{
	stage1_mode &= ~i;
}

// warning, many things rely on the fact that:
// - when failing it returns 0
// - it stops on first non-digit char
int charstar_to_int(char * s)
{
	int number = 0;
	while (*s && isdigit(*s)) {
		number = (number * 10) + (*s - '0');
		s++;
	}
	return number;
}

int total_memory(void)
{
	int value;
	struct stat statr;
	if (stat("/proc/kcore", &statr))
		return 0;

	/* drakx powered: use /proc/kcore and rounds every 4 Mbytes */
	value = 4 * ((int)((float)statr.st_size / 1024 / 1024 / 4 + 0.5));
	log_message("Total Memory: %d Mbytes", value);

	return value;
}


int ramdisk_possible(void)
{
	if (total_memory() > (IS_RESCUE ? MEM_LIMIT_RESCUE : MEM_LIMIT_RAMDISK))
		return 1;
	else {
		log_message("warning, ramdisk is not possible due to low mem!");
		return 0;
	}
}


static void save_stuff_for_rescue(void)
{
	void save_this_file(char * file) {
		char buf[5000];
		int fd_r, fd_w, i;
		char location[100];

		if ((fd_r = open(file, O_RDONLY)) < 0) {
			log_message("can't open %s for read", file);
			return;
		}
		strcpy(location, STAGE2_LOCATION);
		strcat(location, file);
		if ((fd_w = open(location, O_WRONLY)) < 0) {
			log_message("can't open %s for write", location);
			close(fd_r);
			return;
		}
		if ((i = read(fd_r, buf, sizeof(buf))) <= 0) {
			log_message("can't read from %s", file);
			close(fd_r); close(fd_w);
			return;
		}
		if (write(fd_w, buf, i) != i)
			log_message("can't write %d bytes to %s", i, location);
		close(fd_r); close(fd_w);
		log_message("saved file %s for rescue (%d bytes)", file, i);
	}
	save_this_file("/etc/resolv.conf");
}


enum return_type load_ramdisk_fd(int source_fd, int size)
{
	char * ramdisk = "/dev/ram3"; /* warning, verify that this file exists in the initrd, and that root=/dev/ram3 is actually passed to the kernel at boot time */
	int ram_fd;
	char buffer[32768];
	char * wait_msg = "Loading program into memory...";
	int bytes_read = 0;
	int actually;
	int seems_ok = 0;

	ram_fd = open(ramdisk, O_WRONLY);
	if (ram_fd == -1) {
		log_perror(ramdisk);
		stg1_error_message("Could not open ramdisk device file.");
		return RETURN_ERROR;
	}
	
	init_progression(wait_msg, size);

	while ((actually = read(source_fd, buffer, sizeof(buffer))) > 0) {
		seems_ok = 1;
		if (write(ram_fd, buffer, actually) != actually) {
			log_perror("writing ramdisk");
			remove_wait_message();
			return RETURN_ERROR;
		}
		update_progression((int)(bytes_read += actually));
	}

	if (!seems_ok) {
		log_message("reading compressed ramdisk: %s", strerror(errno));
		close(ram_fd);
		remove_wait_message();
		stg1_error_message("Could not load second stage ramdisk. "
				   "This is probably an hardware error while reading the data. "
				   "(this may be caused by a hardware failure or a Linux kernel bug)");
		return RETURN_ERROR;
	}

	end_progression();

	close(ram_fd);

	if (do_losetup(LIVE_DEVICE, ramdisk) == -1)
	    fatal_error("could not setup loopback for loaded second stage");

	if (my_mount(LIVE_DEVICE, STAGE2_LOCATION, STAGE2FS, 0))
		return RETURN_ERROR;

	set_param(MODE_RAMDISK);

	if (IS_RESCUE) {
		save_stuff_for_rescue();
		if (umount(STAGE2_LOCATION)) {
			log_perror(ramdisk);
			return RETURN_ERROR;
		}
		return RETURN_OK; /* fucksike, I lost several hours wondering why the kernel won't see the rescue if it is alreay mounted */
	}

	return RETURN_OK;
}
int prepare_progress()
{
    char readbuffer[4096];
    int size;

    if (splashfd > 0) return(0);

	splashfd = open(PROCSPLASH,O_RDWR);
    if (splashfd < 0) {
        log_message("Error open /proc/splash\n");
        return(-1);
    }
	size = read(splashfd, readbuffer, sizeof(readbuffer));
	if (strstr(readbuffer,"silent") && strstr(readbuffer,": on")) {
		log_message("bootsplash detected and progress enabled");
	} else  {
		close(splashfd);
		splashfd = -1;
	}
    if (splashcount == 0 ) {
	    char * splash_param = get_param_valued("splashcount");
	    if (!splash_param) {
			splashcount = -1;
			return(0);
	    }
	    if (sscanf(splash_param,"%d",&splashcount) != 1) {
			splashcount = -1;
			return(0);
		}
	}
	return(0);
}

void close_progress()
{
    char s[16];
    if (splashfd < 0)
        return;
    close(splashfd);
    snprintf(s, sizeof(s), "%i", splashcount);
    add_to_env("SPLASHCOUNT", s);
    snprintf(s, sizeof(s), "%i", splashstep);
    add_to_env("SPLASHSTEP", s);
    return;
}

int set_splash(char * mode)
{
    if (splashfd < 0)
        return 0; 
    return(write(splashfd, mode, strlen(mode)));
}

int update_splash()
{
    int progress = 0;
    char data[32];
    if (splashcount <= 0)
		return 0;
    splashstep++;
    progress = (65534 * splashstep ) / splashcount;
    log_message("boostplash progress: %i",progress);
    sprintf(data,"show %i\n",progress);
    return write(splashfd,data,strlen(data));
}

char * get_ramdisk_realname(void)
{
	char img_name[500];
	char * stg2_name = get_param_valued("stagename");
	char * begin_img = RAMDISK_LOCATION;

	if (!stg2_name)
		stg2_name = "altinst";

	strcpy(img_name, begin_img);
	strcat(img_name, stg2_name);

	return strdup(img_name);
}

char * get_ramdisk_path(const char *mount_path)
{
    char img_name[500];
    char * st2_path = get_ramdisk_realname();

	/* FIXME */
	strcpy(img_name, mount_path ? mount_path : IMAGE_LOCATION);
    strcat(img_name, st2_path);
    return strdup(img_name);
}

enum return_type load_ramdisk(char *mount_path)
{
	int st2_fd;
	struct stat statr;
	char img_name[500];

	strcpy(img_name, mount_path ? mount_path : IMAGE_LOCATION);
	strcat(img_name, get_ramdisk_realname());

	log_message("trying to load %s as a ramdisk", img_name);

	st2_fd = open(img_name, O_RDONLY); /* to be able to see the progression */

	if (st2_fd == -1) {
		log_message("open ramdisk file (%s) failed", img_name);
		stg1_error_message("Could not open compressed ramdisk file (%s).", img_name);
		return RETURN_ERROR;
	}

	if (stat(img_name, &statr))
		return RETURN_ERROR;
	else
		return load_ramdisk_fd(st2_fd, statr.st_size);
}

/* pixel's */
void * memdup(void *src, size_t size)
{
	void * r;
	r = malloc(size);
	if(!r)
		perror("malloc");
	memcpy(r, src, size);
	return r;
}


static char ** my_env = NULL;
static int env_size = 0;

void handle_env(char ** env)
{
	char ** ptr = env;
	while (ptr && *ptr) {
		ptr++;
		env_size++;
	}
	my_env = malloc(sizeof(char *) * 100);
	if(!my_env)
		perror("malloc");
	memcpy(my_env, env, sizeof(char *) * (env_size+1));
}

char ** grab_env(void) {
	return my_env;
}

void add_to_env(char * name, char * value)
{
	char tmp[500];
	sprintf(tmp, "%s=%s", name, value);
	my_env[env_size] = strdup(tmp);
	env_size++;
	my_env[env_size] = NULL;
}

int pass_env(int fd)
{
	char ** ptr = my_env;
	char *s;
	int i = 0;

	while (ptr && *ptr) {
		s = *ptr;
		while(*s++);
		i = write(fd, *ptr, s - *ptr);
		ptr++;
	}
	return i;
}

char ** list_directory(char * direct)
{
	char * tmp[50000]; /* in /dev there can be many many files.. */
	int i = 0;
	struct dirent *ep;
	DIR *dp = opendir(direct);
	while (dp && (ep = readdir(dp))) {
		if (strcmp(ep->d_name, ".") && strcmp(ep->d_name, "..")) {
			tmp[i] = strdup(ep->d_name);
			i++;
		}
	}
	if (dp)
		closedir(dp);
	tmp[i] = NULL;
	return memdup(tmp, sizeof(char*) * (i+1));
}


int string_array_length(char ** a)
{
	int i = 0;
	if (!a)
		return -1;
	while (a && *a) {
		a++;
		i++;
	}
	return i;
}

int do_losetup(char * device, char * target)
{
	int loopfd, targfd;
	struct loop_info loopInfo;

	my_insmod("loop", ANY_DRIVER_TYPE, NULL);

	loopfd = open( device, O_RDONLY );
	if ( loopfd < 0 )
        {
		log_message( "losetup: error opening %s: %s", device, strerror(errno) );
		return -1;
	}

	targfd = open( target, O_RDONLY );
	if ( targfd < 0 )
	{
		log_message( "losetup: error opening %s: %s", target, strerror(errno) );
		close( loopfd );
		return -1;
	}

	if ( ioctl(loopfd, LOOP_SET_FD, targfd) < 0 )
	{
		log_message( "losetup: error setting up loopback device: %s", strerror(errno) );
		close( loopfd );
		close( targfd );
		return -1;
	}

	memset(&loopInfo, 0, sizeof(loopInfo));
	strcpy(loopInfo.lo_name, target);

	if ( ioctl(loopfd, LOOP_SET_STATUS, &loopInfo) < 0 )
		log_message( "losetup: error setting up loopback device: %s", strerror(errno) );

	close( loopfd );
	close( targfd );
	return 0;
}