#!/bin/bash
#
# mk-images
#
# Copyright (C) 2007  Red Hat, Inc.  All rights reserved.
#
# 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, see <http://www.gnu.org/licenses/>.
#

LANG=C

PATH=$PATH:/sbin:/usr/sbin
IMAGEUUID=$(date +%Y%m%d%H%M).$(uname -i)
TMPDIR=${TMPDIR:-/tmp}

usage () {
    echo "usage: mk-images <pkgsrc> <toplevel> <template> <imgdir> <buildarch> <productname> <version>"
    exit 0
}

DEBUG=""
BUILDARCH=`uname -m`
BOOTISO="boot.iso"
ISBETA="false"

while [ $# -gt 0 ]; do
    case $1 in
        --debug)
            DEBUG="--debug"
            shift
        ;;
        --noiso)
            BOOTISO=""
            shift
        ;;
        --isbeta)
            ISBETA="true"
            shift
        ;;
        --arch)
            BUILDARCH=$2
            shift; shift
        ;;
        --imgdir)
            IMGPATH=$2
            shift; shift
        ;;
        --product)
            PRODUCT=$2
            shift; shift
        ;;
        --version)
            VERSION=$2
            shift; shift
        ;;
        --bugurl)
            BUGURL=$2
            shift; shift
        ;;
        --output)
            TOPDESTPATH=$2
            shift; shift
        ;;
        --nogr)
            echo "*** DeprecationWarning: ignoring --nogr option." >&2
            shift
        ;;
        --mindir)
            echo "*** DeprecationWarning: ignoring --mindir option." >&2
            shift; shift
        ;;
        --stg2dir)
            echo "*** DeprecationWarning: please use --imgdir instead of --stg2dir." >&2
            shift; shift
        ;;
        *)
            yumconf=$1
            shift
        ;;
    esac
done

if [ -z "$TOPDESTPATH" -o -z "$IMGPATH" -o -z "$PRODUCT" -o -z "$VERSION" ]; then usage; fi

TOPDIR=$(echo $0 | sed "s,/[^/]*$,,")
if [ $TOPDIR = $0 ]; then
    $TOPDIR="."
fi
TOPDIR=$(cd $TOPDIR; pwd)

# modules that are needed.  this is the generic "needed for every arch" stuff
COMMONMODS="fat vfat nfs sunrpc lockd floppy cramfs loop edd pcspkr squashfs ipv6 8021q virtio_pci netconsole"
UMSMODS="ums-jumpshot ums-datafab ums-freecom ums-usbat ums-sddr55 ums-onetouch ums-alauda ums-karma ums-sddr09 ums-cypress"
USBMODS="$UMSMODS ohci-hcd uhci-hcd ehci-hcd usbhid mousedev usb-storage sd_mod sr_mod ub appletouch bcm5974"
FIREWIREMODS="ohci1394 sbp2 fw-ohci fw-sbp2 firewire-sbp2 firewire-ohci"
SDMODS="mmc-block sdhci sdhci-pci"
IDEMODS="ide-cd ide-cd_mod"
SCSIMODS="sr_mod sg st sd_mod scsi_mod iscsi_tcp iscsi_ibft scsi_wait_scan cxgb3i bnx2i be2iscsi"
FSMODS="fat msdos vfat ext2 ext3 ext4 reiserfs jfs xfs gfs2 cifs fuse btrfs hfsplus"
LVMMODS="dm-mod dm-zero dm-snapshot dm-mirror dm-multipath dm-round-robin dm-crypt"
RAIDMODS="raid0 raid1 raid5 raid6 raid456 raid10 linear"
CRYPTOMODS="sha256_generic cbc xts lrw aes_generic crypto_blkcipher crc32c ecb arc4"
PCMCIASOCKMODS="yenta_socket i82365 tcic pcmcia"
INITRDMODS="$USBMODS $FIREWIREMODS $IDEMODS $SCSIMODS $FSMODS $LVMMODS $RAIDMODS $CRYPTOMODS $COMMONMODS $PCMCIASOCKMODS $SDMODS =scsi =net =drm"

. $(dirname $0)/buildinstall.functions

# Set, verify, and create paths
IMAGEPATH=$TOPDESTPATH/images
FULLMODPATH=$TMPDIR/instimagemods.$$
FINALFULLMODPATH=$IMGPATH/modules
INSTIMGPATH=$TOPDESTPATH/images
KERNELBASE=$TMPDIR/updboot.kernel.$$

KERNELNAME=vmlinuz
if [ "$BUILDARCH" = "ia64" ]; then
    KERNELDIR="/boot/efi/EFI/redhat"
else
    KERNELDIR="/boot"
fi

if [ "$BUILDARCH" = "sparc64" ]; then
    BASEARCH=sparc
elif [ "$BUILDARCH" = "ppc64" ]; then
    BASEARCH=ppc
else
    BASEARCH=$BUILDARCH
fi

# explicit block size setting for some arches (FIXME: we compose
# ppc64-ish trees as ppc, so we have to set the "wrong" block size)
if [ "$BUILDARCH" = "sparc64" ]; then
    CRAMBS="--blocksize 8192"
elif [ "$BUILDARCH" = "sparc" ]; then
    CRAMBS="--blocksize 4096"
else
    CRAMBS=""
fi

if [ "$BUILDARCH" = "x86_64" -o "$BUILDARCH" = "s390x" -o "$BUILDARCH" = "ppc64" ]; then
    LIBDIR=lib64
else
    LIBDIR=lib
fi

rm -rf $IMAGEPATH
rm -rf $FULLMODPATH
rm -rf $FINALFULLMODPATH
rm -rf $KERNELBASE
mkdir -p $IMAGEPATH
mkdir -p $FULLMODPATH
mkdir -p $FINALFULLMODPATH
mkdir -p $KERNELBASE
mkdir -p $INSTIMGPATH

# Stuff that we need
TRIMPCIIDS=$IMGPATH/usr/libexec/anaconda/trimpciids
GETKEYMAPS=$IMGPATH/usr/libexec/anaconda/getkeymaps
LIBEXECBINDIR=$IMGPATH/usr/libexec/anaconda
ADDRSIZE=$IMGPATH/usr/$LIBDIR/anaconda/addrsize
MKS390CDBOOT=$IMGPATH/usr/$LIBDIR/anaconda/mk-s390-cdboot
GENMODINFO=$IMGPATH/usr/libexec/anaconda/genmodinfo
SCREENFONT=$IMGPATH/usr/share/anaconda/screenfont-${BASEARCH}.gz
MODLIST=$IMGPATH/usr/libexec/anaconda/modlist
MODINFO=$TMPDIR/modinfo-$BUILDARCH.$$
LOADERBINDIR=$IMGPATH/usr/$LIBDIR/anaconda
BOOTDISKDIR=$IMGPATH/usr/share/anaconda/boot
LANGTABLE=$IMGPATH/usr/share/anaconda/lang-table
PCIIDS=$IMGPATH/usr/share/hwdata/pci.ids
XDRIVERS=$IMGPATH/usr/share/hwdata/videoaliases
XDRIVERDESCS=$IMGPATH/usr/share/hwdata/videodrivers

REQUIREMENTS="$TRIMPCIIDS $PCIIDS $XDRIVERDESCS $GENMODINFO
      $LANGTABLE $GETKEYMAPS"

dieLater=
for n in $REQUIREMENTS; do
    if [ ! -f $n ]; then
        echo "$n doesn't exist"
        dieLater=1
    fi
done

for n in $BOOTDISKDIR; do
    if [ ! -d $n ]; then
        echo "$n doesn't exist"
        dieLater=1
    fi
done

if [ -n "$dieLater" ]; then exit 1; fi

if [ "$BUILDARCH" != "s390" -a "$BUILDARCH" != "s390x" ]; then
    # go ahead and create the keymaps so we only have to do it once
    if [ -f $IMGPATH/usr/share/anaconda/keymaps-override-$BASEARCH ]; then
        echo "Found keymap override, using it"
        cp $IMGPATH/usr/share/anaconda/keymaps-override-$BASEARCH $IMGPATH/etc/keymaps.gz
    else
        echo "Running: $GETKEYMAPS $BUILDARCH $KEYMAPS $IMGPATH"
        $GETKEYMAPS $BUILDARCH $IMGPATH/etc/keymaps.gz $IMGPATH
        if [ $? != 0 ]; then
            echo "Unable to create keymaps and thus can't create initrd."
            exit 1
        fi
    fi
fi

findPackage() {
    name=$1

    pkg=$(repoquery --qf "%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}" -c $yumconf --archlist=$KERNELARCH $name.$KERNELARCH)
    if [ -n "$pkg" ]; then
        echo $pkg
        return
    fi
    echo "cannot find package $name" >&2
}

rundepmod () {
    where=$1

    $FAKEARCH /sbin/depmod -a -F $KERNELROOT/boot/System.map-$version \
            -b $where $version
}

# This loops to make sure it resolves dependencies of dependencies of...
resdeps () {
    items="$*"

    deplist=""
    for item in $items ; do
        deps=$(awk -F ':' "/$item.ko: / { print gensub(\".*/$item.ko: \",\"\",\"g\") }" $KERNELROOT/lib/modules/$version/modules.dep)
        for dep in $deps ; do
            depfile=${dep##*/}
            depname=${dep%%.ko}
            deplist="$deplist $depname"
        done
    done
    items=$(for n in $items $deplist; do echo $n; done | sort -u)
    echo $items
}

expandModuleSet() {
    SET=""
    for name in $1; do
        char=$(echo $name | cut -c1)
        if [ $char = '=' ]; then
	    NAME=$(echo $name | cut -c2-)
	    if [ "$NAME" = "ata" ]; then
		SET="$SET $(egrep '(ata|ahci)' $KERNELROOT/lib/modules/$version/modules.block |sed -e 's/.ko//')"
	    elif [ "$NAME" = "scsi" ]; then
		SET="$SET $(sed -e 's/.ko//' $KERNELROOT/lib/modules/$version/modules.block)"
	    elif [ "$NAME" = "net" ]; then
		SET="$SET $(sed -e 's/.ko//' $KERNELROOT/lib/modules/$version/modules.networking)"
	    else
                # Ignore if group list does not exist
                if [ -e $KERNELROOT/lib/modules/$version/modules.$NAME ]; then
		    SET="$SET $(sed -e 's/.ko//' $KERNELROOT/lib/modules/$version/modules.$NAME)"
                fi
            fi
        else
            SET="$SET $name"
        fi
    done

    echo $SET
}

makemoduletree() {
    MMB_DIR=$1
    MMB_MODULESET=$(resdeps $2)

    mkdir -p $MMB_DIR/lib
    mkdir -p $MMB_DIR/modules
    mkdir -p $MMB_DIR/firmware
    ln -snf ../modules $MMB_DIR/lib/modules
    ln -snf ../firmware $MMB_DIR/lib/firmware

    echo "Copying kernel modules..."
    # might not be the first kernel we are building the image for, remove the target dir's contents
    rm -rf $MMB_DIR/lib/modules/*
    cp -a $KERNELROOT/lib/modules/* $MMB_DIR/lib/modules/
    echo "Removing extraneous modules..."
    find $MMB_DIR/lib/modules/ -name *.ko | while read module ; do
        m=${module##*/}
        modname=${m%%.ko}
        echo $MMB_MODULESET | grep -wq $modname || {
            rm -f $module
        }
    done

    echo "Copying required firmware..."
    find $MMB_DIR/lib/modules/ -name *.ko | while read module ; do
        for fw in $(modinfo -F firmware $module); do
            dest=$MMB_DIR/firmware/$fw
            destdir=$(dirname $dest)

            # Some firmware files are expected to be in their own directories.
            if [ ! -d $destdir ]; then
                mkdir -p $destdir
            fi

            cp $KERNELROOT/lib/firmware/$fw $dest
        done
    done

    # Copy in driver firmware we know we'll want during installation. This is
    # required for modules which still don't (or can't) export information
    # about what firmware files they require.
    for module in $MODSET ; do
        case $module in
            ipw2100)
                cp $KERNELROOT/lib/firmware/ipw2100* $MMB_DIR/firmware
            ;;
            ipw2200)
                cp $KERNELROOT/lib/firmware/ipw2200* $MMB_DIR/firmware
            ;;
            iwl3945)
                cp $KERNELROOT/lib/firmware/iwlwifi-3945* $MMB_DIR/firmware
            ;;
            atmel)
                cp $KERNELROOT/lib/firmware/atmel_*.bin $MMB_DIR/firmware
            ;;
            zd1211rw)
                cp -r $KERNELROOT/lib/firmware/zd1211 $MMB_DIR/firmware
            ;;
        esac
    done

    # create depmod.conf to support DDs
    mkdir -p $MMB_DIR/etc/depmod.d
    cat > $MMB_DIR/etc/depmod.d/dd.conf << EOF
search updates built-in
EOF

    # clean up leftover cruft
    find -H $MMB_DIR/lib/modules -type d -exec rmdir -f {} \; 2>/dev/null
    $MODLIST --modinfo-file $MODINFO --ignore-missing --modinfo \
    $MMB_MODULESET > $MMB_DIR/lib/modules/module-info
    # compress modules
    find -H $MMB_DIR/lib/modules -type f -name *.ko -exec gzip -9 {} \;
    rundepmod $MMB_DIR
    rm -f $MMB_DIR/lib/modules/*/modules.*map
    rm -f $MMB_DIR/lib/modules/*/{build,source}

    # create the pci.ids, from modules.alias and the X driver aliases
    awk '!/^(\t\t|#)/ { print ;if ($0 == "ffff  Illegal Vendor ID") nextfile; }' < $PCIIDS | \
        $TRIMPCIIDS $MMB_DIR/lib/modules/*/modules.alias $XDRIVERS/* > ../pci.ids
}


makeproductfile() {
    root=$1

    rm -f $root/.buildstamp
    cat > $root/.buildstamp << EOF
[Main]
BugURL=$BUGURL
IsBeta=$ISBETA
Product=$PRODUCT
UUID=$IMAGEUUID
Version=$VERSION
EOF
}

makeinitrd() {
    INITRDMODULES=""
    KEEP=""
    OUTFILE=""

    while [ x$(echo $1 | cut -c1-2) = x"--" ]; do
        if [ $1 = "--dest" ]; then
            OUTFILE=$2
            shift; shift
            continue
        elif [ $1 = "--keep" ]; then
            KEEP=yes
            shift
            continue
        elif [ $1 = "--modules" ]; then
            INITRDMODULES=$2
            shift; shift
            continue
        fi
        echo "Unknown option passed to makeinitrd"
        exit 1
    done

    if [ -z "$INITRDMODULES" ]; then
        echo "warning: no loader modules specified!" >&2
    fi

    MBD_FSIMAGE=$TMPDIR/makebootdisk.initrdimage.$$
    MBD_BOOTTREE=$TMPDIR/makebootdisk.tree.$$

    if [ -n "$INITRDMODULES" ]; then
        MODSET=`expandModuleSet "$INITRDMODULES"`
        makemoduletree $IMGPATH "$MODSET"
    fi

    makeproductfile $IMGPATH

    rm -f $MBD_FSIMAGE
    (cd $IMGPATH; find . |cpio --quiet -c -o) |gzip -9 > $MBD_FSIMAGE

    size=$(du $MBD_FSIMAGE | awk '{ print $1 }')

    echo "Wrote $MBD_FSIMAGE (${size}k compressed)"

    if [ -n "$OUTFILE" ]; then
        mkdir -p `dirname $OUTFILE`
        cp -a $MBD_FSIMAGE $OUTFILE
    fi

    if [ -z "$KEEP" ]; then
        rm -rf $MBD_FSIMAGE $MBD_BOOTTREE
    fi
}

doPostImages() {
   /bin/true
}

# this gets overloaded if we're on an EFI-capable arch (... with grub)
makeEfiImages()
{
    echo "Not on an EFI capable machine; skipping EFI images."
    /bin/true
}

# source the architecture specific mk-images file so we can call functions
# in it
if [ ${BUILDARCH} = s390x ]; then
    # FIXME: this is a bad hack for s390, but better than copying for now
    source $TOPDIR/mk-images.s390
elif [ ${BUILDARCH} = ppc64 ]; then
    # ... and similar for ppc64
    source $TOPDIR/mk-images.ppc
elif [ ${BUILDARCH} = "x86_64" ]; then
    export UEFI_BOOT_ISO="no"
    source $TOPDIR/mk-images.x86
    source $TOPDIR/mk-images.efi
elif [ ${BUILDARCH} = "i386" ]; then
    source $TOPDIR/mk-images.x86
elif [ ${BUILDARCH} = "sparc64" -o ${BUILDARCH} = "sparcv9" ]; then
    source $TOPDIR/mk-images.sparc
else
    source $TOPDIR/mk-images.${BUILDARCH}
fi

# Find the kernel, unpack it, and verify it
kerneltags="kernel"
efiarch=""
arches="$BUILDARCH"
if [ "$BUILDARCH" = "ppc" ]; then
    arches="ppc64 ppc"
elif [ "$BUILDARCH" = "i386" ]; then
    arches="i586"
    efiarch="ia32"
    kerneltags="kernel kernel-PAE"
    kernelxen="kernel-PAE"
elif [ "$BUILDARCH" = "x86_64" ]; then
    kerneltags="kernel"
    efiarch="x64"
elif [ "$BUILDARCH" = "ia64" ]; then
    kerneltags="kernel"
    efiarch="ia64"
elif [ "$BUILDARCH" = "sparc" -o "$BUILDARCH" = "sparcv9" -o "$BUILDARCH" = "sparc64" ]; then
    arches="sparc64"
fi

foundakernel=""
for KERNELARCH in $arches; do
    for kernelvers in $kerneltags; do
        kpackage=$(findPackage $kernelvers)
        if [ "$KERNELARCH" = "i586" -a -z "$kpackage" ]; then
            echo "No i586 kernel, trying i686..."
            KERNELARCH="i686"
            kpackage=$(findPackage $kernelvers)
        fi

        if [ -z "$kpackage" ]; then
            echo "Unable to find kernel package $kernelvers"
            continue
        fi

        yumdownloader -c $yumconf --archlist=$KERNELARCH $kpackage
        kpackage="$kpackage.rpm"
        if [ ! -f "$kpackage" ]; then
            echo "kernel ($kernelvers) doesn't exist for $KERNELARCH.  skipping"
            continue
        fi

        KERNELROOT=$KERNELBASE/$KERNELARCH
        mkdir -p $KERNELROOT
        rm -rf $KERNELROOT/* # in case we already populated the dir for a previous kernel tag

        foundakernel="yes"

        if [ "$BUILDARCH" = "ia64" ]; then
            vmlinuz=$(rpm --nodigest --nosignature -qpl $kpackage |grep ^/boot/efi/EFI/redhat/vmlinuz | head -n 1)
            version=${vmlinuz##/boot/efi/EFI/redhat/vmlinuz-}
        else
            vmlinuz=$(rpm --nodigest --nosignature -qpl $kpackage |grep ^/boot/vmlinuz | head -n 1)
            version=${vmlinuz##/boot/vmlinuz-}
        fi
        arch=$(rpm --nodigest --nosignature --qf '%{ARCH}\n' -qp $kpackage)

        rpm2cpio $kpackage | (cd $KERNELROOT; cpio --quiet -iumd)
        rm -f $kpackage
        # expand out any available firmware too
        for p in $(repoquery -c $yumconf '*firmware*') ; do
            yumdownloader -c $yumconf $p
            rpm2cpio *firmware*.rpm | (cd $KERNELROOT; cpio --quiet -iumd)
            rm -f *firmware*.rpm
        done

        if [ ! -d "$KERNELROOT/lib/modules/$version" ]; then
            echo "$KERNELROOT/lib/modules/$version is not a valid modules directory" 2>&1
            exit 1
        fi

        if [ ! -f "$KERNELROOT/$KERNELDIR/${KERNELNAME}-$version" ]; then
            echo "$KERNELROOT/$KERNELDIR/${KERNELNAME}-$version does not exist"
            exit 1
        fi

        allmods=$(find $KERNELROOT/lib/modules/$version -name *.ko)

        rundepmod $KERNELROOT
        $GENMODINFO $KERNELROOT/lib/modules/$version > $MODINFO

        # make the boot images
        makeBootImages

        makeEfiImages $yumconf
    done
done

doPostImages

cd $TOPDIR
