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