User:Goverp/Genlist
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.
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.
# 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"
#! /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