From 2d9f9216eb81a3d1bf813e967535d22468ac1f76 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 16 Jan 2023 16:01:42 +0100 Subject: [PATCH] armbian-next: introduce `tmpfs-utils.sh`; put `LOGDIR` and `WORKDIR` under tmpfs; set `CCACHE_TEMPDIR` under `WORKDIR` - introduce generic `prepare_tmpfs_for()`, which manages it's own cleanup/dir removal - use it for `WORKDIR` (which is `TMPDIR`) and `LOGDIR` - adapt previous cleanup handlers for those, so they delete their contents but not the dir itself (which might be mounted) - also: make `ARMBIAN_LOG_CLI_ID` readonly together with other superglobals - set `XDG_RUNTIME_DIR` & `XDG_RUNTIME_DIR` together with `TMPDIR` - kernel-make.sh: pass `CCACHE_TEMPDIR` down to Kernel make (thanks @the-Going) --- lib/functions/cli/entrypoint.sh | 12 +++- lib/functions/compilation/kernel-make.sh | 15 ++-- lib/functions/host/tmpfs-utils.sh | 87 ++++++++++++++++++++++++ lib/functions/logging/logging.sh | 8 +-- lib/functions/main/default-build.sh | 17 +++-- lib/library-functions.sh | 9 +++ 6 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 lib/functions/host/tmpfs-utils.sh diff --git a/lib/functions/cli/entrypoint.sh b/lib/functions/cli/entrypoint.sh index f5d041fd1..a7e15c0e8 100644 --- a/lib/functions/cli/entrypoint.sh +++ b/lib/functions/cli/entrypoint.sh @@ -108,9 +108,17 @@ function cli_entrypoint() { declare -g -r MOUNT="${WORKDIR_BASE_TMP}/mount-${ARMBIAN_BUILD_UUID}" # MOUNT ("mounted on the loop") is the mounted root on final image (via loop). "image" stage declare -g -r DESTIMG="${WORKDIR_BASE_TMP}/image-${ARMBIAN_BUILD_UUID}" # DESTIMG is where the backing image (raw, huge, sparse file) is kept (not the final destination) - LOG_SECTION="entrypoint" start_logging_section # This creates LOGDIR. @TODO: also maybe causes a spurious group to be created in the log file + # Make sure ARMBIAN_LOG_CLI_ID is set, and unique, and readonly. + # Pre-runs might change it before this, but if not set, default to ARMBIAN_COMMAND. + declare -r -g ARMBIAN_LOG_CLI_ID="${ARMBIAN_LOG_CLI_ID:-${ARMBIAN_COMMAND}}" + + # If we're on Linux & root, mount tmpfs on LOGDIR. This has it's own cleanup handler. + # It also _creates_ the LOGDIR, and the cleanup handler will delete. + prepare_tmpfs_for "LOGDIR" "${LOGDIR}" + + LOG_SECTION="entrypoint" start_logging_section # This will create LOGDIR if it does not exist. @TODO: also maybe causes a spurious group to be created in the log file add_cleanup_handler trap_handler_cleanup_logging # cleanup handler for logs; it rolls it up from LOGDIR into DEST/logs - add_cleanup_handler trap_handler_reset_output_owner # make sure output folder is owned by pre-sudo user if that's the case + add_cleanup_handler trap_handler_reset_output_owner # make sure output folder is owned by pre-sudo/pre-Docker user if that's the case # @TODO: So gigantic contention point here about logging the basic deps installation. if [[ "${ARMBIAN_COMMAND_REQUIRE_BASIC_DEPS}" == "yes" ]]; then diff --git a/lib/functions/compilation/kernel-make.sh b/lib/functions/compilation/kernel-make.sh index 4f48c0651..6ee5cee58 100644 --- a/lib/functions/compilation/kernel-make.sh +++ b/lib/functions/compilation/kernel-make.sh @@ -11,11 +11,12 @@ function run_kernel_make_internal() { prepare_distcc_compilation_config common_make_envs=( - "CCACHE_BASEDIR=\"$(pwd)\"" # Base directory for ccache, for cache reuse # @TODO: experiment with this and the source path to maximize hit rate - "PATH=\"${toolchain}:${PATH}\"" # Insert the toolchain first into the PATH. - "DPKG_COLORS=always" # Use colors for dpkg @TODO no dpkg is done anymore, remove? - "XZ_OPT='--threads=0'" # Use parallel XZ compression - "TERM='${TERM}'" # Pass the terminal type, so that 'make menuconfig' can work. + "CCACHE_BASEDIR=\"$(pwd)\"" # Base directory for ccache, for cache reuse # @TODO: experiment with this and the source path to maximize hit rate + "CCACHE_TEMPDIR=\"${CCACHE_TEMPDIR:?}\"" # Temporary directory for ccache, under WORKDIR + "PATH=\"${toolchain}:${PATH}\"" # Insert the toolchain first into the PATH. + "DPKG_COLORS=always" # Use colors for dpkg @TODO no dpkg is done anymore, remove? + "XZ_OPT='--threads=0'" # Use parallel XZ compression + "TERM='${TERM}'" # Pass the terminal type, so that 'make menuconfig' can work. ) # If CCACHE_DIR is set, pass it to the kernel build; Pass the ccache dir explicitly, since we'll run under "env -i" @@ -32,7 +33,7 @@ function run_kernel_make_internal() { "${DISTCC_MAKE_J_PARALLEL[@]}" # Parallel compile, "-j X" for X cpus; determined by distcc, or is just "$CTHREADS" if distcc is not enabled. "ARCH=${ARCHITECTURE}" # Key param. Everything depends on this. - "LOCALVERSION=-${LINUXFAMILY}" # Change the internal kernel version to include the family. Changing this causes recompiles # @TODO change to "localversion" file + "LOCALVERSION=-${LINUXFAMILY}" # Change the internal kernel version to include the family. Changing this causes recompiles # @TODO change hack at .config; that might handles mtime better "CROSS_COMPILE=${CCACHE} ${DISTCC_CROSS_COMPILE_PREFIX[@]} ${KERNEL_COMPILER}" # added as prefix to every compiler invocation by make "KCFLAGS=-fdiagnostics-color=always -Wno-error=misleading-indentation" # Force GCC colored messages, downgrade misleading indentation to warning @@ -42,7 +43,7 @@ function run_kernel_make_internal() { "KBUILD_BUILD_USER=armbian" # https://www.kernel.org/doc/html/latest/kbuild/kbuild.html#kbuild-build-user-kbuild-build-host "KBUILD_BUILD_HOST=next" # https://www.kernel.org/doc/html/latest/kbuild/kbuild.html#kbuild-build-user-kbuild-build-host - "KGZIP=pigz" "KBZIP2=pbzip2" # Parallel compression, use explicit parallel compressors https://lore.kernel.org/lkml/20200901151002.988547791@linuxfoundation.org/ + "KGZIP=pigz" "KBZIP2=pbzip2" # Parallel compression, use explicit parallel compressors https://lore.kernel.org/lkml/20200901151002.988547791@linuxfoundation.org/ # @TODO: what about XZ? ) # last statement, so it passes the result to calling function. "env -i" is used for empty env diff --git a/lib/functions/host/tmpfs-utils.sh b/lib/functions/host/tmpfs-utils.sh new file mode 100644 index 000000000..81b9cb9b2 --- /dev/null +++ b/lib/functions/host/tmpfs-utils.sh @@ -0,0 +1,87 @@ +# Call: prepare_tmpfs_for "NAME_OF_TMPFS_DIR" "${PATH_TO_DIR}" # this adds its own cleanup handler +function prepare_tmpfs_for() { + declare tmpfs_name="${1}" + declare tmpfs_path="${2}" + # validate parameters + if [[ -z "${tmpfs_name}" ]]; then + exit_with_error "prepare_tmpfs_for: tmpfs_name (arg 1) is empty" + fi + if [[ -z "${tmpfs_path}" ]]; then + exit_with_error "prepare_tmpfs_for: tmpfs_path (arg 2) is empty" + fi + + # params for the handler; initially just repeat our own params + declare -a cleanup_params=("${tmpfs_name}" "${tmpfs_path}") + + # create the dir if not exists and note that down for the cleanup handler + if [[ ! -d "${tmpfs_path}" ]]; then + display_alert "prepare_tmpfs_for: creating dir" "${tmpfs_path}" "cleanup" + mkdir -p "${tmpfs_path}" + cleanup_params+=("remove_dir") + else + display_alert "prepare_tmpfs_for: dir exists" "${tmpfs_path}" "cleanup" + cleanup_params+=("no_remove_dir") + fi + + # Do nothing if we're not on Linux (detect via OSTYPE) and root. Still, setup the cleanup handler. + if [[ "${OSTYPE}" != "linux"* ]] || [[ "${EUID}" -ne 0 ]]; then + display_alert "prepare_tmpfs_for: not on Linux or not root, skipping" "${tmpfs_name}" "cleanup" + cleanup_params+=("no_umount_tmpfs") + elif [[ "${ARMBIAN_INSIDE_DOCKERFILE_BUILD}" == "yes" ]]; then + display_alert "prepare_tmpfs_for: inside Dockerfile build, skipping" "${tmpfs_name}" "cleanup" + cleanup_params+=("no_umount_tmpfs") + else + display_alert "prepare_tmpfs_for: on Linux and root, MOUNTING TMPFS" "${tmpfs_name}" "cleanup" + # mount tmpfs on it + mount -t tmpfs tmpfs "${tmpfs_path}" + cleanup_params+=("umount_tmpfs") + + #cleanup_params+=("no_umount_tmpfs") + fi + + # add the cleanup handler + declare cleanup_handler="cleanup_tmpfs_for ${cleanup_params[*]@Q}" + display_alert "prepare_tmpfs_for: add cleanup handler" "${cleanup_handler}" "cleanup" + add_cleanup_handler "${cleanup_handler}" + + return 0 +} + +function cleanup_tmpfs_for() { + declare tmpfs_name="${1}" + declare tmpfs_path="${2}" + declare remove_dir="${3}" + declare umount_tmpfs="${4}" + + # validate parameters + if [[ -z "${tmpfs_name}" ]]; then + exit_with_error "cleanup_tmpfs_for: tmpfs_name (arg 1) is empty" + fi + if [[ -z "${tmpfs_path}" ]]; then + exit_with_error "cleanup_tmpfs_for: tmpfs_path (arg 2) is empty" + fi + if [[ -z "${remove_dir}" ]]; then + exit_with_error "cleanup_tmpfs_for: remove_dir (arg 3) is empty" + fi + if [[ -z "${umount_tmpfs}" ]]; then + exit_with_error "cleanup_tmpfs_for: umount_tmpfs (arg 4) is empty" + fi + + # umount tmpfs + if [[ "${umount_tmpfs}" == "umount_tmpfs" ]]; then + display_alert "cleanup_tmpfs_for: umount tmpfs" "${tmpfs_name}" "cleanup" + umount "${tmpfs_path}" + else + display_alert "cleanup_tmpfs_for: not umounting tmpfs" "${tmpfs_name}" "cleanup" + fi + + # remove the dir if we created it + if [[ "${remove_dir}" == "remove_dir" ]]; then + display_alert "cleanup_tmpfs_for: removing dir" "${tmpfs_path}" "cleanup" + rm -rf "${tmpfs_path:?}" + else + display_alert "cleanup_tmpfs_for: not removing dir" "${tmpfs_path}" "cleanup" + fi + + return 0 +} diff --git a/lib/functions/logging/logging.sh b/lib/functions/logging/logging.sh index 483c15647..e05aaef06 100644 --- a/lib/functions/logging/logging.sh +++ b/lib/functions/logging/logging.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# This is called both early in compile.sh, but also after processing cmdline params in the cli entrypoint.sh +# This is called both early in compile.sh, but also after processing cmdline params in the cli entrypoint.sh function logging_init() { # defaults. # if stdout is a terminal, then default SHOW_LOG to yes @@ -101,10 +101,10 @@ function print_current_asset_log_base_file() { } function discard_logs_tmp_dir() { - # Linux allows us to be more careful, but really, those are log files we're talking about. + # Do not delete the dir itself, since it might be a tmpfs mount. if [[ "$(uname)" == "Linux" ]]; then - rm -rf --one-file-system "${LOGDIR}" + rm -rf --one-file-system "${LOGDIR:?}"/* # Note this is protected by :? else - rm -rf "${LOGDIR}" + rm -rf "${LOGDIR:?}"/* # Note this is protected by :? fi } diff --git a/lib/functions/main/default-build.sh b/lib/functions/main/default-build.sh index e90f4cff4..8a6ea0065 100644 --- a/lib/functions/main/default-build.sh +++ b/lib/functions/main/default-build.sh @@ -7,16 +7,19 @@ function main_default_build_single() { fi # Check the sanity of WORKDIR_BASE_TMP regarding mount options. - check_dir_for_mount_options "${WORKDIR_BASE_TMP}" "main temporary dir" + LOG_SECTION="check_dir_for_mount_options" do_with_logging check_dir_for_mount_options "${WORKDIR_BASE_TMP}" "main temporary dir" # Starting work. Export TMPDIR, which will be picked up by all `mktemp` invocations hopefully. # Runner functions in logging/runners.sh will explicitly unset TMPDIR before invoking chroot. # Invoking chroot directly will fail in subtle ways, so, please use the runner.sh functions. display_alert "Starting single build, exporting TMPDIR" "${WORKDIR}" "debug" - mkdir -p "${WORKDIR}" - add_cleanup_handler trap_handler_cleanup_workdir + LOG_SECTION="prepare_tmpfs_workdir" do_with_logging prepare_tmpfs_for "WORKDIR" "${WORKDIR}" # this adds its own cleanup handler, which deletes it if it was created + add_cleanup_handler trap_handler_cleanup_workdir # this is for when it is NOT a tmpfs, for any reason; it does not delete the dir itself. - export TMPDIR="${WORKDIR}" + # '-x': export + declare -g -x TMPDIR="${WORKDIR}" # TMPDIR is default for a lot of stuff, but... + declare -g -x CCACHE_TEMPDIR="${WORKDIR}/ccache_tmp" # Export CCACHE_TEMPDIR, under Workdir, which is hopefully under tmpfs. Thanks @the-Going for this. + declare -g -x XDG_RUNTIME_DIR="${WORKDIR}/xdg_tmp" # XDG_RUNTIME_DIR is used by the likes of systemd/freedesktop centric apps. start=$(date +%s) @@ -247,10 +250,12 @@ function trap_handler_cleanup_workdir() { unset TMPDIR if [[ -d "${WORKDIR}" ]]; then if [[ "${PRESERVE_WORKDIR}" != "yes" ]]; then - display_alert "Cleaning up WORKDIR" "$(du -h -s "$WORKDIR")" "debug" - rm -rf "${WORKDIR}" + display_alert "Cleaning up WORKDIR" "$(du -h -s "$WORKDIR")" "cleanup" + # Remove all files and directories in WORKDIR, but not WORKDIR itself. + rm -rf "${WORKDIR:?}"/* # Note this is protected by :? else display_alert "Preserving WORKDIR due to PRESERVE_WORKDIR=yes" "$(du -h -s "$WORKDIR")" "warn" + # @TODO: tmpfs might just be unmounted, though. fi fi } diff --git a/lib/library-functions.sh b/lib/library-functions.sh index fe7fb8730..da3bd3774 100644 --- a/lib/library-functions.sh +++ b/lib/library-functions.sh @@ -550,6 +550,15 @@ set -o errexit ## set -e : exit the script if any statement returns a non-true # shellcheck source=lib/functions/host/prepare-host.sh source "${SRC}"/lib/functions/host/prepare-host.sh +# no errors tolerated. invoked before each sourced file to make sure. +#set -o pipefail # trace ERR through pipes - will be enabled "soon" +#set -o nounset ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled +set -o errtrace # trace ERR through - enabled +set -o errexit ## set -e : exit the script if any statement returns a non-true return value - enabled +### lib/functions/host/tmpfs-utils.sh +# shellcheck source=lib/functions/host/tmpfs-utils.sh +source "${SRC}"/lib/functions/host/tmpfs-utils.sh + # no errors tolerated. invoked before each sourced file to make sure. #set -o pipefail # trace ERR through pipes - will be enabled "soon" #set -o nounset ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled