Philipp Deppenwiese (zaolin.daisuki@googlemail.com) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/15170
-gerrit
commit d4bed779caeb2a97f6c1070287513ddc9a736d9a Author: Philipp Deppenwiese zaolin@das-labor.org Date: Mon Jun 13 17:01:07 2016 -0700
util/crossgcc: Implement buildgcc script checksum verification
In order to build a secure toolchain https and checksum verification should be used for the sources.
The hash alogrithm has changed from SHA1 to SHA384 for security reasons.
Add gpg verification for file hash upgrades by developers.
Change-Id: Id45097fc7e8435fadce4bda75769592af2730b0c Signed-off-by: Philipp Deppenwiese zaolin@das-labor.org --- util/crossgcc/buildgcc | 134 ++++++++++++++++----- util/crossgcc/gpg.keyring | Bin 0 -> 222182 bytes util/crossgcc/sum/Python-3.5.1.tar.xz.cksum | 2 +- .../sum/acpica-unix2-20160318.tar.gz.cksum | 2 +- util/crossgcc/sum/binutils-2.26.tar.bz2.cksum | 2 +- util/crossgcc/sum/cfe-3.8.0.src.tar.xz.cksum | 2 +- .../sum/clang-tools-extra-3.8.0.src.tar.xz.cksum | 2 +- .../sum/compiler-rt-3.8.0.src.tar.xz.cksum | 2 +- util/crossgcc/sum/expat-2.1.1.tar.bz2.cksum | 2 +- util/crossgcc/sum/gcc-5.3.0.tar.bz2.cksum | 2 +- util/crossgcc/sum/gdb-7.11.tar.xz.cksum | 2 +- util/crossgcc/sum/gmp-6.1.0.tar.xz.cksum | 2 +- util/crossgcc/sum/libelf-0.8.13.tar.gz.cksum | 2 +- util/crossgcc/sum/llvm-3.8.0.src.tar.xz.cksum | 2 +- util/crossgcc/sum/make-4.1.tar.bz2.cksum | 2 +- util/crossgcc/sum/mpc-1.0.3.tar.gz.cksum | 2 +- util/crossgcc/sum/mpfr-3.1.4.tar.xz.cksum | 2 +- util/lint/lint-stable-003-whitespace | 2 +- 18 files changed, 118 insertions(+), 48 deletions(-)
diff --git a/util/crossgcc/buildgcc b/util/crossgcc/buildgcc index 1e761da..2285b27 100755 --- a/util/crossgcc/buildgcc +++ b/util/crossgcc/buildgcc @@ -47,26 +47,28 @@ EXPAT_VERSION=2.1.1 CLANG_VERSION=3.8.0 MAKE_VERSION=4.1
+GPG_KEYRING=$(pwd)/gpg.keyring + # GCC toolchain archive locations # These are sanitized by the jenkins toolchain test builder, so if # a completely new URL is added here, it probably needs to be added # to the jenkins build as well, or the builder won't download it. -GMP_ARCHIVE="http://ftpmirror.gnu.org/gmp/gmp-$%7BGMP_VERSION%7D.tar.xz" -MPFR_ARCHIVE="http://ftpmirror.gnu.org/mpfr/mpfr-$%7BMPFR_VERSION%7D.tar.xz" -MPC_ARCHIVE="http://ftpmirror.gnu.org/mpc/mpc-$%7BMPC_VERSION%7D.tar.gz" +GMP_ARCHIVE="https://ftp.gnu.org/gnu/gmp/gmp-$%7BGMP_VERSION%7D.tar.xz" +MPFR_ARCHIVE="https://ftp.gnu.org/gnu/mpfr/mpfr-$%7BMPFR_VERSION%7D.tar.xz" +MPC_ARCHIVE="https://ftp.gnu.org/gnu/mpc/mpc-$%7BMPC_VERSION%7D.tar.gz" LIBELF_ARCHIVE="http://www.mr511.de/software/libelf-$%7BLIBELF_VERSION%7D.tar.gz" -GCC_ARCHIVE="http://ftpmirror.gnu.org/gcc/gcc-$%7BGCC_VERSION%7D/gcc-$%7BGCC_VERSION%7D.t..." -BINUTILS_ARCHIVE="http://ftpmirror.gnu.org/binutils/binutils-$%7BBINUTILS_VERSION%7D.tar.bz2" -GDB_ARCHIVE="http://ftpmirror.gnu.org/gdb/gdb-$%7BGDB_VERSION%7D.tar.xz" +GCC_ARCHIVE="https://ftp.gnu.org/gnu/gcc/gcc-$%7BGCC_VERSION%7D/gcc-$%7BGCC_VERSION%7D.ta..." +BINUTILS_ARCHIVE="https://ftp.gnu.org/gnu/binutils/binutils-$%7BBINUTILS_VERSION%7D.tar.bz2" +GDB_ARCHIVE="https://ftp.gnu.org/gnu/gdb/gdb-$%7BGDB_VERSION%7D.tar.xz" IASL_ARCHIVE="https://acpica.org/sites/acpica/files/acpica-unix2-$%7BIASL_VERSION%7D.tar.g..." -PYTHON_ARCHIVE="http://www.python.org/ftp/python/$%7BPYTHON_VERSION%7D/Python-$%7BPYTHON_VER..." -EXPAT_ARCHIVE="http://downloads.sourceforge.net/sourceforge/expat/expat-$%7BEXPAT_VERSION%7..." +PYTHON_ARCHIVE="https://www.python.org/ftp/python/$%7BPYTHON_VERSION%7D/Python-$%7BPYTHON_VE..." +EXPAT_ARCHIVE="https://downloads.sourceforge.net/sourceforge/expat/expat-$%7BEXPAT_VERSION%..." # CLANG toolchain archive locations LLVM_ARCHIVE="http://llvm.org/releases/$%7BCLANG_VERSION%7D/llvm-$%7BCLANG_VERSION%7D.src...." CFE_ARCHIVE="http://llvm.org/releases/$%7BCLANG_VERSION%7D/cfe-$%7BCLANG_VERSION%7D.src.t..." CRT_ARCHIVE="http://llvm.org/releases/$%7BCLANG_VERSION%7D/compiler-rt-$%7BCLANG_VERSION%..." CTE_ARCHIVE="http://llvm.org/releases/$%7BCLANG_VERSION%7D/clang-tools-extra-$%7BCLANG_VE..." -MAKE_ARCHIVE="http://ftpmirror.gnu.org/make/make-$%7BMAKE_VERSION%7D.tar.bz2" +MAKE_ARCHIVE="https://ftp.gnu.org/gnu/make/make-$%7BMAKE_VERSION%7D.tar.bz2"
ALL_ARCHIVES="$GMP_ARCHIVE $MPFR_ARCHIVE $MPC_ARCHIVE $LIBELF_ARCHIVE \ $GCC_ARCHIVE $BINUTILS_ARCHIVE $GDB_ARCHIVE $IASL_ARCHIVE \ @@ -202,50 +204,114 @@ check_for_library() { rm -rf "${LIBTEST_FILE}.c" "${LIBTEST_FILE}" }
-check_sum() { - test -z "$CHECKSUM" || \ - test "$(cat sum/$1.cksum 2>/dev/null | sed -e 's@.*([0-9a-f]{40,}).*@\1@')" = \ - "$($CHECKSUM tarballs/$1 2>/dev/null | sed -e 's@.*([0-9a-f]{40,}).*@\1@')" +verify_signature() { + if [ -f "sum/${2}.cksum" ] ; then + return + fi + + if [ -z "${CHECKSUM}" ] ; then + printf "\n${RED}Checksum tool not found.${NC}\n" + exit 1 + fi + + if [ -z "${GPG}" ] ; then + printf "\n${RED}GPG tool not found.${NC}\n" + exit 1 + fi + + if [ ! -f "${GPG_KEYRING}" ] ; then + printf "\n${RED}The gpg keyring can't be found not found.${NC}\n" + exit 1 + fi + + if [ ! -f "tarballs/${1}" ] ; then + printf "\n${RED}No signature found for package: ${2} !! PLEASE CHECK MANUALLY WHAT YOU DOWNLOADED.${NC}\n" + return + fi + + if [ ! -f "tarballs/${2}" ] ; then + printf "\n${RED}Source file couldn't be downloaded.${NC}\n" + exit 1 + fi + + ${GPG} --no-default-keyring --keyring ${GPG_KEYRING} --status-fd 1 --verify "tarballs/${1}" "tarballs/${2}" &>/dev/null + if [ "${?}" == "0" ] ; then + return + fi + + printf "\n${RED}Failed to verify signature of package.${NC}\n" + exit 1 }
-compute_sum() { +calculate_checksum() { test ! -f sum/$1.cksum && test -f tarballs/$1 && \ (test -z "$CHECKSUM" || $CHECKSUM tarballs/$1 > sum/$1.cksum ) && \ - printf "(checksum created. ${RED}Note. Please upload sum/$1.cksum if the corresponding archive is upgraded.)${NC}" + printf "\n(checksum created. ${RED}Note. Please upload sum/$1.cksum if the corresponding archive is upgraded.${NC})" +} + +verify_checksum() { + if [ -z "${CHECKSUM}" ] ; then + printf "\n${RED}Checksum tool not found.${NC}\n" + exit 1 + fi + + local static_hash=$(cat sum/$1.cksum 2>/dev/null | sed -e 's@.*([0-9a-f]{40,}).*@\1@') + local file_hash=$($CHECKSUM tarballs/$1 2>/dev/null | sed -e 's@.*([0-9a-f]{40,}).*@\1@') + + if [ "${static_hash}" != "${file_hash}" ] ; then + printf "\n${RED}Failed to verify checksum of package ${1}.${NC}\n" + exit 1 + fi +} + +validate_url() { + if [[ `wget -S --spider $1 2>&1 | grep 'HTTP/1.1 200 OK'` ]]; then return 1; else return 0; fi }
download_showing_percentage() { url=$1 - printf " ..${red} 0%%" - wget --no-check-certificate $url 2>&1 | while read line; do - printf "${red}" - echo $line | grep -o "[0-9]+%" | awk '{printf("\b\b\b\b%4s", $1)}' - printf "${NC}" - done + printf " ..${red} 0%%${NC}" + + validate_url ${url} + if [ "${?}" == "1" ] ; then + wget $url 2>&1 | while read line; do + printf "${red}" + echo $line | grep -o "[0-9]+%" | awk '{printf("\b\b\b\b%4s", $1)}' + printf "${NC}" + done + fi }
download() { package=$1 archive="$(eval echo $$package"_ARCHIVE")" + signature="${archive}.sig"
- FILE=$(basename $archive) - printf " * $FILE " + SOURCE_FILE=$(basename ${archive}) + SIGNATURE_FILE=$(basename ${signature}) + printf " * ${SOURCE_FILE} "
- if test -f tarballs/$FILE && check_sum $FILE ; then + if test -f tarballs/${SOURCE_FILE} ; then printf "(cached)" else printf "(downloading from $archive)" - rm -f tarballs/$FILE + rm -f tarballs/${SOURCE_FILE} cd tarballs - download_showing_percentage $archive + download_showing_percentage ${archive} + download_showing_percentage ${signature} cd .. - compute_sum $FILE + + verify_signature ${SIGNATURE_FILE} ${SOURCE_FILE} + calculate_checksum ${SOURCE_FILE} fi
- if [ ! -f tarballs/$FILE ]; then - printf "\n${RED}Failed to download $FILE.${NC}\n" + if [ ! -f tarballs/${SOURCE_FILE} ]; then + printf "\n${RED}Failed to download ${SOURCE_FILE}.${NC}\n" exit 1 fi + + verify_checksum ${SOURCE_FILE} ${archive} + printf "\n" }
@@ -253,6 +319,10 @@ unpack_and_patch() { package=$1 archive="$(eval echo $$package"_ARCHIVE")" dir="$(eval echo $$package"_DIR")" + FILE="$(basename $archive)" + + verify_checksum ${FILE} ${archive} + test -d ${dir} && test -f ${dir}/.unpack_success || ( printf " * $(basename $archive)\n" FLAGS=zxf @@ -723,11 +793,11 @@ esac TAR=$(searchtool tar) || exit $? PATCH=$(searchtool patch) || exit $? MAKE=$(searchtool make) || exit $? -SHA1SUM=$(searchtool sha1sum) -SHA512SUM=$(searchtool sha512sum) -CHECKSUM=$SHA1SUM +SHA384SUM=$(searchtool sha384sum) +CHECKSUM=$SHA384SUM LBZIP2=$(searchtool lbzip2 "" nofail) PIGZ=$(searchtool pigz "" nofail) +GPG=$(searchtool gpg "GnuPG" nofail)
searchtool m4 > /dev/null searchtool bison > /dev/null diff --git a/util/crossgcc/gpg.keyring b/util/crossgcc/gpg.keyring new file mode 100644 index 0000000..8cbce7e Binary files /dev/null and b/util/crossgcc/gpg.keyring differ diff --git a/util/crossgcc/sum/Python-3.5.1.tar.xz.cksum b/util/crossgcc/sum/Python-3.5.1.tar.xz.cksum index 58affb0..5093e22 100644 --- a/util/crossgcc/sum/Python-3.5.1.tar.xz.cksum +++ b/util/crossgcc/sum/Python-3.5.1.tar.xz.cksum @@ -1 +1 @@ -0186da436db76776196612b98bb9c2f76acfe90e tarballs/Python-3.5.1.tar.xz +9aca49d2cde4760035f4fff23e8a446f6451e8effb6e6fab3f259645a631593acb2ce3412aadf810f103f3f6b561abe7 tarballs/Python-3.5.1.tar.xz diff --git a/util/crossgcc/sum/acpica-unix2-20160318.tar.gz.cksum b/util/crossgcc/sum/acpica-unix2-20160318.tar.gz.cksum index 2f9b331..793a794 100644 --- a/util/crossgcc/sum/acpica-unix2-20160318.tar.gz.cksum +++ b/util/crossgcc/sum/acpica-unix2-20160318.tar.gz.cksum @@ -1 +1 @@ -786811c1501b9cc90c988293a1c3afefa33b60cc tarballs/acpica-unix2-20160318.tar.gz +73305954ec571a19fd1ced884f5ce951b9a6c183faf111408fefddb1628ccc5aabcc9200d5f0619eb248f3d803e6ae98 tarballs/acpica-unix2-20160318.tar.gz diff --git a/util/crossgcc/sum/binutils-2.26.tar.bz2.cksum b/util/crossgcc/sum/binutils-2.26.tar.bz2.cksum index ac0a3c3..4d99d04 100644 --- a/util/crossgcc/sum/binutils-2.26.tar.bz2.cksum +++ b/util/crossgcc/sum/binutils-2.26.tar.bz2.cksum @@ -1 +1 @@ -a637508cffeb2323b14bc8dd65378819768ad4ff tarballs/binutils-2.26.tar.bz2 +ff22e865d7f4ac6fb5d408f92cbc0dcc749a36ee8c5f19a0292fc3d6f08e00f852e04734c47c9a578f092c6963b46878 tarballs/binutils-2.26.tar.bz2 diff --git a/util/crossgcc/sum/cfe-3.8.0.src.tar.xz.cksum b/util/crossgcc/sum/cfe-3.8.0.src.tar.xz.cksum index 7ba8c8c..b278aac 100644 --- a/util/crossgcc/sum/cfe-3.8.0.src.tar.xz.cksum +++ b/util/crossgcc/sum/cfe-3.8.0.src.tar.xz.cksum @@ -1 +1 @@ -2230ef962f2df3c13ec93f5b04b0e3cdff94b2ce tarballs/cfe-3.8.0.src.tar.xz +3edbe3cafc58ad6fc3d3a133156a656ea7756e8a36176b29bba63434d6f073cef63241cc6df8337fcab3832dd5b5c485 tarballs/cfe-3.8.0.src.tar.xz diff --git a/util/crossgcc/sum/clang-tools-extra-3.8.0.src.tar.xz.cksum b/util/crossgcc/sum/clang-tools-extra-3.8.0.src.tar.xz.cksum index e9d8d72..dd5c566 100644 --- a/util/crossgcc/sum/clang-tools-extra-3.8.0.src.tar.xz.cksum +++ b/util/crossgcc/sum/clang-tools-extra-3.8.0.src.tar.xz.cksum @@ -1 +1 @@ -a99d8b6fc5e593c4671424b327779318a1856acf tarballs/clang-tools-extra-3.8.0.src.tar.xz +abd447239f09295f8592225bb7e07ba57cedc3d19a4972b45cd7d70a2db82106d1874a5be03656f8209a0e8a1308fb31 tarballs/clang-tools-extra-3.8.0.src.tar.xz diff --git a/util/crossgcc/sum/compiler-rt-3.8.0.src.tar.xz.cksum b/util/crossgcc/sum/compiler-rt-3.8.0.src.tar.xz.cksum index 081705d..9239365 100644 --- a/util/crossgcc/sum/compiler-rt-3.8.0.src.tar.xz.cksum +++ b/util/crossgcc/sum/compiler-rt-3.8.0.src.tar.xz.cksum @@ -1 +1 @@ -480ea09e369dac6de1f3759b27fa19417b26b69e tarballs/compiler-rt-3.8.0.src.tar.xz +4560d512f3c3a0dbcc5c29ab7754b07d91ffa1ddc1c06a5215f3d325bc3f138ebf483e2a37abb10fc36cd125e6bf559e tarballs/compiler-rt-3.8.0.src.tar.xz diff --git a/util/crossgcc/sum/expat-2.1.1.tar.bz2.cksum b/util/crossgcc/sum/expat-2.1.1.tar.bz2.cksum index 882e501..11800c1 100644 --- a/util/crossgcc/sum/expat-2.1.1.tar.bz2.cksum +++ b/util/crossgcc/sum/expat-2.1.1.tar.bz2.cksum @@ -1 +1 @@ -ff91419882ac52151050dad0ee8190645fbeee08 tarballs/expat-2.1.1.tar.bz2 +816e8ca9ac57b6aac76e9979d11714313246492a49c228bb0a16d349174be45cd780d760524eb45acf12ff371e8b96a3 tarballs/expat-2.1.1.tar.bz2 diff --git a/util/crossgcc/sum/gcc-5.3.0.tar.bz2.cksum b/util/crossgcc/sum/gcc-5.3.0.tar.bz2.cksum index bb05e39..2b81f5e 100644 --- a/util/crossgcc/sum/gcc-5.3.0.tar.bz2.cksum +++ b/util/crossgcc/sum/gcc-5.3.0.tar.bz2.cksum @@ -1 +1 @@ -0612270b103941da08376df4d0ef4e5662a2e9eb tarballs/gcc-5.3.0.tar.bz2 +4184972f66dc929686c30936cf8f5085829d8584118ebc3e2ec53a40754afbefc351cfd2c211c09dd3b6eb4ab4759820 tarballs/gcc-5.3.0.tar.bz2 diff --git a/util/crossgcc/sum/gdb-7.11.tar.xz.cksum b/util/crossgcc/sum/gdb-7.11.tar.xz.cksum index ffe5a1c..9cc5d91 100644 --- a/util/crossgcc/sum/gdb-7.11.tar.xz.cksum +++ b/util/crossgcc/sum/gdb-7.11.tar.xz.cksum @@ -1 +1 @@ -466208d771d97d3dfcf965d5c835a669cff8d847 tarballs/gdb-7.11.tar.xz +82f89ef35ea1916d5faa67b7fefa2ac3627894d245c43fb4e20828274566cbbb5d7f83899baad77a3cf7fe9fec6f8c7b tarballs/gdb-7.11.tar.xz diff --git a/util/crossgcc/sum/gmp-6.1.0.tar.xz.cksum b/util/crossgcc/sum/gmp-6.1.0.tar.xz.cksum index 348b80f..7e00489 100644 --- a/util/crossgcc/sum/gmp-6.1.0.tar.xz.cksum +++ b/util/crossgcc/sum/gmp-6.1.0.tar.xz.cksum @@ -1 +1 @@ -99d691607613e749aa5d7c0c2a89aeab38fec070 tarballs/gmp-6.1.0.tar.xz +ec69b394cbcf887ab0717473b04bb70ce30102abf24869eca5acb781de405d3a5670fabb776ba68cfe6e117631522d9f tarballs/gmp-6.1.0.tar.xz diff --git a/util/crossgcc/sum/libelf-0.8.13.tar.gz.cksum b/util/crossgcc/sum/libelf-0.8.13.tar.gz.cksum index daa27c6..0d3f568 100644 --- a/util/crossgcc/sum/libelf-0.8.13.tar.gz.cksum +++ b/util/crossgcc/sum/libelf-0.8.13.tar.gz.cksum @@ -1 +1 @@ -c1d6ac5f182d19dd685c4dfd74eedbfe3992425d tarballs/libelf-0.8.13.tar.gz +07fc0b1a40ba3d2b003899df199d1043bb5d44cf7d913e6460c985582e5275b634edb6af6779255d770010fd7d09580e tarballs/libelf-0.8.13.tar.gz diff --git a/util/crossgcc/sum/llvm-3.8.0.src.tar.xz.cksum b/util/crossgcc/sum/llvm-3.8.0.src.tar.xz.cksum index 2f0af53..547ad8f 100644 --- a/util/crossgcc/sum/llvm-3.8.0.src.tar.xz.cksum +++ b/util/crossgcc/sum/llvm-3.8.0.src.tar.xz.cksum @@ -1 +1 @@ -723ac918979255706434a05f5af34b71c49c9971 tarballs/llvm-3.8.0.src.tar.xz +5d5a012e4d494a4534fcec7643ad28bab7467b96029b6d497e1d523357ae7c9e8bd86400dc90e261d791d6391a369b30 tarballs/llvm-3.8.0.src.tar.xz diff --git a/util/crossgcc/sum/make-4.1.tar.bz2.cksum b/util/crossgcc/sum/make-4.1.tar.bz2.cksum index 02807c6..455d779 100644 --- a/util/crossgcc/sum/make-4.1.tar.bz2.cksum +++ b/util/crossgcc/sum/make-4.1.tar.bz2.cksum @@ -1 +1 @@ -0d701882fd6fd61a9652cb8d866ad7fc7de54d58 tarballs/make-4.1.tar.bz2 +8a3adbc49c19db130bfd3c2b7df061d9419b77019d21d23765cf1c961bc6a35d611dbda4241316e98cecc09adccecd90 tarballs/make-4.1.tar.bz2 diff --git a/util/crossgcc/sum/mpc-1.0.3.tar.gz.cksum b/util/crossgcc/sum/mpc-1.0.3.tar.gz.cksum index c7ca1ac..70ee62f 100644 --- a/util/crossgcc/sum/mpc-1.0.3.tar.gz.cksum +++ b/util/crossgcc/sum/mpc-1.0.3.tar.gz.cksum @@ -1 +1 @@ -b8be66396c726fdc36ebb0f692ed8a8cca3bcc66 tarballs/mpc-1.0.3.tar.gz +dceeb2566d6145fa771641d22de3bef866fa601fe1fa964bc680a275919080bd12bc1abab4f68cecdbaef71d295b7397 tarballs/mpc-1.0.3.tar.gz diff --git a/util/crossgcc/sum/mpfr-3.1.4.tar.xz.cksum b/util/crossgcc/sum/mpfr-3.1.4.tar.xz.cksum index 90f90eb..54cd485 100644 --- a/util/crossgcc/sum/mpfr-3.1.4.tar.xz.cksum +++ b/util/crossgcc/sum/mpfr-3.1.4.tar.xz.cksum @@ -1 +1 @@ -cedc0055d55b6ee4cd17e1e6119ed412520ff81a tarballs/mpfr-3.1.4.tar.xz +30a2807378f578544538810b24bb65d44268015e5d2390d8ae6383fe3939b3bba1e85279789400a226b365010de4bbcd tarballs/mpfr-3.1.4.tar.xz diff --git a/util/lint/lint-stable-003-whitespace b/util/lint/lint-stable-003-whitespace index 3dbd473..144cda1 100755 --- a/util/lint/lint-stable-003-whitespace +++ b/util/lint/lint-stable-003-whitespace @@ -15,5 +15,5 @@ # DESCR: Check for superfluous whitespace in the tree
LC_ALL=C export LC_ALL -grep -l "[[:space:]][[:space:]]*$" `git ls-files src util |egrep -v "(^3rdparty|^src/vendorcode/|^util/kconfig/|^util/nvidia/cbootimage$|<COPYING>|<LICENSE>|<README>|_shipped$|.patch$|.bin$|.hex$|.jpg$)"` | \ +grep -l "[[:space:]][[:space:]]*$" `git ls-files src util |egrep -v "(^3rdparty|^src/vendorcode/|^util/kconfig/|^util/nvidia/cbootimage$|<COPYING>|<LICENSE>|<README>|_shipped$|.keyring$|.patch$|.bin$|.hex$|.jpg$)"` | \ sed -e "s,^.*$,File & has lines ending with whitespace.,"