dl-wrapper 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env bash
  2. # This script is a wrapper to the other download backends.
  3. # Its role is to ensure atomicity when saving downloaded files
  4. # back to BR2_DL_DIR, and not clutter BR2_DL_DIR with partial,
  5. # failed downloads.
  6. #
  7. # Call it with -h to see some help.
  8. # To avoid cluttering BR2_DL_DIR, we download to a trashable
  9. # location, namely in $(BUILD_DIR).
  10. # Then, we move the downloaded file to a temporary file in the
  11. # same directory as the final output file.
  12. # This allows us to finally atomically rename it to its final
  13. # name.
  14. # If anything goes wrong, we just remove all the temporaries
  15. # created so far.
  16. # We want to catch any unexpected failure, and exit immediately.
  17. set -e
  18. export BR_BACKEND_DL_GETOPTS=":hc:d:o:n:N:H:ru:qf:e"
  19. main() {
  20. local OPT OPTARG
  21. local backend output hfile recurse quiet rc
  22. local -a uris
  23. # Parse our options; anything after '--' is for the backend
  24. while getopts ":hc:d:D:o:n:N:H:rf:u:q" OPT; do
  25. case "${OPT}" in
  26. h) help; exit 0;;
  27. c) cset="${OPTARG}";;
  28. d) dl_dir="${OPTARG}";;
  29. D) old_dl_dir="${OPTARG}";;
  30. o) output="${OPTARG}";;
  31. n) raw_base_name="${OPTARG}";;
  32. N) base_name="${OPTARG}";;
  33. H) hfile="${OPTARG}";;
  34. r) recurse="-r";;
  35. f) filename="${OPTARG}";;
  36. u) uris+=( "${OPTARG}" );;
  37. q) quiet="-q";;
  38. :) error "option '%s' expects a mandatory argument\n" "${OPTARG}";;
  39. \?) error "unknown option '%s'\n" "${OPTARG}";;
  40. esac
  41. done
  42. # Forget our options, and keep only those for the backend
  43. shift $((OPTIND-1))
  44. if [ -z "${output}" ]; then
  45. error "no output specified, use -o\n"
  46. fi
  47. # Legacy handling: check if the file already exists in the global
  48. # download directory. If it does, hard-link it. If it turns out it
  49. # was an incorrect download, we'd still check it below anyway.
  50. if [ ! -e "${output}" -a -e "${old_dl_dir}/${filename}" ]; then
  51. ln "${old_dl_dir}/${filename}" "${output}"
  52. fi
  53. # If the output file already exists and:
  54. # - there's no .hash file: do not download it again and exit promptly
  55. # - matches all its hashes: do not download it again and exit promptly
  56. # - fails at least one of its hashes: force a re-download
  57. # - there's no hash (but a .hash file): consider it a hard error
  58. if [ -e "${output}" ]; then
  59. if support/download/check-hash ${quiet} "${hfile}" "${output}" "${output##*/}"; then
  60. exit 0
  61. elif [ ${?} -ne 2 ]; then
  62. # Do not remove the file, otherwise it might get re-downloaded
  63. # from a later location (i.e. primary -> upstream -> mirror).
  64. # Do not print a message, check-hash already did.
  65. exit 1
  66. fi
  67. rm -f "${output}"
  68. warn "Re-downloading '%s'...\n" "${output##*/}"
  69. fi
  70. # Look through all the uris that we were given to download the package
  71. # source
  72. download_and_check=0
  73. rc=1
  74. for uri in "${uris[@]}"; do
  75. backend=${uri%+*}
  76. case "${backend}" in
  77. git|svn|cvs|bzr|file|scp|hg) ;;
  78. *) backend="wget" ;;
  79. esac
  80. uri=${uri#*+}
  81. urlencode=${backend#*|}
  82. # urlencode must be "urlencode"
  83. [ "${urlencode}" != "urlencode" ] && urlencode=""
  84. # tmpd is a temporary directory in which backends may store
  85. # intermediate by-products of the download.
  86. # tmpf is the file in which the backends should put the downloaded
  87. # content.
  88. # tmpd is located in $(BUILD_DIR), so as not to clutter the (precious)
  89. # $(BR2_DL_DIR)
  90. # We let the backends create tmpf, so they are able to set whatever
  91. # permission bits they want (although we're only really interested in
  92. # the executable bit.)
  93. tmpd="$(mktemp -d "${BUILD_DIR}/.${output##*/}.XXXXXX")"
  94. tmpf="${tmpd}/output"
  95. # Helpers expect to run in a directory that is *really* trashable, so
  96. # they are free to create whatever files and/or sub-dirs they might need.
  97. # Doing the 'cd' here rather than in all backends is easier.
  98. cd "${tmpd}"
  99. # If the backend fails, we can just remove the content of the temporary
  100. # directory to remove all the cruft it may have left behind, and try
  101. # the next URI until it succeeds. Once out of URI to try, we need to
  102. # cleanup and exit.
  103. if ! "${OLDPWD}/support/download/${backend}" \
  104. $([ -n "${urlencode}" ] && printf %s '-e') \
  105. -c "${cset}" \
  106. -d "${dl_dir}" \
  107. -n "${raw_base_name}" \
  108. -N "${raw_name}" \
  109. -f "${filename}" \
  110. -u "${uri}" \
  111. -o "${tmpf}" \
  112. ${quiet} ${recurse} "${@}"
  113. then
  114. # cd back to keep path coherence
  115. cd "${OLDPWD}"
  116. rm -rf "${tmpd}"
  117. continue
  118. fi
  119. # cd back to free the temp-dir, so we can remove it later
  120. cd "${OLDPWD}"
  121. # Check if the downloaded file is sane, and matches the stored hashes
  122. # for that file
  123. if support/download/check-hash ${quiet} "${hfile}" "${tmpf}" "${output##*/}"; then
  124. rc=0
  125. else
  126. if [ ${?} -ne 3 ]; then
  127. rm -rf "${tmpd}"
  128. continue
  129. fi
  130. # the hash file exists and there was no hash to check the file
  131. # against
  132. rc=1
  133. fi
  134. download_and_check=1
  135. break
  136. done
  137. # We tried every URI possible, none seems to work or to check against the
  138. # available hash. *ABORT MISSION*
  139. if [ "${download_and_check}" -eq 0 ]; then
  140. rm -rf "${tmpd}"
  141. exit 1
  142. fi
  143. # tmp_output is in the same directory as the final output, so we can
  144. # later move it atomically.
  145. tmp_output="$(mktemp "${output}.XXXXXX")"
  146. # 'mktemp' creates files with 'go=-rwx', so the files are not accessible
  147. # to users other than the one doing the download (and root, of course).
  148. # This can be problematic when a shared BR2_DL_DIR is used by different
  149. # users (e.g. on a build server), where all users may write to the shared
  150. # location, since other users would not be allowed to read the files
  151. # another user downloaded.
  152. # So, we restore the 'go' access rights to a more sensible value, while
  153. # still abiding by the current user's umask. We must do that before the
  154. # final 'mv', so just do it now.
  155. # Some backends (cp and scp) may create executable files, so we need to
  156. # carry the executable bit if needed.
  157. [ -x "${tmpf}" ] && new_mode=755 || new_mode=644
  158. new_mode=$(printf "%04o" $((0${new_mode} & ~0$(umask))))
  159. chmod ${new_mode} "${tmp_output}"
  160. # We must *not* unlink tmp_output, otherwise there is a small window
  161. # during which another download process may create the same tmp_output
  162. # name (very, very unlikely; but not impossible.)
  163. # Using 'cp' is not reliable, since 'cp' may unlink the destination file
  164. # if it is unable to open it with O_WRONLY|O_TRUNC; see:
  165. # http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cp.html
  166. # Since the destination filesystem can be anything, it might not support
  167. # O_TRUNC, so 'cp' would unlink it first.
  168. # Use 'cat' and append-redirection '>>' to save to the final location,
  169. # since that is the only way we can be 100% sure of the behaviour.
  170. if ! cat "${tmpf}" >>"${tmp_output}"; then
  171. rm -rf "${tmpd}" "${tmp_output}"
  172. exit 1
  173. fi
  174. rm -rf "${tmpd}"
  175. # tmp_output and output are on the same filesystem, so POSIX guarantees
  176. # that 'mv' is atomic, because it then uses rename() that POSIX mandates
  177. # to be atomic, see:
  178. # http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
  179. if ! mv -f "${tmp_output}" "${output}"; then
  180. rm -f "${tmp_output}"
  181. exit 1
  182. fi
  183. return ${rc}
  184. }
  185. help() {
  186. cat <<_EOF_
  187. NAME
  188. ${my_name} - download wrapper for Buildroot
  189. SYNOPSIS
  190. ${my_name} [OPTION]... -- [BACKEND OPTION]...
  191. DESCRIPTION
  192. Wrapper script around different download mechanisms. Ensures that
  193. concurrent downloads do not conflict, that partial downloads are
  194. properly evicted without leaving temporary files, and that access
  195. rights are maintained.
  196. -h This help text.
  197. -u URIs
  198. The URI to get the file from, the URI must respect the format given in
  199. the example.
  200. You may give as many '-u URI' as you want, the script will stop at the
  201. frist successful download.
  202. Example: backend+URI; git+http://example.com or http+http://example.com
  203. -o FILE
  204. Store the downloaded archive in FILE.
  205. -H FILE
  206. Use FILE to read hashes from, and check them against the downloaded
  207. archive.
  208. Exit status:
  209. 0 if OK
  210. !0 in case of error
  211. ENVIRONMENT
  212. BUILD_DIR
  213. The path to Buildroot's build dir
  214. _EOF_
  215. }
  216. trace() { local msg="${1}"; shift; printf "%s: ${msg}" "${my_name}" "${@}"; }
  217. warn() { trace "${@}" >&2; }
  218. errorN() { local ret="${1}"; shift; warn "${@}"; exit ${ret}; }
  219. error() { errorN 1 "${@}"; }
  220. my_name="${0##*/}"
  221. main "${@}"