Clean up libfetch checking in configure
[pacman-ng.git] / lib / libalpm / dload.c
blob32096e244afab9fe5279e8f7297cb0829103472b
1 /*
2 * download.c
4 * Copyright (c) 2006-2010 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "config.h"
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <signal.h>
32 #include <limits.h>
33 /* the following two are needed on BSD for libfetch */
34 #if defined(HAVE_SYS_SYSLIMITS_H)
35 #include <sys/syslimits.h> /* PATH_MAX */
36 #endif
37 #if defined(HAVE_SYS_PARAM_H)
38 #include <sys/param.h> /* MAXHOSTNAMELEN */
39 #endif
41 #ifdef HAVE_FETCH
42 #include <fetch.h>
43 #endif
45 /* libalpm */
46 #include "dload.h"
47 #include "alpm_list.h"
48 #include "alpm.h"
49 #include "log.h"
50 #include "util.h"
51 #include "handle.h"
53 static char *get_filename(const char *url) {
54 char *filename = strrchr(url, '/');
55 if(filename != NULL) {
56 filename++;
58 return(filename);
61 #ifdef HAVE_FETCH
62 static char *get_destfile(const char *path, const char *filename) {
63 char *destfile;
64 /* len = localpath len + filename len + null */
65 size_t len = strlen(path) + strlen(filename) + 1;
66 CALLOC(destfile, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
67 snprintf(destfile, len, "%s%s", path, filename);
69 return(destfile);
72 static char *get_tempfile(const char *path, const char *filename) {
73 char *tempfile;
74 /* len = localpath len + filename len + '.part' len + null */
75 size_t len = strlen(path) + strlen(filename) + 6;
76 CALLOC(tempfile, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
77 snprintf(tempfile, len, "%s%s.part", path, filename);
79 return(tempfile);
82 static const char *gethost(struct url *fileurl)
84 const char *host = _("disk");
85 if(strcmp(SCHEME_FILE, fileurl->scheme) != 0) {
86 host = fileurl->host;
88 return(host);
91 int dload_interrupted;
92 static RETSIGTYPE inthandler(int signum)
94 dload_interrupted = 1;
97 #define check_stop() if(dload_interrupted) { ret = -1; goto cleanup; }
98 enum sighandlers { OLD = 0, NEW = 1 };
100 static int download_internal(const char *url, const char *localpath,
101 int force) {
102 FILE *localf = NULL;
103 struct stat st;
104 int ret = 0;
105 off_t dl_thisfile = 0;
106 ssize_t nread = 0;
107 char *tempfile, *destfile, *filename;
108 struct sigaction sig_pipe[2], sig_int[2];
110 off_t local_size = 0;
111 time_t local_time = 0;
113 struct url *fileurl;
114 struct url_stat ust;
115 fetchIO *dlf = NULL;
117 char buffer[PM_DLBUF_LEN];
119 filename = get_filename(url);
120 if(!filename) {
121 _alpm_log(PM_LOG_ERROR, _("url '%s' is invalid\n"), url);
122 RET_ERR(PM_ERR_SERVER_BAD_URL, -1);
125 fileurl = fetchParseURL(url);
126 if(!fileurl) {
127 _alpm_log(PM_LOG_ERROR, _("url '%s' is invalid\n"), url);
128 RET_ERR(PM_ERR_LIBFETCH, -1);
131 destfile = get_destfile(localpath, filename);
132 tempfile = get_tempfile(localpath, filename);
134 if(stat(tempfile, &st) == 0 && st.st_size > 0) {
135 _alpm_log(PM_LOG_DEBUG, "tempfile found, attempting continuation\n");
136 local_time = fileurl->last_modified = st.st_mtime;
137 local_size = fileurl->offset = (off_t)st.st_size;
138 dl_thisfile = st.st_size;
139 localf = fopen(tempfile, "ab");
140 } else if(!force && stat(destfile, &st) == 0 && st.st_size > 0) {
141 _alpm_log(PM_LOG_DEBUG, "destfile found, using mtime only\n");
142 local_time = fileurl->last_modified = st.st_mtime;
143 local_size = /* no fu->off here */ (off_t)st.st_size;
144 } else {
145 _alpm_log(PM_LOG_DEBUG, "no file found matching criteria, starting from scratch\n");
148 /* pass the raw filename for passing to the callback function */
149 _alpm_log(PM_LOG_DEBUG, "using '%s' for download progress\n", filename);
151 /* print proxy info for debug purposes */
152 _alpm_log(PM_LOG_DEBUG, "HTTP_PROXY: %s\n", getenv("HTTP_PROXY"));
153 _alpm_log(PM_LOG_DEBUG, "http_proxy: %s\n", getenv("http_proxy"));
154 _alpm_log(PM_LOG_DEBUG, "FTP_PROXY: %s\n", getenv("FTP_PROXY"));
155 _alpm_log(PM_LOG_DEBUG, "ftp_proxy: %s\n", getenv("ftp_proxy"));
157 /* 10s timeout */
158 fetchTimeout = 10;
160 /* ignore any SIGPIPE signals- these may occur if our FTP socket dies or
161 * something along those lines. Store the old signal handler first. */
162 sig_pipe[NEW].sa_handler = SIG_IGN;
163 sigemptyset(&sig_pipe[NEW].sa_mask);
164 sig_pipe[NEW].sa_flags = 0;
165 sigaction(SIGPIPE, NULL, &sig_pipe[OLD]);
166 sigaction(SIGPIPE, &sig_pipe[NEW], NULL);
168 dload_interrupted = 0;
169 sig_int[NEW].sa_handler = &inthandler;
170 sigemptyset(&sig_int[NEW].sa_mask);
171 sig_int[NEW].sa_flags = 0;
172 sigaction(SIGINT, NULL, &sig_int[OLD]);
173 sigaction(SIGINT, &sig_int[NEW], NULL);
175 /* NOTE: libfetch does not reset the error code, be sure to do it before
176 * calls into the library */
178 /* find out the remote size *and* mtime in one go. there is a lot of
179 * trouble in trying to do both size and "if-modified-since" logic in a
180 * non-stat request, so avoid it. */
181 fetchLastErrCode = 0;
182 if(fetchStat(fileurl, &ust, "") == -1) {
183 pm_errno = PM_ERR_LIBFETCH;
184 _alpm_log(PM_LOG_ERROR, _("failed retrieving file '%s' from %s : %s\n"),
185 filename, gethost(fileurl), fetchLastErrString);
186 ret = -1;
187 goto cleanup;
189 check_stop();
191 _alpm_log(PM_LOG_DEBUG, "ust.mtime: %ld local_time: %ld compare: %ld\n",
192 ust.mtime, local_time, local_time - ust.mtime);
193 _alpm_log(PM_LOG_DEBUG, "ust.size: %jd local_size: %jd compare: %jd\n",
194 (intmax_t)ust.size, (intmax_t)local_size, (intmax_t)(local_size - ust.size));
195 if(!force && ust.mtime && ust.mtime == local_time
196 && ust.size && ust.size == local_size) {
197 /* the remote time and size values agreed with what we have, so move on
198 * because there is nothing more to do. */
199 _alpm_log(PM_LOG_DEBUG, "files are identical, skipping %s\n", filename);
200 ret = 1;
201 goto cleanup;
203 if(!ust.mtime || ust.mtime != local_time) {
204 _alpm_log(PM_LOG_DEBUG, "mtimes were different or unavailable, downloading %s from beginning\n", filename);
205 fileurl->offset = 0;
208 fetchLastErrCode = 0;
209 dlf = fetchGet(fileurl, "");
210 check_stop();
212 if(fetchLastErrCode != 0 || dlf == NULL) {
213 pm_errno = PM_ERR_LIBFETCH;
214 _alpm_log(PM_LOG_ERROR, _("failed retrieving file '%s' from %s : %s\n"),
215 filename, gethost(fileurl), fetchLastErrString);
216 ret = -1;
217 goto cleanup;
218 } else {
219 _alpm_log(PM_LOG_DEBUG, "connected to %s successfully\n", fileurl->host);
222 if(localf && fileurl->offset == 0) {
223 _alpm_log(PM_LOG_WARNING, _("resuming download of %s not possible; starting over\n"), filename);
224 fclose(localf);
225 localf = NULL;
226 } else if(fileurl->offset) {
227 _alpm_log(PM_LOG_DEBUG, "resuming download at position %jd\n", (intmax_t)fileurl->offset);
231 if(localf == NULL) {
232 _alpm_rmrf(tempfile);
233 fileurl->offset = (off_t)0;
234 dl_thisfile = 0;
235 localf = fopen(tempfile, "wb");
236 if(localf == NULL) { /* still null? */
237 pm_errno = PM_ERR_RETRIEVE;
238 _alpm_log(PM_LOG_ERROR, _("error writing to file '%s': %s\n"),
239 tempfile, strerror(errno));
240 ret = -1;
241 goto cleanup;
245 /* Progress 0 - initialize */
246 if(handle->dlcb) {
247 handle->dlcb(filename, 0, ust.size);
250 while((nread = fetchIO_read(dlf, buffer, PM_DLBUF_LEN)) > 0) {
251 check_stop();
252 size_t nwritten = 0;
253 nwritten = fwrite(buffer, 1, nread, localf);
254 if((nwritten != (size_t)nread) || ferror(localf)) {
255 pm_errno = PM_ERR_RETRIEVE;
256 _alpm_log(PM_LOG_ERROR, _("error writing to file '%s': %s\n"),
257 tempfile, strerror(errno));
258 ret = -1;
259 goto cleanup;
261 dl_thisfile += nread;
263 if(handle->dlcb) {
264 handle->dlcb(filename, dl_thisfile, ust.size);
268 /* did the transfer complete normally? */
269 if (nread == -1) {
270 /* not PM_ERR_LIBFETCH here because libfetch error string might be empty */
271 pm_errno = PM_ERR_RETRIEVE;
272 _alpm_log(PM_LOG_ERROR, _("failed retrieving file '%s' from %s\n"),
273 filename, gethost(fileurl));
274 ret = -1;
275 goto cleanup;
278 if (ust.size != -1 && dl_thisfile < ust.size) {
279 pm_errno = PM_ERR_RETRIEVE;
280 _alpm_log(PM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
281 filename, (intmax_t)dl_thisfile, (intmax_t)ust.size);
282 ret = -1;
283 goto cleanup;
286 /* probably safer to close the file descriptors now before renaming the file,
287 * for example to make sure the buffers are flushed.
289 fclose(localf);
290 localf = NULL;
291 fetchIO_close(dlf);
292 dlf = NULL;
294 /* set the times on the file to the same as that of the remote file */
295 if(ust.mtime) {
296 struct timeval tv[2];
297 memset(&tv, 0, sizeof(tv));
298 tv[0].tv_sec = ust.atime;
299 tv[1].tv_sec = ust.mtime;
300 utimes(tempfile, tv);
302 rename(tempfile, destfile);
303 ret = 0;
305 cleanup:
306 FREE(tempfile);
307 FREE(destfile);
308 if(localf != NULL) {
309 /* if we still had a local file open, we got interrupted. set the mtimes on
310 * the file accordingly. */
311 fflush(localf);
312 if(ust.mtime) {
313 struct timeval tv[2];
314 memset(&tv, 0, sizeof(tv));
315 tv[0].tv_sec = ust.atime;
316 tv[1].tv_sec = ust.mtime;
317 futimes(fileno(localf), tv);
319 fclose(localf);
321 if(dlf != NULL) {
322 fetchIO_close(dlf);
324 fetchFreeURL(fileurl);
326 /* restore the old signal handlers */
327 sigaction(SIGINT, &sig_int[OLD], NULL);
328 sigaction(SIGPIPE, &sig_pipe[OLD], NULL);
329 /* if we were interrupted, trip the old handler */
330 if(dload_interrupted) {
331 raise(SIGINT);
334 return(ret);
336 #endif
338 static int download(const char *url, const char *localpath,
339 int force) {
340 if(handle->fetchcb == NULL) {
341 #ifdef HAVE_FETCH
342 return(download_internal(url, localpath, force));
343 #else
344 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD, -1);
345 #endif
346 } else {
347 int ret = handle->fetchcb(url, localpath, force);
348 if(ret == -1) {
349 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD, -1);
351 return(ret);
356 * Download a single file
357 * - servers must be a list of urls WITHOUT trailing slashes.
359 * RETURN: 0 for successful download
360 * 1 if the files are identical
361 * -1 on error
363 int _alpm_download_single_file(const char *filename,
364 alpm_list_t *servers, const char *localpath,
365 int force)
367 alpm_list_t *i;
368 int ret = -1;
370 ASSERT(servers != NULL, RET_ERR(PM_ERR_SERVER_NONE, -1));
372 for(i = servers; i; i = i->next) {
373 const char *server = i->data;
374 char *fileurl = NULL;
375 size_t len;
377 /* print server + filename into a buffer */
378 len = strlen(server) + strlen(filename) + 2;
379 CALLOC(fileurl, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, -1));
380 snprintf(fileurl, len, "%s/%s", server, filename);
382 ret = download(fileurl, localpath, force);
383 FREE(fileurl);
384 if(ret != -1) {
385 break;
389 return(ret);
392 int _alpm_download_files(alpm_list_t *files,
393 alpm_list_t *servers, const char *localpath)
395 int ret = 0;
396 alpm_list_t *lp;
398 for(lp = files; lp; lp = lp->next) {
399 char *filename = lp->data;
400 if(_alpm_download_single_file(filename, servers,
401 localpath, 0) == -1) {
402 ret++;
406 return(ret);
409 /** Fetch a remote pkg.
410 * @param url URL of the package to download
411 * @return the downloaded filepath on success, NULL on error
412 * @addtogroup alpm_misc
414 char SYMEXPORT *alpm_fetch_pkgurl(const char *url)
416 char *filename, *filepath;
417 const char *cachedir;
418 int ret;
420 ALPM_LOG_FUNC;
422 filename = get_filename(url);
424 /* find a valid cache dir to download to */
425 cachedir = _alpm_filecache_setup();
427 /* download the file */
428 ret = download(url, cachedir, 0);
429 if(ret == -1) {
430 _alpm_log(PM_LOG_WARNING, _("failed to download %s\n"), url);
431 return(NULL);
433 _alpm_log(PM_LOG_DEBUG, "successfully downloaded %s\n", url);
435 /* we should be able to find the file the second time around */
436 filepath = _alpm_filecache_find(filename);
437 return(filepath);
440 /* vim: set ts=2 sw=2 noet: */