dl-wrapper 8.7 KB

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