scripts/functions
author "Yann E. MORIN" <yann.morin.1998@anciens.enib.fr>
Wed Dec 30 15:36:22 2009 +0100 (2009-12-30)
changeset 1669 61edd9d19e3c
parent 1668 383c37e754df
child 1672 636126117b8c
permissions -rw-r--r--
scripts/functions: add aria2, a powerfull downloader

aria2 is a powerfull downloader that is capable of chunking and
parallel retrieval.

Due to li;itations in crosstool-NG retrieval facilities, it's not possible
to take fully advantage of aria2. It might happen that, in the future,
those limitations get lifted away, so we can take use features such as
parallel downloading from more than one server at the same time. For now,
it should still speed up downloads thanks to parallel downloading of chunks.
     1 # This file contains some usefull common functions
     2 # Copyright 2007 Yann E. MORIN
     3 # Licensed under the GPL v2. See COPYING in the root of this package
     4 
     5 # Prepare the fault handler
     6 CT_OnError() {
     7     ret=$?
     8     # Bail out early in subshell, the upper level shell will act accordingly.
     9     [ ${BASH_SUBSHELL} -eq 0 ] || exit $ret
    10     CT_DoLog ERROR "Build failed in step '${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}'"
    11     for((step=(CT_STEP_COUNT-1); step>1; step--)); do
    12         CT_DoLog ERROR "      called in step '${CT_STEP_MESSAGE[${step}]}'"
    13     done
    14     CT_DoLog ERROR "Error happened in '${BASH_SOURCE[1]}' in function '${FUNCNAME[1]}' (line unknown, sorry)"
    15     for((depth=2; ${BASH_LINENO[$((${depth}-1))]}>0; depth++)); do
    16         CT_DoLog ERROR "      called from '${BASH_SOURCE[${depth}]}' at line # ${BASH_LINENO[${depth}-1]} in function '${FUNCNAME[${depth}]}'"
    17     done
    18     [ "${CT_LOG_TO_FILE}" = "y" ] && CT_DoLog ERROR "Look at '${CT_LOG_FILE}' for more info on this error."
    19     CT_STEP_COUNT=1
    20     CT_DoEnd ERROR
    21     exit $ret
    22 }
    23 
    24 # Install the fault handler
    25 trap CT_OnError ERR
    26 
    27 # Inherit the fault handler in subshells and functions
    28 set -E
    29 
    30 # Make pipes fail on the _first_ failed command
    31 # Not supported on bash < 3.x, but we need it, so drop the obsoleting bash-2.x
    32 set -o pipefail
    33 
    34 # Don't hash commands' locations, and search every time it is requested.
    35 # This is slow, but needed because of the static/shared core gcc which shall
    36 # always match to shared if it exists, and only fallback to static if the
    37 # shared is not found
    38 set +o hashall
    39 
    40 # Log policy:
    41 #  - first of all, save stdout so we can see the live logs: fd #6
    42 exec 6>&1
    43 #  - then point stdout to the log file (temporary for now)
    44 tmp_log_file="${CT_TOP_DIR}/log.$$"
    45 exec >>"${tmp_log_file}"
    46 
    47 # The different log levels:
    48 CT_LOG_LEVEL_ERROR=0
    49 CT_LOG_LEVEL_WARN=1
    50 CT_LOG_LEVEL_INFO=2
    51 CT_LOG_LEVEL_EXTRA=3
    52 CT_LOG_LEVEL_DEBUG=4
    53 CT_LOG_LEVEL_ALL=5
    54 
    55 # Make it easy to use \n and !
    56 CR=$(printf "\n")
    57 BANG='!'
    58 
    59 # A function to log what is happening
    60 # Different log level are available:
    61 #   - ERROR:   A serious, fatal error occurred
    62 #   - WARN:    A non fatal, non serious error occurred, take your responsbility with the generated build
    63 #   - INFO:    Informational messages
    64 #   - EXTRA:   Extra informational messages
    65 #   - DEBUG:   Debug messages
    66 #   - ALL:     Component's build messages
    67 # Usage: CT_DoLog <level> [message]
    68 # If message is empty, then stdin will be logged.
    69 CT_DoLog() {
    70     local max_level LEVEL level cur_l cur_L
    71     local l
    72     eval max_level="\${CT_LOG_LEVEL_${CT_LOG_LEVEL_MAX}}"
    73     # Set the maximum log level to DEBUG if we have none
    74     [ -z "${max_level}" ] && max_level=${CT_LOG_LEVEL_DEBUG}
    75 
    76     LEVEL="$1"; shift
    77     eval level="\${CT_LOG_LEVEL_${LEVEL}}"
    78 
    79     if [ $# -eq 0 ]; then
    80         cat -
    81     else
    82         printf "${*}\n"
    83     fi |( IFS="${CR}" # We want the full lines, even leading spaces
    84           _prog_bar_cpt=0
    85           _prog_bar[0]='/'
    86           _prog_bar[1]='-'
    87           _prog_bar[2]='\'
    88           _prog_bar[3]='|'
    89           indent=$((2*CT_STEP_COUNT))
    90           while read line; do
    91               case "${CT_LOG_SEE_TOOLS_WARN},${line}" in
    92                 y,*"warning:"*)         cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
    93                 y,*"WARNING:"*)         cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
    94                 *"error:"*)             cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
    95                 *"make["*"]: *** ["*)   cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
    96                 *)                      cur_L="${LEVEL}"; cur_l="${level}";;
    97               esac
    98               # There will always be a log file (stdout, fd #1), be it /dev/null
    99               printf "[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}"
   100               if [ ${cur_l} -le ${max_level} ]; then
   101                   # Only print to console (fd #6) if log level is high enough.
   102                   printf "\r[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}" >&6
   103               fi
   104               if [ "${CT_LOG_PROGRESS_BAR}" = "y" ]; then
   105                   printf "\r[%02d:%02d] %s " $((SECONDS/60)) $((SECONDS%60)) "${_prog_bar[$((_prog_bar_cpt/10))]}" >&6
   106                   _prog_bar_cpt=$(((_prog_bar_cpt+1)%40))
   107               fi
   108           done
   109         )
   110 
   111     return 0
   112 }
   113 
   114 # Execute an action, and log its messages
   115 # Usage: [VAR=val...] CT_DoExecLog <level> <command [parameters...]>
   116 CT_DoExecLog() {
   117     local level="$1"
   118     shift
   119     CT_DoLog DEBUG "==> Executing: '${*}'"
   120     "${@}" 2>&1 |CT_DoLog "${level}"
   121 }
   122 
   123 # Tail message to be logged whatever happens
   124 # Usage: CT_DoEnd <level>
   125 CT_DoEnd()
   126 {
   127     local level="$1"
   128     CT_STOP_DATE=$(CT_DoDate +%s%N)
   129     CT_STOP_DATE_HUMAN=$(CT_DoDate +%Y%m%d.%H%M%S)
   130     if [ "${level}" != "ERROR" ]; then
   131         CT_DoLog "${level:-INFO}" "Build completed at ${CT_STOP_DATE_HUMAN}"
   132     fi
   133     elapsed=$((CT_STOP_DATE-CT_STAR_DATE))
   134     elapsed_min=$((elapsed/(60*1000*1000*1000)))
   135     elapsed_sec=$(printf "%02d" $(((elapsed%(60*1000*1000*1000))/(1000*1000*1000))))
   136     elapsed_csec=$(printf "%02d" $(((elapsed%(1000*1000*1000))/(10*1000*1000))))
   137     CT_DoLog ${level:-INFO} "(elapsed: ${elapsed_min}:${elapsed_sec}.${elapsed_csec})"
   138 }
   139 
   140 # Abort the execution with an error message
   141 # Usage: CT_Abort <message>
   142 CT_Abort() {
   143     CT_DoLog ERROR "$1"
   144     exit 1
   145 }
   146 
   147 # Test a condition, and print a message if satisfied
   148 # Usage: CT_Test <message> <tests>
   149 CT_Test() {
   150     local ret
   151     local m="$1"
   152     shift
   153     test "$@" && CT_DoLog WARN "$m"
   154     return 0
   155 }
   156 
   157 # Test a condition, and abort with an error message if satisfied
   158 # Usage: CT_TestAndAbort <message> <tests>
   159 CT_TestAndAbort() {
   160     local m="$1"
   161     shift
   162     test "$@" && CT_Abort "$m"
   163     return 0
   164 }
   165 
   166 # Test a condition, and abort with an error message if not satisfied
   167 # Usage: CT_TestAndAbort <message> <tests>
   168 CT_TestOrAbort() {
   169     local m="$1"
   170     shift
   171     test "$@" || CT_Abort "$m"
   172     return 0
   173 }
   174 
   175 # Test the presence of a tool, or abort if not found
   176 # Usage: CT_HasOrAbort <tool>
   177 CT_HasOrAbort() {
   178     CT_TestAndAbort "'${1}' not found and needed for successful toolchain build." -z "$(CT_Which "${1}")"
   179     return 0
   180 }
   181 
   182 # Search a program: wrap "which" for those system where
   183 # "which" verbosely says there is no match (Mandriva is
   184 # such a sucker...)
   185 # Usage: CT_Which <filename>
   186 CT_Which() {
   187   which "$1" 2>/dev/null || true
   188 }
   189 
   190 # Get current date with nanosecond precision
   191 # On those system not supporting nanosecond precision, faked with rounding down
   192 # to the highest entire second
   193 # Usage: CT_DoDate <fmt>
   194 CT_DoDate() {
   195     date "$1" |sed -r -e 's/%N$/000000000/;'
   196 }
   197 
   198 CT_STEP_COUNT=1
   199 CT_STEP_MESSAGE[${CT_STEP_COUNT}]="<none>"
   200 # Memorise a step being done so that any error is caught
   201 # Usage: CT_DoStep <loglevel> <message>
   202 CT_DoStep() {
   203     local start=$(CT_DoDate +%s%N)
   204     CT_DoLog "$1" "================================================================="
   205     CT_DoLog "$1" "$2"
   206     CT_STEP_COUNT=$((CT_STEP_COUNT+1))
   207     CT_STEP_LEVEL[${CT_STEP_COUNT}]="$1"; shift
   208     CT_STEP_START[${CT_STEP_COUNT}]="${start}"
   209     CT_STEP_MESSAGE[${CT_STEP_COUNT}]="$1"
   210     return 0
   211 }
   212 
   213 # End the step just being done
   214 # Usage: CT_EndStep
   215 CT_EndStep() {
   216     local stop=$(CT_DoDate +%s%N)
   217     local duration=$(printf "%032d" $((stop-${CT_STEP_START[${CT_STEP_COUNT}]})) |sed -r -e 's/([[:digit:]]{2})[[:digit:]]{7}$/\.\1/; s/^0+//; s/^\./0\./;')
   218     local elapsed=$(printf "%02d:%02d" $((SECONDS/60)) $((SECONDS%60)))
   219     local level="${CT_STEP_LEVEL[${CT_STEP_COUNT}]}"
   220     local message="${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}"
   221     CT_STEP_COUNT=$((CT_STEP_COUNT-1))
   222     CT_DoLog "${level}" "${message}: done in ${duration}s (at ${elapsed})"
   223     return 0
   224 }
   225 
   226 # Pushes into a directory, and pops back
   227 CT_Pushd() {
   228     pushd "$1" >/dev/null 2>&1
   229 }
   230 CT_Popd() {
   231     popd >/dev/null 2>&1
   232 }
   233 
   234 # Creates a temporary directory
   235 # $1: variable to assign to
   236 # Usage: CT_MktempDir foo
   237 CT_MktempDir() {
   238     # Some mktemp do not allow more than 6 Xs
   239     eval "$1"=$(mktemp -q -d "${CT_BUILD_DIR}/tmp.XXXXXX")
   240     CT_TestOrAbort "Could not make temporary directory" -n "${!1}" -a -d "${!1}"
   241     CT_DoLog DEBUG "Made temporary directory '${!1}'"
   242     return 0
   243 }
   244 
   245 # Removes one or more directories, even if it is read-only, or its parent is
   246 # Usage: CT_DoForceRmdir dir [...]
   247 CT_DoForceRmdir() {
   248     local dir
   249     local mode
   250     for dir in "${@}"; do
   251         [ -d "${dir}" ] || continue
   252         mode="$(stat -c '%a' "$(dirname "${dir}")")"
   253         CT_DoExecLog ALL chmod u+w "$(dirname "${dir}")"
   254         CT_DoExecLog ALL chmod -R u+w "${dir}"
   255         CT_DoExecLog ALL rm -rf "${dir}"
   256         CT_DoExecLog ALL chmod ${mode} "$(dirname "${dir}")"
   257     done
   258 }
   259 
   260 # Echoes the specified string on stdout until the pipe breaks.
   261 # Doesn't fail
   262 # $1: string to echo
   263 # Usage: CT_DoYes "" |make oldconfig
   264 CT_DoYes() {
   265     yes "$1" || true
   266 }
   267 
   268 # Add the specified directory to LD_LIBRARY_PATH, and export it
   269 # If the specified patch is already present, just export
   270 # $1: path to add
   271 # $2: add as 'first' or 'last' path, 'first' is assumed if $2 is empty
   272 # Usage CT_SetLibPath /some/where/lib [first|last]
   273 CT_SetLibPath() {
   274     local path="$1"
   275     local pos="$2"
   276 
   277     case ":${LD_LIBRARY_PATH}:" in
   278         *:"${path}":*)  ;;
   279         *)  case "${pos}" in
   280                 last)
   281                     CT_DoLog DEBUG "Adding '${path}' at end of LD_LIBRARY_PATH"
   282                     LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${path}"
   283                     ;;
   284                 first|"")
   285                     CT_DoLog DEBUG "Adding '${path}' at start of LD_LIBRARY_PATH"
   286                     LD_LIBRARY_PATH="${path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
   287                     ;;
   288                 *)
   289                     CT_Abort "Incorrect position '${pos}' to add '${path}' to LD_LIBRARY_PATH"
   290                     ;;
   291             esac
   292             ;;
   293     esac
   294     CT_DoLog DEBUG "==> LD_LIBRARY_PATH='${LD_LIBRARY_PATH}'"
   295     export LD_LIBRARY_PATH
   296 }
   297 
   298 # Get the file name extension of a component
   299 # Usage: CT_GetFileExtension <component_name-component_version> [extension]
   300 # If found, echoes the extension to stdout
   301 # If not found, echoes nothing on stdout.
   302 CT_GetFileExtension() {
   303     local ext
   304     local file="$1"
   305     shift
   306     local first_ext="$1"
   307 
   308     # we need to also check for an empty extension for those very
   309     # peculiar components that don't have one (such as sstrip from
   310     # buildroot).
   311     for ext in ${first_ext} .tar.gz .tar.bz2 .tgz .tar ''; do
   312         if [ -f "${CT_TARBALLS_DIR}/${file}${ext}" ]; then
   313             echo "${ext}"
   314             break
   315         fi
   316     done
   317 
   318     return 0
   319 }
   320 
   321 # Download an URL using wget
   322 # Usage: CT_DoGetFileWget <URL>
   323 CT_DoGetFileWget() {
   324     # Need to return true because it is legitimate to not find the tarball at
   325     # some of the provided URLs (think about snapshots, different layouts for
   326     # different gcc versions, etc...)
   327     # Some (very old!) FTP server might not support the passive mode, thus
   328     # retry without
   329     # With automated download as we are doing, it can be very dangerous to use
   330     # -c to continue the downloads. It's far better to simply overwrite the
   331     # destination file
   332     # Some company networks have firewalls to connect to the internet, but it's
   333     # not easy to detect them, and wget does not timeout by default while
   334     # connecting, so force a global ${CT_CONNECT_TIMEOUT}-second timeout.
   335     CT_DoExecLog ALL wget -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary --tries=3 --passive-ftp "$1"    \
   336     || CT_DoExecLog ALL wget -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary --tries=3 "$1"               \
   337     || true
   338 }
   339 
   340 # Download an URL using curl
   341 # Usage: CT_DoGetFileCurl <URL>
   342 CT_DoGetFileCurl() {
   343     # Note: comments about wget method (above) are also valid here
   344     # Plus: no good progress indicator is available with curl,
   345     #       so, be silent.
   346     CT_DoExecLog ALL curl -s --ftp-pasv -O --retry 3 "$1" --connect-timeout ${CT_CONNECT_TIMEOUT}    \
   347     || CT_DoExecLog ALL curl -s -O --retry 3 "$1" --connect-timeout ${CT_CONNECT_TIMEOUT}            \
   348     || true
   349 }
   350 
   351 # Download using aria2
   352 # Usage: CT_DoGetFileAria2 <URL>
   353 CT_DoGetFileAria2() {
   354     # Note: comments about curl method (above) are also valid here
   355     # Plus: default progress indicator is a single line, so use verbose log
   356     #       so that the CT-NG's ouput is 'live'.
   357     CT_DoExecLog ALL aria2c -l - -s ${CT_DOWNLOAD_MAX_CHUNKS} -m 3 --retry-wait 5 -t ${CT_CONNECT_TIMEOUT} -p "$1" \
   358     || CT_DoExecLog ALL aria2c -l - -s ${CT_DOWNLOAD_MAX_CHUNKS} -m 3 --retry-wait 5 -t ${CT_CONNECT_TIMEOUT} "$1" \
   359     || true
   360 }
   361 
   362 # OK, just look if we have them...
   363 _aria2c=$(CT_Which aria2c)
   364 _wget=$(CT_Which wget)
   365 _curl=$(CT_Which curl)
   366 
   367 # Wrapper function to call one of, in order of preference:
   368 #   aria2
   369 #   curl
   370 #   wget
   371 # Usage: CT_DoGetFile <URL>
   372 CT_DoGetFile() {
   373     if   [ -n "${_aria2c}" ]; then
   374         CT_DoGetFileAria2 "$1"
   375     elif [ -n "${_curl}" ]; then
   376         CT_DoGetFileCurl "$1"
   377     elif [ -n "${_wget}" ]; then
   378         CT_DoGetFileWget "$1"
   379     else
   380         CT_Abort "Could find neither wget nor curl"
   381     fi
   382 }
   383 
   384 # This function tries to retrieve a tarball form a local directory
   385 # Usage: CT_GetLocal <basename> [.extension]
   386 CT_GetLocal() {
   387     local basename="$1"
   388     local first_ext="$2"
   389     local ext
   390 
   391     # Do we already have it in *our* tarballs dir?
   392     ext=$(CT_GetFileExtension "${basename}" ${first_ext})
   393     if [ -n "${ext}" ]; then
   394         CT_DoLog DEBUG "Already have '${basename}'"
   395         return 0
   396     fi
   397 
   398     if [ -n "${CT_LOCAL_TARBALLS_DIR}" ]; then
   399         CT_DoLog DEBUG "Trying to retrieve an already downloaded copy of '${basename}'"
   400         # We'd rather have a bzip2'ed tarball, then gzipped tarball, plain tarball,
   401         # or, as a failover, a file without extension.
   402         for ext in ${first_ext} .tar.bz2 .tar.gz .tgz .tar ''; do
   403             CT_DoLog DEBUG "Trying '${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}'"
   404             if [ -r "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" -a \
   405                  "${CT_FORCE_DOWNLOAD}" != "y" ]; then
   406                 CT_DoLog DEBUG "Got '${basename}' from local storage"
   407                 CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" "${CT_TARBALLS_DIR}/${basename}${ext}"
   408                 return 0
   409             fi
   410         done
   411     fi
   412     return 1
   413 }
   414 
   415 # This function saves the specified to local storage if possible,
   416 # and if so, symlinks it for later usage
   417 # Usage: CT_SaveLocal </full/path/file.name>
   418 CT_SaveLocal() {
   419     local file="$1"
   420     local basename="${file##*/}"
   421 
   422     if [ "${CT_SAVE_TARBALLS}" = "y" ]; then
   423         CT_DoLog EXTRA "Saving '${basename}' to local storage"
   424         # The file may already exist if downloads are forced: remove it first
   425         CT_DoExecLog ALL rm -f "${CT_LOCAL_TARBALLS_DIR}/${basename}"
   426         CT_DoExecLog ALL mv -f "${file}" "${CT_LOCAL_TARBALLS_DIR}"
   427         CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}" "${file}"
   428     fi
   429 }
   430 
   431 # Download the file from one of the URLs passed as argument
   432 # Usage: CT_GetFile <basename> [.extension] <url> [url ...]
   433 CT_GetFile() {
   434     local ext
   435     local url URLS LAN_URLS
   436     local file="$1"
   437     local first_ext
   438     shift
   439     # If next argument starts with a dot, then this is not an URL,
   440     # and we can consider that it is a preferred extension.
   441     case "$1" in
   442         .*) first_ext="$1"
   443             shift
   444             ;;
   445     esac
   446 
   447     # Does it exist localy?
   448     CT_GetLocal "${file}" ${first_ext} && return 0 || true
   449     # No, it does not...
   450 
   451     # Are downloads allowed ?
   452     CT_TestAndAbort "File '${file}' not present locally, and downloads are not allowed" "${CT_FORBID_DOWNLOAD}" = "y"
   453 
   454     # Try to retrieve the file
   455     CT_DoLog EXTRA "Retrieving '${file}'"
   456     CT_Pushd "${CT_TARBALLS_DIR}"
   457 
   458     URLS="$@"
   459 
   460     # Add URLs on the LAN mirror
   461     LAN_URLS=
   462     if [ "${CT_USE_MIRROR}" = "y" ]; then
   463         CT_TestOrAbort "Please set the mirror base URL" -n "${CT_MIRROR_BASE_URL}"
   464         LAN_URLS="${LAN_URLS} ${CT_MIRROR_BASE_URL}/${file%-*}"
   465         LAN_URLS="${LAN_URLS} ${CT_MIRROR_BASE_URL}"
   466 
   467         if [ "${CT_PREFER_MIRROR}" = "y" ]; then
   468             CT_DoLog DEBUG "Pre-pending LAN mirror URLs"
   469             URLS="${LAN_URLS} ${URLS}"
   470         else
   471             CT_DoLog DEBUG "Appending LAN mirror URLs"
   472             URLS="${URLS} ${LAN_URLS}"
   473         fi
   474     fi
   475 
   476     # Scan all URLs in turn, and try to grab a tarball from there
   477     for ext in ${first_ext} .tar.bz2 .tar.gz .tgz .tar ''; do
   478         # Try all urls in turn
   479         for url in ${URLS}; do
   480             CT_DoLog DEBUG "Trying '${url}/${file}${ext}'"
   481             CT_DoGetFile "${url}/${file}${ext}"
   482             if [ -f "${file}${ext}" ]; then
   483                 CT_DoLog DEBUG "Got '${file}' from the Internet"
   484                 CT_SaveLocal "${CT_TARBALLS_DIR}/${file}${ext}"
   485                 return 0
   486             fi
   487         done
   488     done
   489     CT_Popd
   490 
   491     CT_Abort "Could not retrieve '${file}'."
   492 }
   493 
   494 # Checkout from CVS, and build the associated tarball
   495 # The tarball will be called ${basename}.tar.bz2
   496 # Prerequisite: either the server does not require password,
   497 # or the user must already be logged in.
   498 # 'tag' is the tag to retrieve. Must be specified, but can be empty.
   499 # If dirname is specified, then module will be renamed to dirname
   500 # prior to building the tarball.
   501 # Usage: CT_GetCVS <basename> <url> <module> <tag> [dirname[=subdir]]
   502 # Note: if '=subdir' is given, then it is used instead of 'module'.
   503 CT_GetCVS() {
   504     local basename="$1"
   505     local uri="$2"
   506     local module="$3"
   507     local tag="${4:+-r ${4}}"
   508     local dirname="$5"
   509     local tmp_dir
   510 
   511     # Does it exist localy?
   512     CT_GetLocal "${basename}" && return 0 || true
   513     # No, it does not...
   514 
   515     # Are downloads allowed ?
   516     CT_TestAndAbort "File '${basename}' not present locally, and downloads are not allowed" "${CT_FORBID_DOWNLOAD}" = "y"
   517 
   518     CT_DoLog EXTRA "Retrieving '${basename}'"
   519 
   520     CT_MktempDir tmp_dir
   521     CT_Pushd "${tmp_dir}"
   522 
   523     CT_DoExecLog ALL cvs -z 9 -d "${uri}" co -P ${tag} "${module}"
   524     if [ -n "${dirname}" ]; then
   525         case "${dirname}" in
   526             *=*)
   527                 CT_DoExecLog DEBUG mv "${dirname#*=}" "${dirname%%=*}"
   528                 CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname%%=*}"
   529                 ;;
   530             *)
   531                 CT_DoExecLog ALL mv "${module}" "${dirname}"
   532                 CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname:-${module}}"
   533                 ;;
   534         esac
   535     fi
   536     CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
   537 
   538     CT_Popd
   539     CT_DoExecLog ALL rm -rf "${tmp_dir}"
   540 }
   541 
   542 # Check out from SVN, and build the associated tarball
   543 # The tarball will be called ${basename}.tar.bz2
   544 # Prerequisite: either the server does not require password,
   545 # or the user must already be logged in.
   546 # 'rev' is the revision to retrieve
   547 # Usage: CT_GetSVN <basename> <url> [rev]
   548 CT_GetSVN() {
   549     local basename="$1"
   550     local uri="$2"
   551     local rev="$3"
   552 
   553     # Does it exist localy?
   554     CT_GetLocal "${basename}" && return 0 || true
   555     # No, it does not...
   556 
   557     # Are downloads allowed ?
   558     CT_TestAndAbort "File '${basename}' not present locally, and downloads are not allowed" "${CT_FORBID_DOWNLOAD}" = "y"
   559 
   560     CT_DoLog EXTRA "Retrieving '${basename}'"
   561 
   562     CT_MktempDir tmp_dir
   563     CT_Pushd "${tmp_dir}"
   564 
   565     CT_DoExecLog ALL svn export ${rev:+-r ${rev}} "${uri}" "${basename}"
   566     CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${basename}"
   567     CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
   568 
   569     CT_Popd
   570     CT_DoExecLog ALL rm -rf "${tmp_dir}"
   571 }
   572 
   573 # Extract a tarball
   574 # Some tarballs need to be extracted in specific places. Eg.: glibc addons
   575 # must be extracted in the glibc directory; uCLibc locales must be extracted
   576 # in the extra/locale sub-directory of uClibc. This is taken into account
   577 # by the caller, that did a 'cd' into the correct path before calling us
   578 # and sets nochdir to 'nochdir'.
   579 # Usage: CT_Extract <basename> [nochdir]
   580 CT_Extract() {
   581     local basename="$1"
   582     local nochdir="$2"
   583     local ext=$(CT_GetFileExtension "${basename}")
   584     CT_TestAndAbort "'${basename}' not found in '${CT_TARBALLS_DIR}'" -z "${ext}"
   585     local full_file="${CT_TARBALLS_DIR}/${basename}${ext}"
   586 
   587     # Check if already extracted
   588     if [ -e "${CT_SRC_DIR}/.${basename}.extracted" ]; then
   589         CT_DoLog DEBUG "Already extracted '${basename}'"
   590         return 0
   591     fi
   592 
   593     [ "${nochdir}" = "nochdir" ] || CT_Pushd "${CT_SRC_DIR}"
   594 
   595     CT_DoLog EXTRA "Extracting '${basename}'"
   596     case "${ext}" in
   597         .tar.bz2)     CT_DoExecLog ALL tar xvjf "${full_file}";;
   598         .tar.gz|.tgz) CT_DoExecLog ALL tar xvzf "${full_file}";;
   599         .tar)         CT_DoExecLog ALL tar xvf  "${full_file}";;
   600         *)            CT_Abort "Don't know how to handle '${basename}${ext}': unknown extension" ;;
   601     esac
   602 
   603     # Some tarballs have read-only files... :-(
   604     # Because of nochdir, we don't know where we are, so chmod all
   605     # the src tree
   606     CT_DoExecLog DEBUG chmod -R u+w "${CT_SRC_DIR}"
   607 
   608     CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.extracted"
   609 
   610     [ "${nochdir}" = "nochdir" ] || CT_Popd
   611 }
   612 
   613 # Patches the specified component
   614 # Usage: CT_Patch <basename> [nochdir]
   615 CT_Patch() {
   616     local basename="$1"
   617     local nochdir="$2"
   618     local base_file="${basename%%-*}"
   619     local ver_file="${basename#*-}"
   620     local d
   621     local -a patch_dirs
   622     local bundled_patch_dir
   623     local local_patch_dir
   624 
   625     # Check if already patched
   626     if [ -e "${CT_SRC_DIR}/.${basename}.patched" ]; then
   627         CT_DoLog DEBUG "Already patched '${basename}'"
   628         return 0
   629     fi
   630 
   631     # Check if already partially patched
   632     if [ -e "${CT_SRC_DIR}/.${basename}.patching" ]; then
   633         CT_DoLog ERROR "The '${basename}' sources were partially patched."
   634         CT_DoLog ERROR "Please remove first:"
   635         CT_DoLog ERROR " - the source dir for '${basename}', in '${CT_SRC_DIR}'"
   636         CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${basename}.extracted'"
   637         CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${basename}.patching'"
   638         CT_Abort "I'll stop now to avoid any carnage..."
   639     fi
   640     touch "${CT_SRC_DIR}/.${basename}.patching"
   641 
   642     [ "${nochdir}" = "nochdir" ] || CT_Pushd "${CT_SRC_DIR}/${basename}"
   643 
   644     CT_DoLog EXTRA "Patching '${basename}'"
   645 
   646     bundled_patch_dir="${CT_LIB_DIR}/patches/${base_file}/${ver_file}"
   647     local_patch_dir="${CT_LOCAL_PATCH_DIR}/${base_file}/${ver_file}"
   648 
   649     case "${CT_PATCH_ORDER}" in
   650         bundled)        patch_dirs=("${bundled_patch_dir}");;
   651         local)          patch_dirs=("${local_patch_dir}");;
   652         bundled,local)  patch_dirs=("${bundled_patch_dir}" "${local_patch_dir}");;
   653         local,bundled)  patch_dirs=("${local_patch_dir}" "${bundled_patch_dir}");;
   654         none)           patch_dirs=;;
   655     esac
   656 
   657     for d in "${patch_dirs[@]}"; do
   658         CT_DoLog DEBUG "Looking for patches in '${d}'..."
   659         if [ -n "${d}" -a -d "${d}" ]; then
   660             for p in "${d}"/*.patch; do
   661                 if [ -f "${p}" ]; then
   662                     CT_DoLog DEBUG "Applying patch '${p}'"
   663                     CT_DoExecLog ALL patch -g0 -F1 -p1 -f <"${p}"
   664                 fi
   665             done
   666             if [ "${CT_PATCH_SINGLE}" = "y" ]; then
   667                 break
   668             fi
   669         fi
   670     done
   671 
   672     if [ "${CT_OVERIDE_CONFIG_GUESS_SUB}" = "y" ]; then
   673         CT_DoLog ALL "Overiding config.guess and config.sub"
   674         for cfg in config_guess config_sub; do
   675             eval ${cfg}="${CT_LIB_DIR}/scripts/${cfg/_/.}"
   676             [ -e "${CT_TOP_DIR}/scripts/${cfg/_/.}" ] && eval ${cfg}="${CT_TOP_DIR}/scripts/${cfg/_/.}"
   677             # Can't use CT_DoExecLog because of the '{} \;' to be passed un-mangled to find
   678             find . -type f -name "${cfg/_/.}" -exec cp -v "${!cfg}" {} \; |CT_DoLog ALL
   679         done
   680     fi
   681 
   682     CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/.${basename}.patching"
   683     CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.patched"
   684 
   685     [ "${nochdir}" = "nochdir" ] || CT_Popd
   686 }
   687 
   688 # Two wrappers to call config.(guess|sub) either from CT_TOP_DIR or CT_LIB_DIR.
   689 # Those from CT_TOP_DIR, if they exist, will be be more recent than those from CT_LIB_DIR.
   690 CT_DoConfigGuess() {
   691     if [ -x "${CT_TOP_DIR}/scripts/config.guess" ]; then
   692         "${CT_TOP_DIR}/scripts/config.guess"
   693     else
   694         "${CT_LIB_DIR}/scripts/config.guess"
   695     fi
   696 }
   697 
   698 CT_DoConfigSub() {
   699     if [ -x "${CT_TOP_DIR}/scripts/config.sub" ]; then
   700         "${CT_TOP_DIR}/scripts/config.sub" "$@"
   701     else
   702         "${CT_LIB_DIR}/scripts/config.sub" "$@"
   703     fi
   704 }
   705 
   706 # Compute the target tuple from what is provided by the user
   707 # Usage: CT_DoBuildTargetTuple
   708 # In fact this function takes the environment variables to build the target
   709 # tuple. It is needed both by the normal build sequence, as well as the
   710 # sample saving sequence.
   711 CT_DoBuildTargetTuple() {
   712     # Set the endianness suffix, and the default endianness gcc option
   713     case "${CT_ARCH_BE},${CT_ARCH_LE}" in
   714         y,) target_endian_eb=eb
   715             target_endian_el=
   716             CT_ARCH_ENDIAN_CFLAG="-mbig-endian"
   717             CT_ARCH_ENDIAN_LDFLAG="-EB"
   718             ;;
   719         ,y) target_endian_eb=
   720             target_endian_el=el
   721             CT_ARCH_ENDIAN_CFLAG="-mlittle-endian"
   722             CT_ARCH_ENDIAN_LDFLAG="-EL"
   723             ;;
   724     esac
   725 
   726     # Build the default architecture tuple part
   727     CT_TARGET_ARCH="${CT_ARCH}"
   728 
   729     # Set defaults for the system part of the tuple. Can be overriden
   730     # by architecture-specific values.
   731     case "${CT_LIBC}" in
   732         *glibc) CT_TARGET_SYS=gnu;;
   733         uClibc) CT_TARGET_SYS=uclibc;;
   734         *)      CT_TARGET_SYS=elf;;
   735     esac
   736 
   737     # Transform the ARCH into a kernel-understandable ARCH
   738     CT_KERNEL_ARCH="${CT_ARCH}"
   739 
   740     # Set the default values for ARCH, ABI, CPU, TUNE, FPU and FLOAT
   741     unset CT_ARCH_ARCH_CFLAG CT_ARCH_ABI_CFLAG CT_ARCH_CPU_CFLAG CT_ARCH_TUNE_CFLAG CT_ARCH_FPU_CFLAG CT_ARCH_FLOAT_CFLAG
   742     unset CT_ARCH_WITH_ARCH CT_ARCH_WITH_ABI CT_ARCH_WITH_CPU CT_ARCH_WITH_TUNE CT_ARCH_WITH_FPU CT_ARCH_WITH_FLOAT
   743     [ "${CT_ARCH_ARCH}"     ] && { CT_ARCH_ARCH_CFLAG="-march=${CT_ARCH_ARCH}";  CT_ARCH_WITH_ARCH="--with-arch=${CT_ARCH_ARCH}"; }
   744     [ "${CT_ARCH_ABI}"      ] && { CT_ARCH_ABI_CFLAG="-mabi=${CT_ARCH_ABI}";     CT_ARCH_WITH_ABI="--with-abi=${CT_ARCH_ABI}";    }
   745     [ "${CT_ARCH_CPU}"      ] && { CT_ARCH_CPU_CFLAG="-mcpu=${CT_ARCH_CPU}";     CT_ARCH_WITH_CPU="--with-cpu=${CT_ARCH_CPU}";    }
   746     [ "${CT_ARCH_TUNE}"     ] && { CT_ARCH_TUNE_CFLAG="-mtune=${CT_ARCH_TUNE}";  CT_ARCH_WITH_TUNE="--with-tune=${CT_ARCH_TUNE}"; }
   747     [ "${CT_ARCH_FPU}"      ] && { CT_ARCH_FPU_CFLAG="-mfpu=${CT_ARCH_FPU}";     CT_ARCH_WITH_FPU="--with-fpu=${CT_ARCH_FPU}";    }
   748     [ "${CT_ARCH_FLOAT_SW}" ] && { CT_ARCH_FLOAT_CFLAG="-msoft-float";           CT_ARCH_WITH_FLOAT="--with-float=soft";          }
   749 
   750     # Build the default kernel tuple part
   751     CT_TARGET_KERNEL="${CT_KERNEL}"
   752 
   753     # Overide the default values with the components specific settings
   754     CT_DoArchTupleValues
   755     CT_DoKernelTupleValues
   756 
   757     # Finish the target tuple construction
   758     CT_TARGET="${CT_TARGET_ARCH}-${CT_TARGET_VENDOR:-unknown}-${CT_TARGET_KERNEL}${CT_TARGET_KERNEL:+-}${CT_TARGET_SYS}"
   759 
   760     # Sanity checks
   761     __sed_alias=""
   762     if [ -n "${CT_TARGET_ALIAS_SED_EXPR}" ]; then
   763         __sed_alias=$(echo "${CT_TARGET}" |sed -r -e "${CT_TARGET_ALIAS_SED_EXPR}")
   764     fi
   765     case ":${CT_TARGET_VENDOR}:${CT_TARGET_ALIAS}:${__sed_alias}:" in
   766       :*" "*:*:*:) CT_Abort "Don't use spaces in the vendor string, it breaks things.";;
   767       :*"-"*:*:*:) CT_Abort "Don't use dashes in the vendor string, it breaks things.";;
   768       :*:*" "*:*:) CT_Abort "Don't use spaces in the target alias, it breaks things.";;
   769       :*:*:*" "*:) CT_Abort "Don't use spaces in the target sed transform, it breaks things.";;
   770     esac
   771 
   772     # Canonicalise it
   773     CT_TARGET=$(CT_DoConfigSub "${CT_TARGET}")
   774 
   775     # Prepare the target CFLAGS
   776     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ENDIAN_CFLAG}"
   777     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ARCH_CFLAG}"
   778     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ABI_CFLAG}"
   779     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_CPU_CFLAG}"
   780     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_TUNE_CFLAG}"
   781     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FPU_CFLAG}"
   782     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FLOAT_CFLAG}"
   783 
   784     # Now on for the target LDFLAGS
   785     CT_ARCH_TARGET_LDFLAGS="${CT_ARCH_TARGET_LDFLAGS} ${CT_ARCH_ENDIAN_LDFLAG}"
   786 }
   787 
   788 # This function does pause the build until the user strikes "Return"
   789 # Usage: CT_DoPause [optional_message]
   790 CT_DoPause() {
   791     local foo
   792     local message="${1:-Pausing for your pleasure}"
   793     CT_DoLog INFO "${message}"
   794     read -p "Press 'Enter' to continue, or Ctrl-C to stop..." foo >&6
   795     return 0
   796 }
   797 
   798 # This function saves the state of the toolchain to be able to restart
   799 # at any one point
   800 # Usage: CT_DoSaveState <next_step_name>
   801 CT_DoSaveState() {
   802 	[ "${CT_DEBUG_CT_SAVE_STEPS}" = "y" ] || return 0
   803     local state_name="$1"
   804     local state_dir="${CT_STATE_DIR}/${state_name}"
   805 
   806     # Log this to the log level required by the user
   807     CT_DoLog ${CT_LOG_LEVEL_MAX} "Saving state to restart at step '${state_name}'..."
   808 
   809     rm -rf "${state_dir}"
   810     mkdir -p "${state_dir}"
   811 
   812     case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   813         y)  tar_opt=z; tar_ext=.gz;;
   814         *)  tar_opt=;  tar_ext=;;
   815     esac
   816 
   817     CT_DoLog DEBUG "  Saving environment and aliases"
   818     # We must omit shell functions, and some specific bash variables
   819     # that break when restoring the environment, later. We could do
   820     # all the processing in the awk script, but a sed is easier...
   821     set |awk '
   822               BEGIN { _p = 1; }
   823               $0~/^[^ ]+ \(\)/ { _p = 0; }
   824               _p == 1
   825               $0 == "}" { _p = 1; }
   826               ' |sed -r -e '/^BASH_(ARGC|ARGV|LINENO|SOURCE|VERSINFO)=/d;
   827                            /^(UID|EUID)=/d;
   828                            /^(FUNCNAME|GROUPS|PPID|SHELLOPTS)=/d;' >"${state_dir}/env.sh"
   829 
   830     CT_DoLog DEBUG "  Saving CT_CONFIG_DIR='${CT_CONFIG_DIR}'"
   831     CT_Pushd "${CT_CONFIG_DIR}"
   832     CT_DoExecLog DEBUG tar cv${tar_opt}f "${state_dir}/config_dir.tar${tar_ext}" .
   833     CT_Popd
   834 
   835     CT_DoLog DEBUG "  Saving CT_CC_CORE_STATIC_PREFIX_DIR='${CT_CC_CORE_STATIC_PREFIX_DIR}'"
   836     CT_Pushd "${CT_CC_CORE_STATIC_PREFIX_DIR}"
   837     CT_DoExecLog DEBUG tar cv${tar_opt}f "${state_dir}/cc_core_static_prefix_dir.tar${tar_ext}" .
   838     CT_Popd
   839 
   840     CT_DoLog DEBUG "  Saving CT_CC_CORE_SHARED_PREFIX_DIR='${CT_CC_CORE_SHARED_PREFIX_DIR}'"
   841     CT_Pushd "${CT_CC_CORE_SHARED_PREFIX_DIR}"
   842     CT_DoExecLog DEBUG tar cv${tar_opt}f "${state_dir}/cc_core_shared_prefix_dir.tar${tar_ext}" .
   843     CT_Popd
   844 
   845     CT_DoLog DEBUG "  Saving CT_PREFIX_DIR='${CT_PREFIX_DIR}'"
   846     CT_Pushd "${CT_PREFIX_DIR}"
   847     CT_DoExecLog DEBUG tar cv${tar_opt}f "${state_dir}/prefix_dir.tar${tar_ext}" --exclude '*.log' .
   848     CT_Popd
   849 
   850     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
   851         CT_DoLog DEBUG "  Saving log file"
   852         exec >/dev/null
   853         case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   854             y)  gzip -3 -c "${CT_LOG_FILE}"  >"${state_dir}/log.gz";;
   855             *)  cat "${CT_LOG_FILE}" >"${state_dir}/log";;
   856         esac
   857         exec >>"${CT_LOG_FILE}"
   858     fi
   859 }
   860 
   861 # This function restores a previously saved state
   862 # Usage: CT_DoLoadState <state_name>
   863 CT_DoLoadState(){
   864     local state_name="$1"
   865     local state_dir="${CT_STATE_DIR}/${state_name}"
   866     local old_RESTART="${CT_RESTART}"
   867     local old_STOP="${CT_STOP}"
   868 
   869     CT_TestOrAbort "The previous build did not reach the point where it could be restarted at '${CT_RESTART}'" -d "${state_dir}"
   870 
   871     # We need to do something special with the log file!
   872     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
   873         exec >"${state_dir}/tail.log"
   874     fi
   875 
   876     # Log this to the log level required by the user
   877     CT_DoLog ${CT_LOG_LEVEL_MAX} "Restoring state at step '${state_name}', as requested."
   878 
   879     case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   880         y)  tar_opt=z; tar_ext=.gz;;
   881         *)  tar_opt=;  tar_ext=;;
   882     esac
   883 
   884     CT_DoLog DEBUG "  Removing previous build directories"
   885     CT_DoForceRmdir             "${CT_PREFIX_DIR}" "${CT_CC_CORE_SHARED_PREFIX_DIR}" "${CT_CC_CORE_STATIC_PREFIX_DIR}" "${CT_CONFIG_DIR}"
   886     CT_DoExecLog DEBUG mkdir -p "${CT_PREFIX_DIR}" "${CT_CC_CORE_SHARED_PREFIX_DIR}" "${CT_CC_CORE_STATIC_PREFIX_DIR}" "${CT_CONFIG_DIR}"
   887 
   888     CT_DoLog DEBUG "  Restoring CT_PREFIX_DIR='${CT_PREFIX_DIR}'"
   889     CT_Pushd "${CT_PREFIX_DIR}"
   890     CT_DoExecLog DEBUG tar xv${tar_opt}f "${state_dir}/prefix_dir.tar${tar_ext}"
   891     CT_Popd
   892 
   893     CT_DoLog DEBUG "  Restoring CT_CC_CORE_SHARED_PREFIX_DIR='${CT_CC_CORE_SHARED_PREFIX_DIR}'"
   894     CT_Pushd "${CT_CC_CORE_SHARED_PREFIX_DIR}"
   895     CT_DoExecLog DEBUG tar xv${tar_opt}f "${state_dir}/cc_core_shared_prefix_dir.tar${tar_ext}"
   896     CT_Popd
   897 
   898     CT_DoLog DEBUG "  Restoring CT_CC_CORE_STATIC_PREFIX_DIR='${CT_CC_CORE_STATIC_PREFIX_DIR}'"
   899     CT_Pushd "${CT_CC_CORE_STATIC_PREFIX_DIR}"
   900     CT_DoExecLog DEBUG tar xv${tar_opt}f "${state_dir}/cc_core_static_prefix_dir.tar${tar_ext}"
   901     CT_Popd
   902 
   903     CT_DoLog DEBUG "  Restoring CT_CONFIG_DIR='${CT_CONFIG_DIR}'"
   904     CT_Pushd "${CT_CONFIG_DIR}"
   905     CT_DoExecLog DEBUG tar xv${tar_opt}f "${state_dir}/config_dir.tar${tar_ext}"
   906     CT_Popd
   907 
   908     # Restore the environment, discarding any error message
   909     # (for example, read-only bash internals)
   910     CT_DoLog DEBUG "  Restoring environment"
   911     . "${state_dir}/env.sh" >/dev/null 2>&1 || true
   912 
   913     # Restore the new RESTART and STOP steps
   914     CT_RESTART="${old_RESTART}"
   915     CT_STOP="${old_STOP}"
   916     unset old_stop old_restart
   917 
   918     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
   919         CT_DoLog DEBUG "  Restoring log file"
   920         exec >/dev/null
   921         case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   922             y)  zcat "${state_dir}/log.gz" >"${CT_LOG_FILE}";;
   923             *)  cat "${state_dir}/log" >"${CT_LOG_FILE}";;
   924         esac
   925         cat "${state_dir}/tail.log" >>"${CT_LOG_FILE}"
   926         exec >>"${CT_LOG_FILE}"
   927         rm -f "${state_dir}/tail.log"
   928     fi
   929 }