#!/usr/bin/perl

use strict;
use warnings;

use POSIX qw( uname );
use Data::Dumper;

use constant VERSION => '0.8';

sub build {
	my $kernel = shift;
	my $bins = shift;
	my $mods = shift;
	my $libs = shift;
	my $tmpdir = '/tmp/initrd-' . randstr();

	mkdir( $tmpdir, 0700 ) || die( "mkdir(): $tmpdir: $!" );

	foreach ( qw( bin dev etc etc/modprobe.d etc/udev etc/udev/rules.d
	lib proc run sys ) ) {
		mkdir( "$tmpdir/$_", 0755 ) || die( "mkdir(): $tmpdir/$_: $!" );
		}

	if ( -f '/etc/hostid' ) {
		if ( system( "cp -p /etc/hostid $tmpdir/etc" ) ) {
			die( "Failed copying hostid" );
			}
		}

	symlink( 'lib', "$tmpdir/lib64" ) ||
		die( "symlynk(): $tmpdir/lib64: $!" );
	symlink( '/proc/mounts', "$tmpdir/etc/mtab" ) ||
		die( "symlynk(): $tmpdir/etc/mtab: $!" );
	symlink( '/bin', "$tmpdir/sbin" ) ||
		die( "symlynk(): $tmpdir/sbin: $!" );

	if ( system( "cp -p /usr/share/mkinitramfs/init $tmpdir" ) ) {
		die( "Failed copying init" );
		}

	foreach ( @$bins ) {
		if ( system( "cp -p $_ $tmpdir/bin" ) ) {
			die( "Failed copying $_\n" );
			}
		}

	foreach ( @$mods ) {
		my $file = modinfo( $_, $kernel->{'version'} ) || next;
		my @patharr = split( '/', $file );
		my $path;

		shift( @patharr );
		pop( @patharr );

		$path = join( '/', @patharr );

		if ( ! -d "$tmpdir/$path" ) {
			mkdirp( "$tmpdir/$path", 0755 );
			}

		if ( system( "cp -p $file $tmpdir/$path" ) ) {
			die( "Failed copying $file\n" );
			}
		}

	foreach ( @$libs ) {
		if ( system( "cp -p $_ $tmpdir/lib" ) ) {
			die( "Failed copying $_\n" );
			}
		}

	if ( system( "cp -a /lib/firmware $tmpdir/lib/firmware" ) ) {
		die( "Failed copying firmware\n" );
		}

	if ( system( "cp -a /etc/modprobe.d $tmpdir/etc/modprobe.d" ) ) {
		die( "Failed copying data from modprobe.d\n" );
		}
	if ( system( "cp -a /etc/udev $tmpdir/etc/udev" ) ) {
		die( "Failed copying udev data\n" );
		}
	if ( system( "cp -a /lib/udev $tmpdir/lib/udev" ) ) {
		die( "Failed copying udev lib data\n" );
		}

	if ( system( "mknod -m 640 $tmpdir/dev/console c 5 1" ) ) {
		die( "Failed to create console device node\n" );
		}
	if ( system( "mknod -m 640 $tmpdir/dev/null c 1 3" ) ) {
		die( "Failed to create null device node\n" );
		}

	if ( system( "cp /lib/modules/$kernel->{'version'}/"
	. "modules.builtin /lib/modules/$kernel->{'version'}/modules.order"
	. " $tmpdir/lib/modules/$kernel->{'version'}" ) ) {
		die( "Failed copying module data\n" );
		}
	if ( system( "depmod -b $tmpdir $kernel->{'version'}" ) ) {
		die( "Failed to execute depmod\n" );
		}

	print "Building cpio image\n";

	if ( system( "cd $tmpdir && find . | cpio -o -H newc --quiet |"
	. " gzip -9 > $kernel->{'initrd'}" ) ) {
		die( "Failed building cpio\n" );
		}
	}

sub getkern {
	my $dir = '/boot';
	my $kernels = {};

	opendir( DIR, $dir ) || die( "opendir(): $dir: $!" );

	foreach ( sort( readdir( DIR ) ) ) {
		if ( $_ =~ /^vmlinuz-(\S+)/ ) {
			$kernels->{$1}{'version'} = $1;
			$kernels->{$1}{'path'} = "$dir/$_";
			$kernels->{$1}{'initrd'} = "$dir/initrd.img-$1";
			}
		}

	return( $kernels );
	}

sub ldd {
	my $file = shift;
	my $libs = [];

	open( LDD, "ldd -v $file|" );

	while ( <LDD> ) {
		if ( $_ =~ /(\S+)\s+=>\s+(\S+)/ ) {
			if ( -e $1 ) {
				push( @$libs, $1 );
				}

			push( @$libs, $2 );
			}
		}

	close( LDD );

	return( $libs );
	}

sub mkdirp{
	( my $dir = shift ) =~ s/\/^//;
	my $mode = shift;
	( my $parent = $dir ) =~ s/\/[^\/]+$//;

	if ( -d $dir ){
		return;
		}

	mkdirp( $parent, $mode );

	mkdir( $dir, $mode ) || die( "mkdir(): $dir: $!" );
	}

sub modinfo {
	my $module = shift;
	my $kernver = shift;
	my $file;

	open( INFO, "modinfo -k $kernver -F filename $module 2>/dev/null|" ) ||
		die( "open(): modinfo: $!" );
	$file = <INFO>;
	close( INFO );

	if ( $file ) {
		chomp( $file );
		}

	return( $file );
	}

sub randstr {
	my $num = shift || 8;
	my @chars = ('a'..'z','A'..'Z',0..9);
	my $str = '';

	for ( my $i = 0; $i < $num; $i++ ) {
		$str .= $chars[rand( $#chars )];
		}

	return( $str );
	}

sub readlines {
	my $dir = '/usr/share/mkinitramfs/' . shift;
	my $lines = [];

	opendir( DIR, $dir ) || die( "opendir(): $dir: $!" );

	while ( readdir( DIR ) ) {
		if ( ! -f "$dir/$_" ) {
			next;
			}

		open( FILE, "<$dir/$_" ) || die( "open(): $dir/$_: $!" );

		while ( my $line = <FILE> ) {
			chomp( $line );

			push( @$lines, $line );
			}

		close( FILE );
		}

	close( DIR );

	return( $lines );
	}

for ( my $i = 0; $i <= $#ARGV; $i++ ) {
	if ( $ARGV[$i] eq '-v' || $ARGV[$i] eq '--version' ) {
		print VERSION . "\n";

		exit 0;
		}
	}

my $arg = $ARGV[0] || ( uname() )[2];
my $kernels = getkern();
my $bins = readlines( 'bins' );
my $mods = readlines( 'mods' );
my $libs = [];
my $builds = [];

foreach my $bin ( @$bins ) {
	my $ldd = ldd( $bin );

	foreach my $lib ( @$ldd ) {
		if ( ! grep( $_ eq $lib, @$libs ) ) {
			push( @$libs, $lib );
			}
		}
	}

if ( $arg eq 'all' ) {
	foreach my $kernver ( keys( %$kernels ) ) {
		push( @$builds, $kernels->{$kernver} );
		}
	}
elsif ( ! $kernels->{$arg} ) {
	print STDERR "Unable to locate kernel version $arg\n";

	exit 1;
	}
else {
	push( @$builds, $kernels->{$arg} );
	}

foreach my $kernel ( @$builds ) {
	print "Generating $kernel->{'initrd'}\n";
	build( $kernel, $bins, $mods, $libs );
	print "Done\n";
	}
