//-----------------------------------------------------------------------------
// Copyright © 2001 - Philip Howard - All rights reserved, released under GPL
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//-----------------------------------------------------------------------------
// package	cdinit
// program	cdinit2 (cd initializer stage 2)
//
// description
//	This program is the stage 2 initializer for the cdinit package.  The
//	stage 1 program will have invoked this program in PID 1.
//
// action
//	1.  Symlink /lib -> /tmpfs/cdrom/lib (for ld-linux.so, libc.so)
//	2.  Run tar to extract /tmpfs/cdrom/${ARCH}/boot/root.tar into /tmpfs
//	3.  Pivot root: /tmpfs -> / -> /mnt
//	4.  Unmount /mnt to release the initrd
//	5.  Free ramdisk buffers (does not currently work due to kernel bug)
//	6.  Execute cdinit3 to release mappings on the CDROM this program has
//
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	phil@ipal.org
//-----------------------------------------------------------------------------

#include "cdinit.h"

//-----------------------------------------------------------------------------
// DISPLAY_PER_SEC is the number of times per second to display progress.
//-----------------------------------------------------------------------------
#define DISPLAY_PER_SEC 16
#define ROTOR_CHARS	"\\|/-"

//-----------------------------------------------------------------------------
// Define timespec struct to make parent sleep briefly while tar starts.
//-----------------------------------------------------------------------------
struct timespec		sleep_time	= { 10, 0 };

//-----------------------------------------------------------------------------
// Define the file to untar to populate the tmpfs.
//-----------------------------------------------------------------------------
static const char	tar_file	[] = "/tmpfs/cdrom/" CDINIT_ARCH_NAME "/boot/root.tar";

//-----------------------------------------------------------------------------
// Define argument lists for execve().
//-----------------------------------------------------------------------------
static char * const	tar_command		[] = {
    "/tmpfs/cdrom/" CDINIT_ARCH_NAME "/bin/tar",
    "xpf",
    "-",
    NULL };

static char *		cdinit3_command	[] = {
    "/sbin/cdinit3",
    NULL };

//-----------------------------------------------------------------------------
// Define an environment for execve().
//-----------------------------------------------------------------------------
static char *		environment		[] = {
    "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
    "HOME=/",
    "TERM=linux",
    NULL };

//-----------------------------------------------------------------------------
// Set up display string.
//-----------------------------------------------------------------------------
//					 1111111111222222222233333333334444444444555555555566666666667777777777
//				1234567890123456789012345678901234567890123456789012345678901234567890123456789
static char	display	[] = "\rloading tmpfs: -          - [  -.-%] of          -  |.........................|";

//-----------------------------------------------------------------------------
// Where to store trace info.
//-----------------------------------------------------------------------------
#ifdef CDINIT_TRACE
static const char *	trace	[4];
#endif

//-----------------------------------------------------------------------------
// Include functions.
//-----------------------------------------------------------------------------
#include "write_string.c"

#ifdef CDINIT_TRACE
#include "write_dec_slong.c"
#endif

//-----------------------------------------------------------------------------
// Function setup does setup to be done only once.
//-----------------------------------------------------------------------------
static
int
main1 ()
{
    struct stat			statbuf		;
    unsigned long		file_size	;
    ssize_t			len		;
    int				fd		;
    int				pid		;
    int				status		;
    int				pipe_fds	[2]		;
    char			cmdline		[1024]		;

    //---------------------------------
    // Show the kernel boot parameters.
    //---------------------------------
    trace2( "mkdir", "/proc" );
    if ( mkdir( "/proc", 0755 ) != 0 ) return 2;
    trace4( "umount"+1, "/proc"+1, "/proc", "/proc"+1 );
    if ( mount( "/proc"+1, "/proc", "/proc"+1, MS_MGC_VAL, 0 ) != 0 ) return 4;
    trace2( "open", "/proc/cmdline" );
    if ( ( fd = open( "/proc/cmdline", O_RDONLY, 0 ) ) < 0 ) return 2;
    trace2( "read", "/proc/cmdline" );
    len = read( fd, cmdline, sizeof (cmdline) );
    if ( len > 2 ) {
	-- len;
	if ( cmdline[len] == '\n' ) cmdline[len] = 0;
	write_string( 1, "boot parameters: " );
	write( 1, cmdline, len );
	write( 1, "\n", 1 );
    }
    close( fd );
    umount( "/proc" );
    rmdir( "/proc" );

    //----------------------------------------------------------
    // Set up to run tar to load tar file from CDROM into tmpfs.
    //----------------------------------------------------------
    trace3( "symlink", "/tmpfs/cdrom/" CDINIT_ARCH_NAME "/lib", "/lib" );
    if ( symlink( "/tmpfs/cdrom/" CDINIT_ARCH_NAME "/lib", "/lib" ) != 0 ) return 3;

    trace3( "open", tar_file, "O_RDONLY" );
    if ( ( fd = open( tar_file, O_RDONLY, 0 ) ) < 0 ) return 3;

    trace1( "fstat" );
    if ( fstat( fd, & statbuf ) != 0 ) return 2;
    file_size = (unsigned long) statbuf.st_size;

    signal( SIGPIPE, SIG_IGN );

    trace1( "pipe" );
    if ( pipe( pipe_fds ) < 0 ) return 1;

    trace4( "fork", tar_command[0], tar_command[1], tar_command[2] );
    pid = fork();
    if ( pid < 0 ) return 4;
    if ( pid == 0 ) {
	close( pipe_fds[1] );
	close( 0 );
	dup2( pipe_fds[0], 0 );
	close( pipe_fds[0] );
	fcntl( 0, F_SETFD, 0 );
	trace2( "chdir", "/tmpfs" );
	if ( chdir( "/tmpfs" ) < 0 ) return 2;
	trace1( "execve" );
	execve( tar_command[0], tar_command, environment );
	return 4;
    }
    close( pipe_fds[0] );

    //-----------------------------------------------------------
    // Copy the tar file to the pipe with rotor progress display.
    //-----------------------------------------------------------
    {
	char *		data_ptr	;
	char *		rotor_ptr	;
	char *		ptr		;
	size_t		total_len	;
	ssize_t		read_len	;
	ssize_t		write_len	;
	unsigned long	num		;
	unsigned long	new_time	;
	unsigned long	old_time	;
	struct timeval	full_time	;
	char		data_buf	[2048]	;


	//-- Fill in file size.
	num = file_size;
	ptr = display+50;
	do { * ptr = '0' + ( num % 10 ); } while ( -- ptr > display+41 && ( num /= 10 ) );
	do { * ptr = ' '; } while ( -- ptr > display+41 );

	write( 1, display, sizeof (display) - 1 );

	data_ptr = data_buf; // not needed except to avoid a warning at -O2.
	rotor_ptr = ROTOR_CHARS;
	read_len = 0;
	write_len = 0;
	total_len = 0;
	old_time = 0;

	for (;;) {

	    trace1( "gettimeofday" );
	    if ( gettimeofday( & full_time, NULL ) < 0 ) return 1;
	    new_time = full_time.tv_usec;
	    new_time /= (unsigned long) ( 1000000U / DISPLAY_PER_SEC );
	    new_time += full_time.tv_sec * DISPLAY_PER_SEC;

	    //-- Check for time to display.
	    if ( read_len < 0 || write_len < 0 || new_time != old_time ) {
		old_time = new_time;

		//-- Pick rotor.
		if ( ! * rotor_ptr ) rotor_ptr = ROTOR_CHARS;
		display[16] = * rotor_ptr ++;

		//-- Fill in current size.
		num = total_len;
		ptr = display+27;
		do { * ptr = '0' + ( num % 10U ); } while ( -- ptr > display+18 && ( num /= 10U ) );
		do { * ptr = ' '; } while ( -- ptr > display+18 );

		//-- Fill in percentage.
		num = (( total_len * 5U )) / (( file_size / 200U ));
		display[34] = '0' + num % 10U;
		num /= 10U;
		display[32] = '0' + num % 10U;
		num /= 10U;
		if ( num ) {
		    display[31] = '0' + num % 10U;
		    num /= 10U;
		    if ( num ) display[30] = '0' + num % 10U;
		}

		//-- Fill in progress bar.
		num = (( total_len * 5U )) / (( file_size / 5U ));
		ptr = display+54;
		while ( num -- ) * ptr ++ = '*';

		//-- Display as much of the progress string as has changed
		write( 1, display, ptr - display );

		//-- Check for time to leave.
		if ( read_len < 0 || write_len < 0 ) break;
	    }

	    //-- If buffer is empty, read in more.
	    if ( read_len == 0 ) {
		read_len = read( fd, data_buf, sizeof (data_buf) );
		if ( read_len == 0 ) read_len = -2;
		if ( read_len < 0 ) continue;
		data_ptr = data_buf;
	    }

	    //-- If buffer has data, write it.
	    if ( read_len > 0 ) {
		write_len = write( pipe_fds[1], data_ptr, read_len );
		if ( write_len < 0 ) continue;
		data_ptr += write_len;
		read_len -= write_len;
		total_len += write_len;
	    }
	}
	if ( read_len == -1 ) write_string( 1, "READ ERROR\n" );
	if ( write_len == -1 ) write_string( 1, "WRITE ERROR\n" );
    }
    close( fd );
    close( pipe_fds[1] );
    trace1( "waitpid" );
    if ( waitpid( pid, & status, 0 ) < 0 ) return 4;
    write( 1, "\n", 1 );

    //---------------------------------------------
    // Switch over to the tmpfs filesystem as root.
    //---------------------------------------------
    errno = 0;
    trace3( "pivot_root", "/tmpfs", "/tmpfs/mnt" );
    if ( pivot_root( "/tmpfs", "/tmpfs/mnt" ) != 0 ) return 3;
    trace2( "chdir", "/" );
    if ( chdir( "/" ) != 0 ) return 2;

    //-----------------------------------------------------
    // Close and reopen the console via the inode in tmpfs.
    // This will not be needed after migrating to devfs.
    //-----------------------------------------------------
    close( 0 );
    close( 1 );
    close( 2 );
    open( "/dev/console", O_RDWR, 0 );
    dup( 0 );
    dup( 0 );

    //-----------------------------------
    // Unmount the original root ramdisk.
    //-----------------------------------
    trace2( "umount", "/tmpfs/mnt"+6 );
    if ( umount( "/tmpfs/mnt"+6 ) != 0 ) return 2;

    //--------------------------------------------------------
    // Since stage 2 is running mapped from the mounted CDROM,
    // run stage 3 from tmpfs to allow unmounting the CDROM.
    //--------------------------------------------------------
    execve( cdinit3_command[0], cdinit3_command, environment );
    trace1( "executing cdinit3" );
    return 1;
}

//-----------------------------------------------------------------------------
// Function _start is really the main function.
//-----------------------------------------------------------------------------
void
_start ()
{
    int		rc		;

    errno = 0;

#if 0
    write_string( 1,
		  "\r"
#ifdef CDINIT_COLOR
		  "\033[33;1m"
#endif
		  "cdinit v" CDINIT_VERSION
		  ", "
		  "Copyright (C) 2001, Philip Howard, All rights reserved, See GPL"
#ifdef CDINIT_COLOR
		  "\033[0m"
#endif
		  "\n" );
#endif

    rc = main1();
#ifdef CDINIT_TRACE
    if ( rc != 0 ) {
	int i;
	write_string( 1, "Error " );
	write_dec_slong( 1, (signed long) ( errno ) );
	write_string( 1, " doing:" );
	for ( i = 0; i < rc; ++ i ) {
	    write_string( 1, " " );
	    write_string( 1, trace[i] );
	}
	write_string( 1, "\n" );
    }
#endif

    write_string( 1, "halting ... \n\n\n\n\n\n" );
    reboot( LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_HALT, NULL );
    for (;;);
}

