#!/bin/bash # Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. TEXTDOMAIN=cl_kernel CL_KERNEL_VERSION=0.2.6 DESCRIPTION=$"Kernel building tool" DEFAULT_KERNEL_DIRECTORY="$(readlink -f /usr/src/linux)" SRC_DIRECTORY=/usr/src LOCAL_TEMPLATES_DIR=/var/calculate/templates/kernel TEMPLATES_BACKUP=${LOCAL_TEMPLATES_DIR}/backup DEBUG_LOG=/var/log/calculate/cl-kernel.log KVER= KERNEL_DIR=${DEFAULT_KERNEL_DIRECTORY} # создавать базовую конфигурацию ядра # create the base kernel configuration CREATE_BASE=1 # создавать текущую конфигурацию ядра # create the current kernel configuration CREATE_NEW=1 # собирать ядро после конфигурации # compile the kernel once configuration is complete BUILD_KERNEL=1 # очистить исходники ядра перед сборкой # clean the kernel sources before the compilation CLEAN_KERNEL=1 # запускать изменение конфигурации ядра пользователем # execute manual kernel configuration MANUAL_CONFIG=1 # права на файл шаблона # template file privileges CHMOD=0644 # наличие dracut в системе # dracut present or not in the system DRACUT=$(which dracut 2>/dev/null) declare -a TAILOUT=() # поддержка настроек ccache # support ccache source <(cat /etc/portage/make.conf/* | grep CCACHE_DIR) export CCACHE_DIR if [[ -n $CCACHE_DIR ]] then export PATH="/usr/lib/ccache/bin:/lib/rc/bin:$PATH" else export PATH="/lib/rc/bin:$PATH" fi # проверить пользователя # check user if [[ $(id -u) -ne 0 ]] then eerror $"This program must be run as root" exit 1 fi # прервать скрипт в случае ошибки любой из команд # break the script execution in case of a command error set -e : >$DEBUG_LOG # вывод короткой справки # show the short help message usage() { echo $"Usage: $0 [OPTION] Version: $CL_KERNEL_VERSION ${DESCRIPTION} -h, --help display all options and exit" } # вывод полной справки # show the long help message long_usage() { echo $"Usage: $0 [OPTION] Version: $CL_KERNEL_VERSION ${DESCRIPTION} --kver [VERSION] specify the kernel version ('list' for displaying possible values) --kver-old [VERSION] specify the kernel version for new options ('list' for displaying possible values) --convert migrate .config from the kernel directory to templates -s, --skip-build do not build the kernel after configuration --skip-config do not manual configure the kernel --noclean do not clean the kernel sources before the compilation --march [ARCH] kernel architecture (x86 or x86_64) --safemode create an additional initrd with all modules (only for calculate-sources) --help display this help and exit " } # подготовить параметры командной строки # prepare the commmand line parameters rearrange_params() { set +e TEMP=$(unset POSIXLY_CORRECT; getopt \ -o "hs" \ --long help \ --long kver: \ --long kver-old: \ --long march: \ --long convert \ --long noclean \ --long skip-build \ --long skip-config \ --long safemode \ -- "$@" 2>&1) if (( $? != 0 )); then echo "$TEMP" | sed 's/getopt: /cl-kernel: /;$d' exit 1 fi set -e } # выполнить параметры командной строки # apply the command line parameters do_args() { while :; do case $1 in -h|--help) long_usage exit 0 ;; --kver) KVER="$2" shift ;; --kver-old) KVER_OLD="$2" shift ;; --safemode) SAFEMODE=1 ;; --march) MARCH="$2" if [[ ${MARCH} != "x86" ]] && [[ ${MARCH} != "x86_64" ]] then eerror $"Error in parameter --march. The value may be 'x86' or 'x86_64'" fi shift ;; --convert) MIGRATE=1 CREATE_NEW= ;; -s|--skip-build) BUILD_KERNEL= ;; --skip-config) MANUAL_CONFIG= ;; --noclean) CLEAN_KERNEL= ;; --) shift;break;; *) usage; eerror $"Unknown option: $1" ;; esac shift done if [[ -n $1 ]] then usage; eerror $"Unknown argument: $1" fi } # получить значение переменной calculate # get the value of variable 'calculate' variable_value() { local varname=$1 /usr/libexec/calculate/cl-variable --value $varname } # оставить только названия параметров + "=" # keep parameter names only + "=" options_name() { sed -r 's/^# (CON.*) is not set.*$/\1=/' | sed -r 's/^(CON.*=).*/\1/' } # преобразовать опции в синтаксис удаления параметра # convert the options into parameter removal syntax remove_syntax() { sed -r 's/^(.*=).*$/!\1/' } # преобразовать CONFIG_XXX=n -> # CONFIG_XXX is not set # make the conversion # CONFIG_XXX is not set -> CONFIG_XXX=n n2not_set() { sed -r 's/(CONFIG.*)=n/# \1 is not set/' } # получить разницу в конфигурационных файлах # get a difference of configuration files diff_config() { diff -u <(grep CONFIG_ $1 | sort | n2not_set) <(grep CONFIG_ $2 | sort | n2not_set) } # получить разницу в параметрах конфигурационных файлов # get paramters difference of configuration files diff_config_options() { diff -u <(cat $1 | options_name | sort) <(cat $2 | options_name | sort) } # изменённые параметры # changed parameters append_options() { diff_config $1 $2 | grep -e "^+CON" -e "^+# CON" | sed 's/^.//' | sort } # удаленные параметры # removed parameters removed_options() { diff_config_options $1 $2 | grep -e "^-CON" | sed 's/^.//' | sort } # получить содержимое шаблона # get the template contents diff_template_body() { append_options $1 $2 removed_options $1 $2 | remove_syntax } # вывести заголов для шаблона # show the template headers # Args: # category/package name # package version diff_template_head() { local category_pn=$1 local pv=$2 echo "# Calculate format=kernel name=.config os_install_arch_machine==${TEMPLATE_ARCH}&&merge(${category_pn})>=${pv}" } # вывести полный шаблон # show the full template # Args: # category/package name # package version # base configuration file # new configuration file create_template() { local category_pn=$1 local pv=$2 local base_config=$3 local new_config=$4 diff_template_head ${category_pn} ${pv} diff_template_body ${base_config} ${new_config} } # получить конфигурацию ядра # get the kernel configuration # Args: # kernel sources directory # category/package name # package version # used templates (locations) create_kernel_config() { local kernel_dir=$1 local category_pn=( ${2/\// } ) local category=${category_pn[0]} local pn=${category_pn[1]} local pv=$3 # создать временную директорию для выполнения шаблонов # create temporary directory for templates applying local tempdir=$(${MKTEMP} -d) [[ -n $4 ]] && local templates="-T $4" # получить конфигурацию ядра по умолчанию, нужной архитектуры # get default kernel configuration for architecture local temp_config=".config_clkernel_${ARCH}" (cd ${kernel_dir};ARCH=$MARCH KCONFIG_CONFIG=${temp_config} make defconfig;mv ${temp_config} ${tempdir}/.config) &>>$DEBUG_LOG || eerror $"Failed to create the default kernel config" # получить тип системы if [[ "$(variable_value main.os_linux_system)" == "desktop" ]] then USEVALUE=desktop else USEVALUE=-desktop fi # выполнить шаблоны (патчи) # apply templates (patches) USE="$USEVALUE" /usr/sbin/cl-core --method core_patch --march=$TEMPLATE_ARCH --pkg-name ${pn} --pkg-category ${category} --pkg-version=${pv} --pkg-slot=${pv} --pkg-path=$tempdir $templates &>>$DEBUG_LOG || eerror $"Failed to apply kernel templates" # вывести содержимое файла конфигурации ядра # display content of kernel configuration file cat $tempdir/.config || eerror $"Kernel configuration file not found" rm -rf $tempdir &>>$DEBUG_LOG } # проверить, содержит ли каталог полный исходный код ядра # check if the directory contains the full kernel sources check_kernel_sources() { local sources_dir=$1 [[ -f ${sources_dir}/arch/x86/configs/i386_defconfig ]] } # версия исходного кода ядра # kernel sources version sources_ver() { basename "$(readlink -f $1)" | sed 's/linux-//' || true } # текущее ядро # current_kernel current_kernel() { sources_ver /usr/src/linux } # вывести список версий ядер с полным исходным кодом # list all kernels with full sources available list_kernel() { local asterisk=$(echo -e "\033[1m*\033[0m") local green_asterisk=$(echo -e "\033[1;32m*\033[0m") local red_asterisk=$(echo -e "\033[1;31m*\033[0m") local curver=$1 for f in $(ls -drv /usr/src/linux-[[:digit:]]*); do local ver=$(sources_ver "${f}") [[ $ver == $curver ]] && mark=$asterisk || mark= check_kernel_sources $f && echo " ${green_asterisk}" $ver $mark || echo " ${red_asterisk}" $ver $mark done } # вывести сообщение и добавить его в список выводимых после сборки ядра сообщений # show the message and add it to the list of messages to be displayed after compilation einfo_tail() { einfo $* TAILOUT+=( "$*" ) } # получить содержимое текущего конфига # get the content of current kernel configuration # .config into kernel sources directory # /boot/config- # /proc/config.gz get_old_config() { local source_config="${KERNEL_DIR}/.config" local boot_config="/boot/config-$(uname -r)" local proc_config="/proc/config.gz" if [[ -f $source_config ]] then config=$source_config elif [[ -f $boot_config ]] then config=$boot_config cp $config $NEW_CONFIG elif [[ -f $proc_config ]] then config=$proc_config zcat $proc_config >$NEW_CONFIG fi if [[ -n $config ]] then einfo $"Will be used $config kernel configuration" else eerror $"Failed to get current kernel configuration" fi } # qfile hack _qfile() { (cd /; qfile $*) } qfile_pkgname() { _qfile $* | sed -r 's/:? .*$//;' } # проверить принадлежит ли директория только одному пакету # check that the directory belongs to only one package check_belong() { local fn=$1 local linenum=$(_qfile -C $fn | wc -l) if [[ $linenum -lt 1 ]] then eerror $"${fn} does not belong to any package" elif [[ $linenum -gt 1 ]] then eerror $"${fn} belongs to multiple packages" fi return 0 } real_kernel_version() { local kdir=$1 file $kdir/arch/x86/boot/bzImage | sed -r 's/.*version\s(\S+)\s.*/\1/' } ###################### # Обработать параметры # Process the options ###################### rearrange_params "$@" eval set -- "$TEMP" do_args "$@" ######################## # Подготовить переменные # Prepare variables ######################## [[ "$(variable_value main.cl_chroot_status)" == "off" ]] && NOT_CHROOT=1 # вычислить архитектуру # detect the architecture [[ -z $MARCH ]] && MARCH=$(/usr/bin/arch) if [[ "$MARCH" == "x86" ]] || [[ "$MARCH" == "i686" ]] then # архитектура для конфигурации по умолчанию # default configuration architecture MARCH=i386 # templates architecture # архитектура для шаблонов TEMPLATE_ARCH=i686 # название архитектуры # architecture name NAME_ARCH=x86 else TEMPLATE_ARCH=$MARCH NAME_ARCH=$MARCH fi # подготовить имя для шаблона очистить старые временные данные # prepare the template name; clear old temporary data TMP_TEMPLATE=/tmp/cl_kernel_${MARCH} MKTEMP="/usr/bin/mktemp ${TMP_TEMPLATE}.XXXXXX" rm -rf ${TMP_TEMPLATE}* # пропустить сборку ядра если выбранная архитектура и архитектура машины отличаются # skip the kernel compilation if selected architecture is different from machine architecture if [[ $TEMPLATE_ARCH != $(/usr/bin/arch) ]] then OTHER_ARCH=1 BUILD_KERNEL= fi # вывести список доступных ядер # list avaiable kernels if [[ $KVER == "list" ]] || [[ $KVER_OLD == "list" ]] then list_kernel $(current_kernel) exit 0 fi # получить директорию ядра по версии # get the kernel directory by version if [[ -n $KVER ]] then KERNEL_DIR=${SRC_DIRECTORY}/linux-${KVER} fi if [[ -n $KVER_OLD ]] then KERNEL_OLD_DIR=${SRC_DIRECTORY}/linux-${KVER_OLD} fi # проверить правильность исходников # check the integrity of the sources for check_dir in ${KERNEL_DIR} ${KERNEL_OLD_DIR} do [[ -d ${check_dir} ]] || eerror $"Kernel directory ${check_dir} not found" check_kernel_sources ${check_dir} || eerror $"Kernel directory ${check_dir} has no full sources" done # получить версию из директории ядра # get the version from the kernel directory if [[ -z $KVER ]] then KVER=$(sources_ver $KERNEL_DIR) fi # проверка доступности safemode # check if safemode is available if [[ -n $SAFEMODE ]] && [[ -z $NOT_CHROOT ]] then eerror $"--safemode unavailable for chroot" fi if [[ -n $SAFEMODE ]] && ! [[ $KVER =~ -calculate ]] then eerror $"--safemode available for calculate-sources only" fi if [[ -n $SAFEMODE ]] && [[ -z $DRACUT ]] then eerror $"--safemode unavailable without dracut" fi # создать каталог в локальных шаблонах для шаблонов ядра # create the directory for kernel templates in the local tempalte tree if ! [[ -d $LOCAL_TEMPLATES_DIR ]] then (mkdir -p $LOCAL_TEMPLATES_DIR ; echo "# Calculate env=install ac_install_patch==on append=skip" >${LOCAL_TEMPLATES_DIR}/.calculate_directory) || eerror $"Failed to create the kernel template directory" fi # если другая архитектура # if other architecture if [[ -n $OTHER_ARCH ]] then NEW_CONFIG=${KERNEL_DIR}/.config_${TEMPLATE_ARCH} else NEW_CONFIG=${KERNEL_DIR}/.config fi check_belong ${KERNEL_DIR}/Makefile # получение параметров пакета, которому принадлежат исходники # getting the parameters for the package whose sources are being processed CATEGORY_PN=$( qfile_pkgname -C ${KERNEL_DIR}/Makefile ) PV=$( qfile_pkgname -Cv ${KERNEL_DIR}/Makefile ) PV=${PV/$CATEGORY_PN-/} if [[ -n $KERNEL_OLD_DIR ]] then check_belong ${KERNEL_OLD_DIR}/Makefile CATEGORY_PN_OLD=$( qfile_pkgname -C ${KERNEL_OLD_DIR}/Makefile ) PV_OLD=$( qfile_pkgname -Cv ${KERNEL_OLD_DIR}/Makefile ) PV_OLD=${PV_OLD/${CATEGORY_PN_OLD}-/} fi # получить версии MAJOR.MINOR для условий в шаблонах # get the version (MAJOR.MINOR format) for conditions in templates [[ $KVER =~ ^([[:digit:]]+\.[[:digit:]]+) ]] && PV2=${BASH_REMATCH[0]} || PV2=$PV # определение имени шаблонов для пакета # define templates name for the package if [[ $CATEGORY_PN =~ ^.*/(.*)-sources ]] then TEMPLATE_NAME_PREFIX=10-${BASH_REMATCH[1]}- else TEMPLATE_NAME_PREFIX=10-config- fi TEMPLATE_NAME_ARCH_PREFIX=${TEMPLATE_NAME_PREFIX}${NAME_ARCH}- TEMPLATE_NAME="${LOCAL_TEMPLATES_DIR}/${TEMPLATE_NAME_ARCH_PREFIX}${PV2}" ######################################## # Подготовка новой конфигурации ядра # Preparing the new kernel configuration ######################################## CONFIG_GZ=/proc/config.gz if [[ -n $CREATE_NEW ]] then ebegin $"Preparing the current kernel configuration" create_kernel_config ${KERNEL_OLD_DIR:-${KERNEL_DIR}} \ ${CATEGORY_PN_OLD:-${CATEGORY_PN}} \ ${PV_OLD:-${PV}} >$NEW_CONFIG eend else if [[ -n $OTHER_ARCH ]] then eerror $"--convert is uncompatible with --march" fi if [[ -n $KVER_OLD ]] then eerror $"--convert is uncompatible with --kver-old" fi get_old_config fi ######################################### # Подготовка базовой конфигурации ядра # Preparing the base kernel configuration ######################################### BASE_CONFIG=$( ${MKTEMP} ) ebegin $"Preparing the basic kernel configuration" if [[ -n $CREATE_BASE ]] then # будут использоваться только шаблоны оверлеев # will be used the overlay templates only TEMPLATES=$(variable_value main.cl_template_location) create_kernel_config ${KERNEL_DIR} ${CATEGORY_PN} ${PV} ${TEMPLATES/,local,remote/} >$BASE_CONFIG else cp $NEW_CONFIG $BASE_CONFIG fi eend ########################################### # Изменение конфигурации ядра пользователем # Manual kernel configuration ########################################### if [[ -n $MANUAL_CONFIG ]] then (cd $KERNEL_DIR; [[ -n ${KERNEL_OLD_DIR} || -z ${CREATE_NEW} ]] && KCONFIG_CONFIG=$(basename $NEW_CONFIG) make oldconfig;KCONFIG_CONFIG=$(basename $NEW_CONFIG) make -s menuconfig) || true fi ########################### # Создание шаблона # Template creation ########################### NEW_TEMPLATE=$( ${MKTEMP} ) create_template $CATEGORY_PN $PV2 $BASE_CONFIG $NEW_CONFIG >${NEW_TEMPLATE} ################################## # Создание резервной копии шаблона # Template backup ################################## if ls ${LOCAL_TEMPLATES_DIR}/${TEMPLATE_NAME_ARCH_PREFIX}* &>/dev/null then for i in ${LOCAL_TEMPLATES_DIR}/${TEMPLATE_NAME_ARCH_PREFIX}* do if diff -u $i $NEW_TEMPLATE &>/dev/null then einfo_tail $"The kernel configuration has not changed" rm $i SKIP_CREATE_INFO=1 else newname="$(basename $i)-$(date +%Y%m%d_%H%M -r $i)" einfo_tail $"Backing up the template" "$(basename $i) -> ${newname}" if ! [[ -d ${TEMPLATES_BACKUP} ]] then (mkdir -p ${TEMPLATES_BACKUP} && echo "# Calculate cl_action==skip" >${TEMPLATES_BACKUP}/.calculate_directory) || eerror $"Failed to create a backup directory" fi mv $i ${TEMPLATES_BACKUP}/${newname} fi done fi # пропуск создания пустого шаблона # skip the empty template if [[ $(sed 1d $NEW_TEMPLATE | wc -l) -gt 0 ]] then mv $NEW_TEMPLATE $TEMPLATE_NAME chmod ${CHMOD} $TEMPLATE_NAME if [[ -z $SKIP_CREATE_INFO ]] then einfo_tail $"Creating the template" "$(basename $TEMPLATE_NAME)" fi else einfo_tail $"Skipping the empty template" fi rm -f $BASE_CONFIG STARTTIME=$(date +%s) #################### # Сборка ядра # Kernel compilation #################### if [[ -n ${BUILD_KERNEL} ]] then cd $KERNEL_DIR ( MAKEOPTS=$(variable_value install.os_install_makeopts) echo -- $MAKEOPTS set -e if [[ -n ${CLEAN_KERNEL} ]] then make clean || eerror $"Failed to clean the kernel sources" || exit 1 fi make $MAKEOPTS || eerror $"Failed to compile the kernel" || exit 1 make $MAKEOPTS modules_install || eerror $"Failed to compile the kernel modules" || exit 1 make $MAKEOPTS install || eerror $"Failed to install the kernel" || exit 1 ) || eerror $"Failed to build kernel" # использовать сжатие если поддерживается ядром (zstd,gzip,xz) if grep -q "CONFIG_RD_ZSTD=y" ${NEW_CONFIG} then COMPRESS=--zstd elif grep -q "CONFIG_RD_GZIP=y" ${NEW_CONFIG} then COMPRESS=--gzip elif grep -q "CONFIG_RD_XZ=y" ${NEW_CONFIG} then COMPRESS=--xz fi # сборка initramfs # initramfs building VMLINUZ_VER=$(real_kernel_version $KERNEL_DIR) if [[ -n $DRACUT ]] && grep -q "CONFIG_BLK_DEV_INITRD=y" ${NEW_CONFIG} then ${DRACUT} -f${NOT_CHROOT:+H} $COMPRESS --kver=$VMLINUZ_VER /boot/initramfs-${VMLINUZ_VER}.img || eerror $"Failed to create the host-only initramfs" if [[ $KVER =~ calculate ]] && [[ -n $SAFEMODE ]] then ${DRACUT} -f $COMPRESS --kver=$VMLINUZ_VER /boot/initramfs-${VMLINUZ_VER/-calculate/-SafeMode-calculate}.img || eerror $"Failed to create the safemode initramfs" fi else # удалить старый ramfs если его поддержка выключена в ядре # remove the old ramfs if its support disabled in the kernel configuration rm -f /boot/initramfs-${VMLINUZ_VER}.img /boot/initramfs-${VMLINUZ_VER/-calculate/-SafeMode-calculate}.img fi # выполнение шаблонов для настройки загрузки системы # applying the templates for boot configuration if [[ -n $NOT_CHROOT ]] then cl-setup-boot fi # вывод времени компиляции ядра # display kernel compilation time DELTATIME=$(( $(date +%s) - $STARTTIME )) HOUR=$(( $DELTATIME / 3600 )) DELTATIME=$(( $DELTATIME % 3600 )) MIN=$(( $DELTATIME / 60 )) SEC=$(( $DELTATIME % 60 )) echo -en " \033[1;32m*\033[0m " echo -n $"Kernel build time: " if [[ ${HOUR} -gt 0 ]] then printf $"%d hour %d min %d sec\n" ${HOUR} ${MIN} ${SEC} else printf $"%d min %d sec\n" ${MIN} ${SEC} fi # вывод информационных сообщений, отображённых до сборки ядра # display info messages, show before kernel compilation for line in "${TAILOUT[@]}" do einfo $line done # rebuild kernel modules if kernel directory is /usr/src/linux if [[ "$(readlink -f ${DEFAULT_KERNEL_DIRECTORY})" == "$(readlink -f $KERNEL_DIR)" ]] then ewarn $"To rebuild kernel modules packages, please run:" ewarn "" ewarn " emerge -a @module-rebuild" fi fi einfo $"All done!"