User:Goverp/Genlist

From Gentoo Wiki
Jump to:navigation Jump to:search

This article contains a shell script to generate gen_init_cpio input from a configuration file, for use when generating initramfs files or configurations for the linux kernel.

The idea is to create the configuration file to specify the programs used in your /init and list the resources (files, devices etc) that it uses. The script locates the program files, and the libraries on which they depend, and generates the necessary gen_init_cpio instructions to build the appropriate directory tree and files and so forth.

The script works by generating a list of all resources and their dependencies (i.e. libraries, files, and the directories in their paths), and then expands the results into the gen_init_cpio input.

It's been updated (28 Mar 2021) to improve parsing of the ldd output, and remove the ill-considered function to read /etc/fstab. You can still include /etc/fstab as a file (to allow fsck -A) but you'd probably want your own mount points under say /mnt/root.

Usage is to run:

user $./genlist > my_init_gen_init_cpio_input_file

and then do whatever is appropriate to your kernel/bootloader configuration.

Note
genlist uses "ldd" to identify the libraries used by your /init's programs. If those programs need to be run as root, or use setuid root (for example, the "mount" command does this), ldd won't be able to locate the libraries unless genlist is run as root.
FILE /root/genlist.confgen_init-cpio input configuration
# Executable programs to be copied (with their dependent libraries) into the initramfs
# This file will be sourced as a shell script.
# Globbed names and paths, such as "/dev/sd?" work
programs="mdadm fsck.f2fs busybox"

# If a path exist in the invoking environment,
# attrs, Uid, Guid and type will be copied from it,
# otherwise defaulted to 755 0 0
paths="/proc /sys /dev/console /dev/tty0 /dev/tty1 /dev/null /mnt/root"

# Files to be copied into the initramfs, optionally moved if there's a new path/name preceding ":="
# Don't forget to include your init file moved to "/init"
files="/init:=/usr/src/initramfs/init /etc/mdadm.conf"

# Symbolic links to create in the initramfs, format "link->target"
slinks="/bin/sh:=busybox"
FILE /root/genlistgen_init-cpio input generator
#! /bin/sh

doLicence() {
cat <<- 'endLicence'
        genlist.sh - Version 0.9 - Generate gen_init_cpio input from genlist.conf

        Copyright (C) 2020-2021 Paul Gover

    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 3 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 <https://www.gnu.org/licenses/>.
endLicence
}

doHelp() {
cat << 'endHelp'
Usage: genlist.sh [OPTION]...

Where the output is determined by the genlist.conf file.

Options
-h      print this help text
-l      print the licence information
-d      print the list of Gentoo packages containing all the programs listed in genlist.sh
        (Monitor updates to these packages to trigger rerunning genlist.sh)

Files
genlist.conf in the same directory as genlist.sh, is sourced as a shell script.
It should define up to five variables:
        programs, a list of the programs used by the initramfs's init script or program
                typically these will include either busybox
                or a shell such as dash and at least switch_root
        paths,  a list of directories and nodes needed by the initramfs;
                typically these will include /proc, /sys, /dev and /dev/console
        files,  a list of "name" or "name:=location" of files to be copied into the initramfs
                typically including at least "/init:=<location of my init file>"
        slinks, a list of symbolic links to be created,
                typically including "/bin/sh:=busybox" or "/bin/sh:=<my chosen shell program>"
endHelp
}

# Issue an error message and quit
error() {
        printf "%s\n" "$*" >&2
        exit 1
}

DEFAULT_PROPERTIES="755 0 0 0 0 directory"

# Print a gen_init_cpio input line to create a node/directory/file/pipe or socket
# Input is the name or "name:=location" pair from which to generate the line
# The output will have attr, uid, guid etc. copied from the executiing system's version of the named file,
# assuming it has one, otherwise the default.
# Symbolic links get treated as files, the line getting the link's name but the stat for its target
expandFile() {
        local name location stat
        name="${1%:=*}"

        if [ "$name" = "$1" ]
        then    location="$name"
        else    location="${1#*:=}"
        fi

        stat="$(stat --dereference --format "%n %a %u %g %t %T %F" "$location" 2>/dev/null)" || stat="$location 755 0 0 0 0 directory"
        expand $name $stat
}

# For each program in the paramter list,
# list the program file, and its dynamically-linked libraries
listProgram() {
        local program path soname library arror file
        for program
        do
                path=$(which "$program")
                listFile "$path"

                # Changed to parse the ldd lines at read time rather than messy parse later
                ldd "$path" 2>/dev/null | while read -r soname arrow file _
                do
                        library="${soname%%.so.*}"
                        if      [ "$library" = "linux-vdso" ]
                        then    continue        # Nothing to do, it's part of the kernel
                        elif    [ "$arrow" = "=>" ] && [ -f "$file" ]
                        then    listFile "$file"
                        elif    [ -f "$soname" ]
                        then    listFile "$soname"
                        else    error "Unexpected ldd $path output $soname $arrow $file"
                        fi
                done
        done
}

# For each item in the parameters, print it and all the directories in its path
listPath() {
        local file path

        for file
        do
                path="$file"
                while [ "$path" != "" ]
                do
                        expandFile "$path"
                        path="${path%/*}"
                done
        done
}

# Like listPath, but ignoring any location in "name:=location"
listFile() {
        local file path

        for file
        do
                expandFile "$file"
                path="${file%:=*}"
                listPath "${path%/*}"
        done
}

# Like listPath, but ignoring locations and the link files themselves.
#And handle slinks, which may not be files, and therefore need different handling
listLink() {
        local name target
        for link
        do
                name="${link%:=*}"
                listPath "${name%/*}"
                target="${link#*:=}"
                printf "slink %-30s %-30s %4s %4s %4s\n" "$name" "$target" 777 0 0
        done
}

# Print an expanded cpio input line for a name and the stat of it's location
# Parameters 1:name 2:location 3:attr 4:uid 5:gid 6:maj 7:min 8:type
expand() {
        case "$8" in
        character)      printf "nod   %-61s %4s %4s %4s c %d %d\n" "$1" "$3" "$4" "$5" "0x$6" "0x$7";;
        block)          printf "nod   %-61s %4s %4s %4s b %d %d\n" "$1" "$3" "$4" "$5" "0x$6" "0x$7";;
        directory)      printf "dir   %-61s %4s %4s %4s\n" "$1" "$3" "$4" "$5";;
        symbolic)       printf "file  %-30s %-30s %4s %4s %4s\n" "$1" "$2" "$3" "$4" "$5";;
        regular)        printf "file  %-30s %-30s %4s %4s %4s\n" "$1" "$2" "$3" "$4" "$5";;
        # Not sure pipes and socks are relevant, but gen_init_cpio supports them
        pipe)           printf "pipe  %-61s %4s %4s %4s\n" "$1" "$3" "$4" "$5";;
        sock)           printf "sock  %-61s %4s %4s %4s\n" "$1" "$3" "$4" "$5";;
        *)              error "Unexpected type $8 from stat $2";;
        esac
}

#
### Mainline code
#

# Preamble: find the config file, and handle any parameters
mypath="${0%/*}"
if [ "$mypath" = "$0" ]
then
        mynameext="$mypath"
        mypath="."
else
        mynameext="${0##*/}"
        mypath="$mypath"
fi

myname="${mynameext%.*}"
myconf="$mypath/${1:-$myname.conf}"
if [ -r "$myconf" ]
then    . "$myconf"
else    error "No configuration $myconf"
fi

[ "0" != "$(id -u)" ] && printf "\n%s\n\n" "Warning - $myname may give incomplete output if not run as root" >&2

# Handle options
while getopts hld f
do
        case $f in
        h)      doHelp ; exit ;;
        l)      doLicence ; exit ;;
        d)      qfile -q $(which $programs) | sort -u ; exit ;;
        *)      doHelp ; exit ;;
        esac
done
shift $(( OPTIND - 1 ))

# Preamble over.

# Generate a list of all files, nodes and directories, with their dependencies,
# expanded into cpio input lines
# Finally sort and extract the unique lines.
{       listPath $paths;
        listProgram $programs;
        listFile $files;
        listLink $slinks;
} | sort -d -u