[klibc] [WIP] rebuild initramfs when content changes

Sam Ravnborg sam at ravnborg.org
Sat Apr 8 14:49:12 PDT 2006


hpa notified me in private mail that we have an issue that when building
initramfs - if the content changes (say kinit) then the initramfs will
not be rebuild.

The solution I have come up with is to let make check all the files
included in the initramfs and rebuild it if any files are newer than the
initramfs and also rebuild if arguments changes (root_uid).

In same process the usr/Kbuild file was cluttered with functionality
that really belonged in a shell script.
So in the end all functionality was moved to the shell script
gen_initramfs_list.sh.
The script got a new name and a new home during the process.
It is now located in usr/ along with gen_init_cpio

Outstanding issues:
- needs to find a nice way to get rid of temp files
- carefull review when I'm awake again

Posting it here to get some inital comments on the approach and maybe
some usefull comments on my less-skilled shell script programming.

The files are just pasted in below for comments. Gimme a day more to
finish it up.

	Sam

Restructured usr/Kbuild:
#
# kbuild file for usr/ - including initramfs image and klibc
#

# klibc definitions - to be moved to top-level Makefile later
export KLIBCSRC := $(srctree)/$(src)/klibc
export KLIBCINC := $(srctree)/$(src)/include
export KLIBCOBJ := $(objtree)/$(obj)/klibc
CONFIG_KLIBC := 1

# Klibc binaries
ifdef CONFIG_KLIBC
klibc := -f $(srctree)/scripts/Kbuild.klibc obj

.PHONY: klibcdirs
$(obj)/initramfs_list: klibcdirs

klibcdirs: FORCE
	$(Q)$(MAKE) $(klibc)=$(src)/klibc
	$(Q)$(MAKE) $(klibc)=$(src)/kinit
	$(Q)$(MAKE) $(klibc)=$(src)/utils
	$(Q)$(MAKE) $(klibc)=$(src)/dash
	$(Q)$(MAKE) $(klibc)=$(src)/gzip
endif
subdir- := klibc kinit utils dash gzip


# Generate builtin.o based on initramfs_data.o
obj-y        := initramfs_data.o

# initramfs_data.o contains the initramfs_data.cpio.gz image.
# The image is included using .incbin, a dependency which is not
# tracked automatically.
$(obj)/initramfs_data.o: $(obj)/initramfs_data.cpio.gz FORCE

#####
# Generate the initramfs cpio archive
# Following steps are executed:
# Create list of files to be included in archive
# If any of the files are newer than target generate new cpio archive

# Create cpio archive from filelist
hostprogs-y  := gen_init_cpio

# Files included in cpio archive (cpio archive is updated if
# any included files are newer than the cpio archive)
initramfs     := $(CONFIG_SHELL) $(srctree)/$(src)/gen_initramfs
ramfs-input    = $(if $(filter-out "",$(CONFIG_INITRAMFS_SOURCE)), \
                      $(CONFIG_INITRAMFS_SOURCE),-d)
ramfs-filelist = $(shell $(initramfs) -l $(ramfs-input))

quiet_cmd_initfs = GEN     $@
      cmd_initfs = \
	$(initramfs) -o $@ \
	$(if $(CONFIG_INITRAMFS_ROOT_UID), -u $(CONFIG_INITRAMFS_ROOT_UID)) \
	$(if $(CONFIG_INITRAMFS_ROOT_GID), -g $(CONFIG_INITRAMFS_ROOT_GID)) \
	$(ramfs-input)

targets := initramfs_data.cpio.gz
$(obj)/initramfs_data.cpio.gz: $(ramfs-filelist) $(obj)/gen_init_cpio FORCE
	$(call if_changed,initfs)

# Cleaning time
clean-files  := initramfs_list initramfs_data.cpio.gz


The restructured usr/gen_initramfs shell script:
#!/bin/bash
# Copyright (C) Martin Schlemmer <azarah at nosferatu.za.org>
# Released under the terms of the GNU GPL
#
# Generate a newline separated list of entries from the file/directory
# supplied as an argument.
#
# If a file/directory is not supplied then generate a small dummy file.
#
# The output is suitable for gen_init_cpio built from usr/gen_init_cpio.c.
#
list_default_initramfs() {
	echo usr/kinit/kinit
}

default_initramfs() {
	cat <<-EOF >> ${cpio_list}
		# This is a very simple, default initramfs

		dir /dev 0755 0 0
		nod /dev/console 0600 0 0 c 5 1
		dir /root 0700 0 0
		# file /kinit usr/kinit/kinit 0755 0 0
		# slink /init kinit 0755 0 0
		file /init usr/kinit/kinit 0755 0 0
	EOF
}

filetype() {
	local argv1="$1"

	# symlink test must come before file test
	if [ -L "${argv1}" ]; then
		echo "slink"
	elif [ -f "${argv1}" ]; then
		echo "file"
	elif [ -d "${argv1}" ]; then
		echo "dir"
	elif [ -b "${argv1}" -o -c "${argv1}" ]; then
		echo "nod"
	elif [ -p "${argv1}" ]; then
		echo "pipe"
	elif [ -S "${argv1}" ]; then
		echo "sock"
	else
		echo "invalid"
	fi
	return 0
}

list_print_mtime() {
	:
}

print_mtime() {
	local argv1="$1"
	local my_mtime="0"

	if [ -e "${argv1}" ]; then
		my_mtime=$(find "${argv1}" -printf "%T@\n" | sort -r | head -n 1)
	fi

	echo "# Last modified: ${my_mtime}" >> ${cpio_list}
	echo "" >> ${cpio_list}
}

list_parse() {
	echo "$1"
}

parse() {
	local location="$1"
	local name="${location/${srcdir}//}"
	# change '//' into '/'
	name="${name//\/\///}"
	local mode="$2"
	local uid="$3"
	local gid="$4"
	local ftype=$(filetype "${location}")
	# remap uid/gid to 0 if necessary
	[ "$uid" -eq "$root_uid" ] && uid=0
	[ "$gid" -eq "$root_gid" ] && gid=0
	local str="${mode} ${uid} ${gid}"

	[ "${ftype}" == "invalid" ] && return 0
	[ "${location}" == "${srcdir}" ] && return 0

	case "${ftype}" in
		"file")
			str="${ftype} ${name} ${location} ${str}"
			;;
		"nod")
			local dev_type=
			local maj=$(LC_ALL=C ls -l "${location}" | \
					gawk '{sub(/,/, "", $5); print $5}')
			local min=$(LC_ALL=C ls -l "${location}" | \
					gawk '{print $6}')

			if [ -b "${location}" ]; then
				dev_type="b"
			else
				dev_type="c"
			fi
			str="${ftype} ${name} ${str} ${dev_type} ${maj} ${min}"
			;;
		"slink")
			local target=$(LC_ALL=C ls -l "${location}" | \
					gawk '{print $11}')
			str="${ftype} ${name} ${target} ${str}"
			;;
		*)
			str="${ftype} ${name} ${str}"
			;;
	esac

	echo "${str}" >> ${cpio_list}

	return 0
}

usage() {
cat << EOF
Usage:
$0 [-o <file>] [-u <uid>] [-g <gid>] [-d | <cpio_source>] ] ...
$0 -l [-u <uid>] [-g <gid>] [-d | <cpio_source>] ] ...
	-o <file>      Create gzipped initramfs file using gen_init_cpio
	-l             List all files that will be added to initramfs
	-u <uid>       User ID to map to user ID 0 (root).
	               <uid> is only meaningful if <cpio_source>
	               is a directory.
	-g <gid>       Group ID to map to group ID 0 (root).
	               <gid> is only meaningful if <cpio_source>
	               is a directory.
	<cpio_source>  File list or directory for cpio archive.
	               If <cpio_source> is a .cpio file it will be used
		       as input to initramfs.
	-d             Output the default cpio list.

All options may be repeated and are interpreted sequentially
and immediately.  -u and -g states are preserved across
<cpio_source> options so an explicit "-u 0 -g 0" is required
to reset the root/group mapping.
EOF
}

unknown_option() {
	printf "ERROR: unknown option \"$arg\"\n" >&2
	printf "If the filename validly begins with '-', " >&2
	printf "then it must be prefixed\n" >&2
	printf "by './' so that it won't be interpreted as an option." >&2
	printf "\n" >&2
	usage >&2
	exit 1
}

list_header() {
	:
}

header() {
	printf "\n#####################\n# $1\n" >> ${cpio_list}
}

dir_filelist() {
	${file_list}header "$1"

	srcdir=$(echo "$1" | sed -e 's://*:/:g')
	dirlist=$(find "${srcdir}" -printf "%p %m %U %G\n" 2>/dev/null)

	# If $dirlist is only one line, then the directory is empty
	if [  "$(echo "${dirlist}" | wc -l)" -gt 1 ]; then
		${file_list}print_mtime "$1"

		echo "${dirlist}" | \
		while read x; do
			${file_list}parse ${x}
		done
	fi
}



# if only one file is specified and it is .cpio file then use it direct as fs
# if a directory is specified then add all files in given direcotry to fs
# if a regular file is specified assume it is in gen_initramfs format
input_file() {
	source="$1"
	if [ -f "$1" ]; then
		is_cpio="$(echo "$1" | sed 's/^.\*\.cpio/cpio/')"
		if [ $2 -eq 1 -a ${is_cpio} == "cpio" ]; then
			cpio_file=$1
			[ ! -z ${list_file} ] && echo "$1"
			return 0
		fi
		if [ -z ${file_list} ]; then
			header "$1"
			print_mtime "$1" >> ${cpio_list}
			cat "$1"         >> ${cpio_list}
		else
			grep ^file "$1" | cut -d ' ' -f 3
		fi
	elif [ -d "$1" ]; then
		dir_filelist "$1"
	else
		echo "  ${prog}: Cannot open '$1'" >&2
		exit 1
	fi
}

prog=$0
root_uid=0
root_gid=0
file_list=
cpio_file=
cpio_list="/dev/stdout"
output_file=""
input_seen=

arg="$1"
case "$arg" in
	"-l")
		file_list="list_"
		shift
		;;
	"-o")
		shift
		output_file="$1"
		cpio_list="$(mktemp /tmp/cpiolist.XXXXXX)"
		shift
		;;
esac
while [ $# -gt 0 ]; do
	arg="$1"
	shift
	case "$arg" in
		"-u")
			root_uid="$1"
			shift
			;;
		"-g")
			root_gid="$1"
			shift
			;;
		"-d")
			default_list="$arg"
			${file_list}default_initramfs
			;;
		"-h")
			usage
			exit 0
			;;
		*)
			case "$arg" in
				"-"*)
					unknown_option
					;;
				*)
					input_file "$arg" "$#"
					input_seen=y
					;;
			esac
			;;
	esac
done

[ -z ${input_seen} ] &&	${file_list}default_initramfs
[ ! -z ${file_list} ] && exit 0


# If output_file is set we will generate cpio archive and gzip it
if [ ! -z ${output_file} ]; then
	if [ -z ${cpio_file} ]; then
		cpio_file="$(mktemp /tmp/cpiofile.XXXXXX)"
		$(dirname ${prog})/gen_init_cpio ${cpio_list} > ${cpio_file}
	fi
	cat ${cpio_file} | gzip -f -9 - > ${output_file}
fi
exit 0



More information about the klibc mailing list