#!/usr/bin/env bash # Buildroot wrapper to the collection of ext2/3/4 filesystem tools: # - genext2fs, to generate ext2 filesystem images # - tune2fs, to modify an ext2/3/4 filesystem (possibly in an image file) # - e2fsck, to check and fix an ext2/3/4 filesystem (possibly in an image file) set -e main() { local OPT OPTARG local nb_blocks nb_inodes nb_res_blocks root_dir image gen rev label uuid local -a genext2fs_opts local -a tune2fs_opts local tune2fs_O_opts # Default values gen=2 rev=1 nb_extra_blocks=0 nb_extra_inodes=0 while getopts :hb:B:i:I:r:d:o:G:R:l:u: OPT; do case "${OPT}" in h) help; exit 0;; b) nb_blocks=${OPTARG};; B) nb_extra_blocks=${OPTARG};; i) nb_inodes=${OPTARG};; I) nb_extra_inodes=${OPTARG};; r) nb_res_blocks=${OPTARG};; d) root_dir="${OPTARG}";; o) image="${OPTARG}";; G) gen=${OPTARG};; R) rev=${OPTARG};; l) label="${OPTARG}";; u) uuid="${OPTARG}";; :) error "option '%s' expects a mandatory argument\n" "${OPTARG}";; \?) error "unknown option '%s'\n" "${OPTARG}";; esac done # Sanity checks if [ -z "${root_dir}" ]; then error "you must specify a root directory with '-d'\n" fi if [ -z "${image}" ]; then error "you must specify an output image file with '-o'\n" fi case "${gen}:${rev}" in 2:0|2:1|3:1|4:1) ;; 3:0|4:0) error "revision 0 is invalid for ext3 and ext4\n" ;; *) error "unknown ext generation '%s' and/or revision '%s'\n" \ "${gen}" "${rev}" ;; esac # calculate needed inodes if [ -z "${nb_inodes}" ]; then nb_inodes=$(find "${root_dir}" | wc -l) nb_inodes=$((nb_inodes+400)) fi nb_inodes=$((nb_inodes+nb_extra_inodes)) # calculate needed blocks if [ -z "${nb_blocks}" ]; then # size ~= superblock, block+inode bitmaps, inodes (8 per block), # blocks; we scale inodes / blocks with 10% to compensate for # bitmaps size + slack nb_blocks=$(du -s -k "${root_dir}" |sed -r -e 's/[[:space:]]+.*$//') nb_blocks=$((500+(nb_blocks+nb_inodes/8)*11/10)) if [ ${gen} -ge 3 ]; then # we add 1300 blocks (a bit more than 1 MiB, assuming 1KiB blocks) # for the journal # Note: I came to 1300 blocks after trial-and-error checks. YMMV. nb_blocks=$((nb_blocks+1300)) fi fi nb_blocks=$((nb_blocks+nb_extra_blocks)) # Upgrade to rev1 if needed if [ ${rev} -ge 1 ]; then tune2fs_O_opts+=",filetype" fi # Add a journal for ext3 and above if [ ${gen} -ge 3 ]; then tune2fs_opts+=( -j -J size=1 ) fi # Add ext4 specific features if [ ${gen} -ge 4 ]; then tune2fs_O_opts+=",extents,uninit_bg,dir_index" fi # Add our -O options (there will be at most one leading comma, remove it) if [ -n "${tune2fs_O_opts}" ]; then tune2fs_opts+=( -O "${tune2fs_O_opts#,}" ) fi # Add the label if specified if [ -n "${label}" ]; then tune2fs_opts+=( -L "${label}" ) fi # Generate the filesystem genext2fs_opts=( -z -b ${nb_blocks} -N ${nb_inodes} -d "${root_dir}" ) if [ -n "${nb_res_blocks}" ]; then genext2fs_opts+=( -m ${nb_res_blocks} ) fi genext2fs "${genext2fs_opts[@]}" "${image}" # genext2fs does not generate a UUID, but fsck will whine if one # is missing, so we need to add a UUID. # Of course, this has to happen _before_ we run fsck. # Also, some ext4 metadata are based on the UUID, so we must # set it before we can convert the filesystem to ext4. # If the user did not specify a UUID, we generate a random one. # Although a random UUID may seem bad for reproducibility, there # already are so many things that are not reproducible in a # filesystem: file dates, file ordering, content of the files... tune2fs -U "${uuid:-random}" "${image}" # Upgrade the filesystem if [ ${#tune2fs_opts[@]} -ne 0 ]; then tune2fs "${tune2fs_opts[@]}" "${image}" fi # After changing filesystem options, running fsck is required # (see: man tune2fs). Running e2fsck in other cases will ensure # coherency of the filesystem, although it is not required. # 'e2fsck -pDf' means: # - automatically repair # - optimise and check for duplicate entries # - force checking # Sending output to oblivion, as e2fsck can be *very* verbose, # especially with filesystems generated by genext2fs. # Exit codes 1 & 2 are OK, it means fs errors were successfully # corrected, hence our little trick with $ret. ret=0 e2fsck -pDf "${image}" >/dev/null || ret=$? case ${ret} in 0|1|2) ;; *) errorN ${ret} "failed to run e2fsck on '%s' (ext%d)\n" \ "${image}" ${gen} esac printf "\n" trace "e2fsck was successfully run on '%s' (ext%d)\n" "${image}" ${gen} printf "\n" # Remove count- and time-based checks, they are not welcome # on embedded devices, where they can cause serious boot-time # issues by tremendously slowing down the boot. tune2fs -c 0 -i 0 "${image}" } help() { cat <<_EOF_ NAME ${my_name} - Create an ext2/3/4 filesystem image SYNOPSIS ${my_name} [OPTION]... DESCRIPTION Create ext2/3/4 filesystem image from the content of a directory. -b BLOCKS Create a filesystem of BLOCKS 1024-byte blocs. The default is to compute the required number of blocks. -i INODES Create a filesystem with INODES inodes. The default is to compute the required number of inodes. -r RES_BLOCKS Create a filesystem with RES_BLOCKS reserved blocks. The default is to reserve 0 block. -d ROOT_DIR Create a filesystem, using the content of ROOT_DIR as the content of the root of the filesystem. Mandatory. -o FILE Create the filesystem in FILE. Madatory. -G GEN -R REV Create a filesystem of generation GEN (2, 3 or 4), and revision REV (0 or 1). The default is to generate an ext2 revision 1 filesystem; revision 0 is invalid for ext3 and ext4. -l LABEL Create a filesystem with label LABEL. The default is to not set a label. -u UUID Create filesystem with uuid UUID. The default is to set a random UUID. Exit status: 0 if OK !0 in case of error _EOF_ } trace() { local msg="${1}"; shift; printf "%s: ${msg}" "${my_name}" "${@}"; } warn() { trace "${@}" >&2; } errorN() { local ret="${1}"; shift; warn "${@}"; exit ${ret}; } error() { errorN 1 "${@}"; } my_name="${0##*/}" main "$@"