Files
bpfire/tools/make-functions
Arne Fitzenreiter fbe365b955 make-functions: move MAKETUNING before make
this is needed to edit MAKTUNING in the lfs file
which is used by boost and cmake to build on
machines with less than 4GB Memory.

Signed-off-by: Arne Fitzenreiter <arne_f@ipfire.org>
2017-07-08 15:31:15 +02:00

729 lines
19 KiB
Bash

#!/bin/bash
###############################################################################
# #
# IPFire.org - A linux based firewall #
# Copyright (C) 2007-2016 IPFire Team <info@ipfire.org> #
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
###############################################################################
#
# Beautifying variables & presentation & input output interface
#
###############################################################################
## Screen Dimentions
# Find current screen size
if [ -z "${COLUMNS}" ]; then
COLUMNS=$(stty size)
COLUMNS=${COLUMNS##* }
fi
# When using remote connections, such as a serial port, stty size returns 0
if [ "${COLUMNS}" = "0" ]; then
COLUMNS=80
fi
## Measurements for positioning result messages
RESULT_WIDTH=4
TIME_WIDTH=8
OPT_WIDTH=6
VER_WIDTH=10
RESULT_COL=$((${COLUMNS} - $RESULT_WIDTH - 4))
TIME_COL=$((${RESULT_COL} - $TIME_WIDTH - 5))
OPT_COL=$((${TIME_COL} - $OPT_WIDTH - 5))
VER_COL=$((${OPT_COL} - $VER_WIDTH - 5))
## Set Cursur Position Commands, used via echo -e
SET_RESULT_COL="\\033[${RESULT_COL}G"
SET_TIME_COL="\\033[${TIME_COL}G"
SET_OPT_COL="\\033[${OPT_COL}G"
SET_VER_COL="\\033[${VER_COL}G"
# Define color for messages
BOLD="\\033[1;39m"
DONE="\\033[1;32m"
SKIP="\\033[1;34m"
WARN="\\033[1;35m"
FAIL="\\033[1;31m"
NORMAL="\\033[0;39m"
configure_build() {
local build_arch="${1}"
if [ "${build_arch}" = "default" ]; then
build_arch="$(configure_build_guess)"
fi
case "${build_arch}" in
x86_64)
BUILDTARGET="${build_arch}-unknown-linux-gnu"
CROSSTARGET="${build_arch}-cross-linux-gnu"
BUILD_PLATFORM="x86"
CFLAGS_ARCH="-m64 -mtune=generic"
;;
i586)
BUILDTARGET="${build_arch}-pc-linux-gnu"
CROSSTARGET="${build_arch}-cross-linux-gnu"
BUILD_PLATFORM="x86"
CFLAGS_ARCH="-march=i586 -mtune=generic -fomit-frame-pointer"
;;
aarch64)
BUILDTARGET="${build_arch}-unknown-linux-gnu"
CROSSTARGET="${build_arch}-cross-linux-gnu"
BUILD_PLATFORM="arm"
CFLAGS_ARCH=""
;;
armv7hl)
BUILDTARGET="${build_arch}-unknown-linux-gnueabi"
CROSSTARGET="${build_arch}-cross-linux-gnueabi"
BUILD_PLATFORM="arm"
CFLAGS_ARCH="-march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard"
;;
armv5tel)
BUILDTARGET="${build_arch}-unknown-linux-gnueabi"
CROSSTARGET="${build_arch}-cross-linux-gnueabi"
BUILD_PLATFORM="arm"
CFLAGS_ARCH="-march=armv5te -mfloat-abi=soft -fomit-frame-pointer"
;;
*)
exiterror "Cannot build for architure ${build_arch}"
;;
esac
# Check if the QEMU helper is available if needed.
if qemu_is_required "${build_arch}"; then
local qemu_build_helper="$(qemu_find_build_helper_name "${build_arch}")"
if [ -n "${qemu_build_helper}" ]; then
QEMU_TARGET_HELPER="${qemu_build_helper}"
else
exiterror "Could not find a binfmt_misc helper entry for ${build_arch}"
fi
fi
BUILD_ARCH="${build_arch}"
# Enables hardening
HARDENING_CFLAGS="-Wp,-D_FORTIFY_SOURCE=2 -fstack-protector-strong --param=ssp-buffer-size=4"
CFLAGS="-O2 -pipe -Wall -fexceptions -fPIC ${CFLAGS_ARCH}"
CXXFLAGS="${CFLAGS}"
}
configure_build_guess() {
case "${HOST_ARCH}" in
x86_64|i686|i586)
echo "i586"
;;
aarch64)
echo "aarch64"
;;
armv7*)
echo "armv7hl"
;;
armv6*|armv5*)
echo "armv5tel"
;;
*)
exiterror "Cannot guess build architecture"
;;
esac
}
evaluate() {
if [ "$?" -eq "0" ]; then
beautify message DONE
else
EXITCODE=$1
shift 1
beautify message FAIL
$*
if [ $EXITCODE -ne "0" ]; then
exit $EXITCODE
fi
fi
}
position_cursor()
{
# ARG1=starting position on screen
# ARG2=string to be printed
# ARG3=offset, negative for left movement, positive for right movement, relative to ARG1
# For example if your starting position is column 50 and you want to print Hello three columns to the right
# of your starting position, your call will look like this:
# position_cursor 50 "Hello" 3 (you'll get the string Hello at position 53 (= 50 + 3)
# If on the other hand you want your string "Hello" to end three columns to the left of position 50,
# your call will look like this:
# position_cursor 50 "Hello" -3 (you'll get the string Hello at position 42 (= 50 - 5 -3)
# If you want to start printing at the exact starting location, use offset 0
START=$1
STRING=$2
OFFSET=$3
STRING_LENGTH=${#STRING}
if [ ${OFFSET} -lt 0 ]; then
COL=$((${START} + ${OFFSET} - ${STRING_LENGTH}))
else
COL=$((${START} + ${OFFSET}))
fi
SET_COL="\\033[${COL}G"
echo $SET_COL
} # End of position_cursor()
beautify()
{
# Commands: build_stage, make_pkg, message, result
case "$1" in
message)
case "$2" in
DONE)
echo -ne "${SET_RESULT_COL}[${DONE} DONE ${NORMAL}]\n"
;;
WARN)
echo -ne "${WARN}${3}${NORMAL}${SET_RESULT_COL}[${WARN} WARN ${NORMAL}]\n"
;;
FAIL)
echo -ne "${SET_RESULT_COL}[${FAIL} FAIL ${NORMAL}]\n"
;;
SKIP)
echo -ne "${SET_RESULT_COL}[${SKIP} SKIP ${NORMAL}]\n"
;;
esac
;;
build_stage)
MESSAGE=$2
if [ "$STAGE_TIME_START" ]; then
LAST_STAGE_TIME=$[ `date +%s` - $STAGE_TIME_START ]
fi
STAGE_TIME_START=`date +%s`
echo -ne "${BOLD}*** (${BUILD_ARCH}) ${MESSAGE}${NORMAL}"
if [ "$LAST_STAGE_TIME" ]; then
echo -ne "${DONE} (Last stage took $LAST_STAGE_TIME secs)${NORMAL}\n"
fi
echo -ne "${BOLD}${SET_VER_COL} version${SET_OPT_COL} options${SET_TIME_COL} time (sec)${SET_RESULT_COL} status${NORMAL}\n"
;;
build_start)
BUILD_TIME_START=`date +%s`
;;
build_end)
BUILD_TIME_END=`date +%s`
seconds=$[ $BUILD_TIME_END - $BUILD_TIME_START ]
hours=$((seconds / 3600))
seconds=$((seconds % 3600))
minutes=$((seconds / 60))
seconds=$((seconds % 60))
echo -ne "${DONE}***Build is finished now and took $hours hour(s) $minutes minute(s) $seconds second(s)!${NORMAL}\n"
;;
make_pkg)
echo "$2" | while read PKG_VER PROGRAM OPTIONS
do
SET_VER_COL_REAL=`position_cursor $OPT_COL $PKG_VER -3`
if [ "$OPTIONS" == "" ]; then
echo -ne "${PROGRAM}${SET_VER_COL}[ ${BOLD}${SET_VER_COL_REAL}${PKG_VER}"
echo -ne "${NORMAL} ]${SET_RESULT_COL}"
else
echo -ne "${PROGRAM}${SET_VER_COL}[ ${BOLD}${SET_VER_COL_REAL}${PKG_VER}"
echo -ne "${NORMAL} ]${SET_OPT_COL}[ ${BOLD}${OPTIONS}"
echo -ne "${NORMAL} ]${SET_RESULT_COL}"
fi
done
;;
result)
RESULT=$2
if [ ! $3 ]; then
PKG_TIME=0
else
PKG_TIME=$3
fi
SET_TIME_COL_REAL=`position_cursor $RESULT_COL $PKG_TIME -3`
case "$RESULT" in
DONE)
echo -ne "${SET_TIME_COL}[ ${BOLD}${SET_TIME_COL_REAL}$PKG_TIME${NORMAL} ]"
echo -ne "${SET_RESULT_COL}[${DONE} DONE ${NORMAL}]\n"
;;
FAIL)
echo -ne "${SET_TIME_COL}[ ${BOLD}${SET_TIME_COL_REAL}$PKG_TIME${NORMAL} ]"
echo -ne "${SET_RESULT_COL}[${FAIL} FAIL ${NORMAL}]\n"
;;
SKIP)
echo -ne "${SET_RESULT_COL}[${SKIP} SKIP ${NORMAL}]\n"
;;
esac
;;
esac
} # End of beautify()
get_pkg_ver()
{
PKG_VER=`grep -E "^VER |^VER=|^VER " $1 | awk '{print $3}'`
if [ -z $PKG_VER ]; then
PKG_VER=`grep "Exp " $1 | awk '{print $4}'`
fi
if [ -z $PKG_VER ]; then
PKG_VER="?"
fi
if [ ${#PKG_VER} -gt $VER_WIDTH ]; then
# If a package version number is greater than $VER_WIDTH, we keep the first 4 characters
# and replace enough characters to fit the resulting string on the screen. We'll replace
# the extra character with .. (two dots). That's why the "+ 2" in the formula below.
# Example: if we have a 21-long version number that we want to fit into a 10-long space,
# we have to remove 11 characters. But if we replace 11 characters with 2 characters, we'll
# end up with a 12-character long string. That's why we replace 12 characters with ..
REMOVE=`expr substr "$PKG_VER" 4 $[ ${#PKG_VER} - $VER_WIDTH + 2 ]`
PKG_VER=`echo ${PKG_VER/$REMOVE/..}`
fi
echo "$PKG_VER"
} # End of get_pkg_ver()
# Define immediately
stdumount() {
umount $BASEDIR/build/sys 2>/dev/null;
umount $BASEDIR/build/dev/shm 2>/dev/null;
umount $BASEDIR/build/dev/pts 2>/dev/null;
umount $BASEDIR/build/dev 2>/dev/null;
umount $BASEDIR/build/proc 2>/dev/null;
umount $BASEDIR/build/install/mnt 2>/dev/null;
umount $BASEDIR/build/usr/src/cache 2>/dev/null;
umount $BASEDIR/build/usr/src/ccache 2>/dev/null;
umount $BASEDIR/build/usr/src/config 2>/dev/null;
umount $BASEDIR/build/usr/src/doc 2>/dev/null;
umount $BASEDIR/build/usr/src/html 2>/dev/null;
umount $BASEDIR/build/usr/src/langs 2>/dev/null;
umount $BASEDIR/build/usr/src/lfs 2>/dev/null;
umount $BASEDIR/build/usr/src/log 2>/dev/null;
umount $BASEDIR/build/usr/src/src 2>/dev/null;
}
exiterror() {
stdumount
for i in `seq 0 7`; do
if ( losetup /dev/loop${i} 2>/dev/null | grep -q "/install/images" ); then
losetup -d /dev/loop${i} 2>/dev/null
fi;
done
if [ -n "${LOGFILE}" ]; then
echo # empty line
local line
while read -r line; do
echo " ${line}"
done <<< "$(tail -n30 ${LOGFILE})"
fi
echo -e "\nERROR: $*"
echo " Check $LOGFILE for errors if applicable"
exit 1
}
fake_environ() {
[ -e "${BASEDIR}/build/tools/lib/libpakfire_preload.so" ] || return
local env="LD_PRELOAD=/tools/lib/libpakfire_preload.so"
# Fake kernel version, because some of the packages do not compile
# with kernel 3.0 and later.
env="${env} UTS_RELEASE=${KVER}"
# Fake machine version.
env="${env} UTS_MACHINE=${BUILD_ARCH}"
echo "${env}"
}
qemu_environ() {
local env
# Don't add anything if qemu is not used.
if ! qemu_is_required; then
return
fi
# Set default qemu options
case "${BUILD_ARCH}" in
arm*)
QEMU_CPU="${QEMU_CPU:-cortex-a9}"
env="${env} QEMU_CPU=${QEMU_CPU}"
;;
esac
# Enable QEMU strace
#env="${env} QEMU_STRACE=1"
echo "${env}"
}
qemu_is_required() {
local build_arch="${1}"
if [ -z "${build_arch}" ]; then
build_arch="${BUILD_ARCH}"
fi
case "${HOST_ARCH},${build_arch}" in
x86_64,arm*|i?86,arm*|i?86,x86_64)
return 0
;;
*)
return 1
;;
esac
}
qemu_install_helper() {
# Do nothing, if qemu is not required
if ! qemu_is_required; then
return 0
fi
if [ ! -e /proc/sys/fs/binfmt_misc/status ]; then
exiterror "binfmt_misc not mounted. QEMU_TARGET_HELPER not useable."
fi
if [ ! $(cat /proc/sys/fs/binfmt_misc/status) = 'enabled' ]; then
exiterror "binfmt_misc not enabled. QEMU_TARGET_HELPER not useable."
fi
if [ -z "${QEMU_TARGET_HELPER}" ]; then
exiterror "QEMU_TARGET_HELPER not set"
fi
# Check if the helper is already installed.
if [ -x "${LFS}${QEMU_TARGET_HELPER}" ]; then
return 0
fi
# Try to find a suitable binary that we can install
# to the build environment.
local file
for file in "${QEMU_TARGET_HELPER}" "${QEMU_TARGET_HELPER}-static"; do
# file must exist and be executable.
[ -x "${file}" ] || continue
# Must be static.
file_is_static "${file}" || continue
local dirname="${LFS}$(dirname "${file}")"
mkdir -p "${dirname}"
install -m 755 "${file}" "${LFS}${QEMU_TARGET_HELPER}"
return 0
done
exiterror "Could not find a statically-linked QEMU emulator: ${QEMU_TARGET_HELPER}"
}
qemu_find_build_helper_name() {
local build_arch="${1}"
local magic
case "${build_arch}" in
arm*)
magic="7f454c4601010100000000000000000002002800"
;;
x86_64)
magic="7f454c4602010100000000000000000002003e00"
;;
esac
[ -z "${magic}" ] && return 1
local file
for file in /proc/sys/fs/binfmt_misc/*; do
# skip write only register entry
[ $(basename "${file}") = "register" ] && continue
# Search for the file with the correct magic value.
grep -qE "^magic ${magic}$" "${file}" || continue
local interpreter="$(grep "^interpreter" "${file}" | awk '{ print $2 }')"
[ -n "${interpreter}" ] || continue
[ "${interpreter:0:1}" = "/" ] || continue
[ -x "${interpreter}" ] || continue
echo "${interpreter}"
return 0
done
return 1
}
file_is_static() {
local file="${1}"
file ${file} 2>/dev/null | grep -q "statically linked"
}
enterchroot() {
# Install QEMU helper, if needed
qemu_install_helper
local PATH="/tools/ccache/bin:/bin:/usr/bin:/sbin:/usr/sbin:/tools/bin"
PATH="${PATH}" chroot ${LFS} env -i \
HOME="/root" \
TERM="${TERM}" \
PS1="${PS1}" \
PATH="${PATH}" \
SYSTEM_RELEASE="${SYSTEM_RELEASE}" \
PAKFIRE_CORE="${PAKFIRE_CORE}" \
NAME="${NAME}" \
SNAME="${SNAME}" \
VERSION="${VERSION}" \
CORE="${CORE}" \
SLOGAN="${SLOGAN}" \
CONFIG_ROOT="${CONFIG_ROOT}" \
CFLAGS="${CFLAGS} ${HARDENING_CFLAGS}" \
CXXFLAGS="${CXXFLAGS} ${HARDENING_CFLAGS}" \
BUILDTARGET="${BUILDTARGET}" \
CROSSTARGET="${CROSSTARGET}" \
BUILD_ARCH="${BUILD_ARCH}" \
BUILD_PLATFORM="${BUILD_PLATFORM}" \
CCACHE_DIR=/usr/src/ccache \
CCACHE_COMPRESS="${CCACHE_COMPRESS}" \
CCACHE_COMPILERCHECK="${CCACHE_COMPILERCHECK}" \
KVER="${KVER}" \
$(fake_environ) \
$(qemu_environ) \
"$@"
}
entershell() {
if [ ! -e $BASEDIR/build/usr/src/lfs/ ]; then
exiterror "No such file or directory: $BASEDIR/build/usr/src/lfs/"
fi
echo "Entering to a shell inside LFS chroot, go out with exit"
local PS1="ipfire build chroot ($(uname -m)) \u:\w\$ "
if enterchroot bash -i; then
stdumount
else
beautify message FAIL
exiterror "chroot error"
fi
}
############################################################################
# #
# Necessary shell functions #
# #
############################################################################
#
# Common checking before entering the chroot and compilling
#
# Return:0 caller can continue
# :1 skip (nothing to do)
# or fail if no script file found
#
lfsmakecommoncheck()
{
# Script present?
if [ ! -f $BASEDIR/lfs/$1 ]; then
exiterror "No such file or directory: $BASEDIR/$1"
fi
local PKG_VER=`get_pkg_ver $BASEDIR/lfs/$1`
beautify make_pkg "$PKG_VER $*"
# Check if this package is supported by our architecture.
# If no SUP_ARCH is found, we assume the package can be built for all.
if grep "^SUP_ARCH" ${BASEDIR}/lfs/${1} >/dev/null; then
# Check if package supports ${BUILD_ARCH} or all architectures.
if ! grep -E "^SUP_ARCH.*${BUILD_ARCH}|^SUP_ARCH.*all" ${BASEDIR}/lfs/${1} >/dev/null; then
beautify result SKIP
return 1
fi
fi
# Script slipped?
local i
for i in $SKIP_PACKAGE_LIST
do
if [ "$i" == "$1" ]; then
beautify result SKIP
return 1;
fi
done
echo -ne "`date -u '+%b %e %T'`: Building $* " >> $LOGFILE
cd $BASEDIR/lfs && make -s -f $* LFS_BASEDIR=$BASEDIR BUILD_ARCH="${BUILD_ARCH}" \
MESSAGE="$1\t " download >> $LOGFILE 2>&1
if [ $? -ne 0 ]; then
exiterror "Download error in $1"
fi
cd $BASEDIR/lfs && make -s -f $* LFS_BASEDIR=$BASEDIR BUILD_ARCH="${BUILD_ARCH}" \
MESSAGE="$1\t md5sum" md5 >> $LOGFILE 2>&1
if [ $? -ne 0 ]; then
exiterror "md5sum error in $1, check file in cache or signature"
fi
return 0 # pass all!
} # End of lfsmakecommoncheck()
lfsmake1() {
lfsmakecommoncheck $*
[ $? == 1 ] && return 0
local PKG_TIME_START=`date +%s`
cd $BASEDIR/lfs && env -i \
PATH="/tools/ccache/bin:/tools/bin:$PATH" \
CCACHE_DIR="${CCACHE_DIR}" \
CCACHE_COMPRESS="${CCACHE_COMPRESS}" \
CCACHE_COMPILERCHECK="${CCACHE_COMPILERCHECK}" \
CFLAGS="${CFLAGS}" \
CXXFLAGS="${CXXFLAGS}" \
MAKETUNING="${MAKETUNING}" \
make -f $* \
TOOLCHAIN=1 \
CROSSTARGET="${CROSSTARGET}" \
BUILDTARGET="${BUILDTARGET}" \
BUILD_ARCH="${BUILD_ARCH}" \
BUILD_PLATFORM="${BUILD_PLATFORM}" \
LFS_BASEDIR="${BASEDIR}" \
ROOT="${LFS}" \
KVER="${KVER}" \
install >> $LOGFILE 2>&1
local COMPILE_SUCCESS=$?
local PKG_TIME_END=`date +%s`
if [ $COMPILE_SUCCESS -ne 0 ]; then
beautify result FAIL $[ $PKG_TIME_END - $PKG_TIME_START ]
exiterror "Building $*";
else
beautify result DONE $[ $PKG_TIME_END - $PKG_TIME_START ]
fi
return 0
}
lfsmake2() {
lfsmakecommoncheck $*
[ $? == 1 ] && return 0
local PKG_TIME_START=`date +%s`
local PS1='\u:\w$ '
enterchroot \
bash -x -c "cd /usr/src/lfs && \
MAKETUNING=${MAKETUNING} \
make -f $* \
LFS_BASEDIR=/usr/src install" \
>> ${LOGFILE} 2>&1
local COMPILE_SUCCESS=$?
local PKG_TIME_END=`date +%s`
if [ $COMPILE_SUCCESS -ne 0 ]; then
beautify result FAIL $[ $PKG_TIME_END - $PKG_TIME_START ]
exiterror "Building $*";
else
beautify result DONE $[ $PKG_TIME_END - $PKG_TIME_START ]
fi
return 0
}
ipfiredist() {
lfsmakecommoncheck $*
[ $? == 1 ] && return 0
local PKG_TIME_START=`date +%s`
local PS1='\u:\w$ '
enterchroot \
bash -x -c "cd /usr/src/lfs && make -f $* LFS_BASEDIR=/usr/src dist" \
>> ${LOGFILE} 2>&1
local COMPILE_SUCCESS=$?
local PKG_TIME_END=`date +%s`
if [ $COMPILE_SUCCESS -ne 0 ]; then
beautify result FAIL $[ $PKG_TIME_END - $PKG_TIME_START ]
exiterror "Packaging $*";
else
beautify result DONE $[ $PKG_TIME_END - $PKG_TIME_START ]
fi
return 0
}
update_langs() {
echo -ne "Checking the translations for missing or obsolete strings..."
chmod 755 $BASEDIR/tools/{check_strings.pl,sort_strings.pl,check_langs.sh}
$BASEDIR/tools/sort_strings.pl en
$BASEDIR/tools/sort_strings.pl de
$BASEDIR/tools/sort_strings.pl fr
$BASEDIR/tools/sort_strings.pl es
$BASEDIR/tools/sort_strings.pl pl
$BASEDIR/tools/sort_strings.pl ru
$BASEDIR/tools/sort_strings.pl nl
$BASEDIR/tools/sort_strings.pl tr
$BASEDIR/tools/sort_strings.pl it
$BASEDIR/tools/check_strings.pl en > $BASEDIR/doc/language_issues.en
$BASEDIR/tools/check_strings.pl de > $BASEDIR/doc/language_issues.de
$BASEDIR/tools/check_strings.pl fr > $BASEDIR/doc/language_issues.fr
$BASEDIR/tools/check_strings.pl es > $BASEDIR/doc/language_issues.es
$BASEDIR/tools/check_strings.pl es > $BASEDIR/doc/language_issues.pl
$BASEDIR/tools/check_strings.pl ru > $BASEDIR/doc/language_issues.ru
$BASEDIR/tools/check_strings.pl nl > $BASEDIR/doc/language_issues.nl
$BASEDIR/tools/check_strings.pl tr > $BASEDIR/doc/language_issues.tr
$BASEDIR/tools/check_strings.pl it > $BASEDIR/doc/language_issues.it
$BASEDIR/tools/check_langs.sh > $BASEDIR/doc/language_missings
beautify message DONE
echo -ne "Updating language lists..."
update_language_list ${BASEDIR}/src/installer/po
update_language_list ${BASEDIR}/src/setup/po
beautify message DONE
}
update_language_list() {
local path="${1}"
local lang
for lang in ${path}/*.po; do
lang="$(basename "${lang}")"
echo "${lang%*.po}"
done | sort -u > "${path}/LINGUAS"
}