Writing Functions in GRUB's DSL

GRUB's DSL (Domain-Specific Language) is fairly extensive, and capable of doing some interesting things. One thing I haven't found is a way to capture the output of a command or feed it back into another. I described this problem to a friend who said "aren't pipes the foundation of Unix?" Yes, they are. So that's disappointing.

If you're reading this, you should familiarize yourself with my previous posts about GRUB: The GRUB Command Line, The GRUB 'grubenv' Variable (although that isn't used here), and particularly GRUB - Pseudo-Graphical Mode and Fonts.

Understand that what I'm doing is writing scripts in a language nobody bothers to script (aside from building menu systems), so I have very few examples to work from ... The stuff I've created may be a bit rough around the edges.

# grub.cfg
export pager=1
loadfont $prefix/fonts/terminus20.pf2
set gfxmode=1920x1200,1920x1080,1280x800,1366x768,1280x1024,1024x768,800x600,640x480,auto
insmod all_video
insmod gfxterm
terminal_output gfxterm

function_folder='(hd0,msdos1)/boot/grub/grubbits'
for func in chfont chres colours cpuinfo dell memtest ; do
    source ${function_folder}/${func}
done

We're already seeing where feeding the output of one command into the input of another would be a huge help: I want to source all files in the ${function_folder} folder, but I have to name them individually instead of using a Bash expansion like *. Each of the files in that folder contains a function with the same name as the file: here they are.

The videoinfo command (not available at the Fedora GRUB HD command line, but available everywhere else, including USB sticks made by Fedora) will show you what resolutions are available on your monitor. You can change to one of the other named resolutions:

function chres {
    if [ ${1}x = x ]
    then
        echo "Please supply a resolution (ie. '800x600') from the"
        echo "output of the 'videoinfo' command."
        exit 1
    else
        set gfxmode=${1}         # load the new resolution
        terminal_output console  # toggle the console to activate the new resolution
        terminal_output gfxterm
    fi
}

If you have alternate .pf2 fonts (see my blog entry on making them), you can change to them with this command (this assumes they're stored in the boot/grub/fonts/ folder):

function _chfont_help {
    echo "Usage:"
    echo "  chfont <fontname>"
    echo "Please supply a font name (without the .pf2 extension) from the"
    echo "${prefix}/fonts/ directory."
}

function chfont {
    newfont=${prefix}/fonts/${1}.pf2
    if [ ${2}x != x ]
    then
        _chfont_help
    elif [ ${1}x = x ]
    then
        _chfont_help
    elif ! [ -f ${newfont} ]
    then
        echo "'${newfont}' doesn't appear to exist."
        _chfont_help
    else
        loadfont ${newfont}      # load the named font
        terminal_output console  # toggle the console to activate the new font
        terminal_output gfxterm
    fi
}

Apparently pretty colours has been a priority for the GRUB community: they're easy to use and there are quite a few (well - by command line standards). Note that colour availability varies by platform: I use i386 and amd64, and colours are well supported there.

# This file echoes a bunch of color codes to the terminal to demonstrate
# what's available.
function colours {
    for bg in 'black' 'blue' 'green' 'cyan' 'red' 'magenta' 'brown' \
            'light-gray' 'dark-gray' 'light-blue' 'light-green' \
            'light-cyan' 'light-red' 'light-magenta' 'yellow' 'white'
    do
    color_normal=light-gray/black  # reset to readable
    echo -n "$bg bg: "
        for fg in 'black' 'blue' 'green' 'cyan' 'red' 'magenta' 'brown' \
                'light-gray' 'dark-gray' 'light-blue' 'light-green' \
                'light-cyan' 'light-red' 'light-magenta' 'yellow' 'white'
        do
            color_normal=${fg}/${bg}
            echo -n "${fg}  "
        done
        echo ""
    done
    color_normal=light-gray/black  # reset to readable
    echo ""
}

GRUB has rudimentary knowledge of the CPU it's running on:

function cpuinfo {
    # only able to determine: 32/64 bit, and is it PAE
    echo "GRUB's ability to analyse processors is limited, we can only tell you:"
    if cpuid -p; then
        pae_assessment="PAE"
    else
        pae_assessment="NO PAE"
    fi
    if cpuid -l; then
        echo "64-bit processor, $pae_assessment"
    else
        echo "32-bit processor, $pae_assessment"
    fi
}

I have an old Dell laptop that has a specific and slightly problematic video card. It was common enough that people wrote a lot of code to support it under Linux (and GRUB). Don't try running this unless you know you have a 915 and it supports this specific resolution, although it could act as an example to access a different 915-supported resolution:

function dell {
    # override video mode 30 (usually 640x480x8) with a res for the
    # Dell Inspiron 700m:
    # ( http://915resolution.mango-lang.org/ )
    915resolution 30 1280 800
    set gfxmode=1280x800,1024x768,800x600,640x480,auto
    terminal_output console ; terminal_output gfxterm
}

GRUB is mostly used as a menu system, but you can just as well boot from the command line. This requires the memtest86+-5.01.bin binary in the isos subfolder of boot/grub/. Most ISOs or kernels are A) bigger, and B) require more settings to boot properly: I chose this as the simplest possible test (it works).

function memtest {
    memtestbin=${_prefix}/isos/memtest86+-5.01.bin
    if [ -f ${memtestbin} ]
    then
        linux16 ${memtestbin}
        boot
    fi
}

Note the addition of the boot keyword to start the boot process: it's assumed in menu entries in GRUB, but is required if you're writing scripts like this.

Bibliography

The reference manual you need to read: https://www.gnu.org/software/grub/manual/grub/html_node/ (very short on examples).