dl-wrapper 9.3 KB

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