pacman: list all unknown targets on removal operation
[pacman-ng.git] / lib / libalpm / dload.c
blobbcbc8095203078abf9a5395e8418d41ac2cca5ad
1 /*
2 * download.c
4 * Copyright (c) 2006-2011 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>
33 #ifdef HAVE_LIBCURL
34 #include <curl/curl.h>
35 #endif
37 /* libalpm */
38 #include "dload.h"
39 #include "alpm_list.h"
40 #include "alpm.h"
41 #include "log.h"
42 #include "util.h"
43 #include "handle.h"
45 #ifdef HAVE_LIBCURL
46 static const char *get_filename(const char *url)
48 char *filename = strrchr(url, '/');
49 if(filename != NULL) {
50 filename++;
52 return filename;
55 static char *get_fullpath(const char *path, const char *filename,
56 const char *suffix)
58 char *filepath;
59 /* len = localpath len + filename len + suffix len + null */
60 size_t len = strlen(path) + strlen(filename) + strlen(suffix) + 1;
61 MALLOC(filepath, len, return NULL);
62 snprintf(filepath, len, "%s%s%s", path, filename, suffix);
64 return filepath;
67 static CURL *get_libcurl_handle(alpm_handle_t *handle)
69 if(!handle->curl) {
70 curl_global_init(CURL_GLOBAL_SSL);
71 handle->curl = curl_easy_init();
73 return handle->curl;
76 enum {
77 ABORT_SIGINT = 1,
78 ABORT_OVER_MAXFILESIZE
81 static int dload_interrupted;
82 static void inthandler(int UNUSED signum)
84 dload_interrupted = ABORT_SIGINT;
87 static int curl_progress(void *file, double dltotal, double dlnow,
88 double UNUSED ultotal, double UNUSED ulnow)
90 struct dload_payload *payload = (struct dload_payload *)file;
91 off_t current_size, total_size;
93 /* SIGINT sent, abort by alerting curl */
94 if(dload_interrupted) {
95 return 1;
98 current_size = payload->initial_size + (off_t)dlnow;
100 /* is our filesize still under any set limit? */
101 if(payload->max_size && current_size > payload->max_size) {
102 dload_interrupted = ABORT_OVER_MAXFILESIZE;
103 return 1;
106 /* none of what follows matters if the front end has no callback */
107 if(payload->handle->dlcb == NULL) {
108 return 0;
111 total_size = payload->initial_size + (off_t)dltotal;
113 if(DOUBLE_EQ(dltotal, 0.0) || payload->prevprogress == total_size) {
114 return 0;
117 /* initialize the progress bar here to avoid displaying it when
118 * a repo is up to date and nothing gets downloaded */
119 if(payload->prevprogress == 0) {
120 payload->handle->dlcb(payload->remote_name, 0, (off_t)dltotal);
123 payload->handle->dlcb(payload->remote_name, current_size, total_size);
125 payload->prevprogress = current_size;
127 return 0;
130 static int curl_gethost(const char *url, char *buffer, size_t buf_len)
132 size_t hostlen;
133 char *p, *q;
135 if(strncmp(url, "file://", 7) == 0) {
136 p = _("disk");
137 hostlen = strlen(p);
138 } else {
139 p = strstr(url, "//");
140 if(!p) {
141 return 1;
143 p += 2; /* jump over the found // */
144 hostlen = strcspn(p, "/");
146 /* there might be a user:pass@ on the URL. hide it. avoid using memrchr()
147 * for portability concerns. */
148 q = p + hostlen;
149 while(--q > p) {
150 if(*q == '@') {
151 break;
154 if(*q == '@' && p != q) {
155 hostlen -= q - p + 1;
156 p = q + 1;
160 if(hostlen > buf_len - 1) {
161 /* buffer overflow imminent */
162 return 1;
164 memcpy(buffer, p, hostlen);
165 buffer[hostlen] = '\0';
167 return 0;
170 static int utimes_long(const char *path, long seconds)
172 if(seconds != -1) {
173 struct timeval tv[2];
174 memset(&tv, 0, sizeof(tv));
175 tv[0].tv_sec = tv[1].tv_sec = seconds;
176 return utimes(path, tv);
178 return 0;
181 /* prefix to avoid possible future clash with getumask(3) */
182 static mode_t _getumask(void)
184 mode_t mask = umask(0);
185 umask(mask);
186 return mask;
189 static size_t parse_headers(void *ptr, size_t size, size_t nmemb, void *user)
191 size_t realsize = size * nmemb;
192 const char *fptr, *endptr = NULL;
193 const char * const cd_header = "Content-Disposition:";
194 const char * const fn_key = "filename=";
195 struct dload_payload *payload = (struct dload_payload *)user;
197 if(_alpm_raw_ncmp(cd_header, ptr, strlen(cd_header)) == 0) {
198 if((fptr = strstr(ptr, fn_key))) {
199 fptr += strlen(fn_key);
201 /* find the end of the field, which is either a semi-colon, or the end of
202 * the data. As per curl_easy_setopt(3), we cannot count on headers being
203 * null terminated, so we look for the closing \r\n */
204 endptr = fptr + strcspn(fptr, ";\r\n") - 1;
206 /* remove quotes */
207 if(*fptr == '"' && *endptr == '"') {
208 fptr++;
209 endptr--;
212 STRNDUP(payload->content_disp_name, fptr, endptr - fptr + 1,
213 RET_ERR(payload->handle, ALPM_ERR_MEMORY, realsize));
217 return realsize;
220 static void curl_set_handle_opts(struct dload_payload *payload,
221 CURL *curl, char *error_buffer)
223 alpm_handle_t *handle = payload->handle;
224 const char *useragent = getenv("HTTP_USER_AGENT");
225 struct stat st;
227 /* the curl_easy handle is initialized with the alpm handle, so we only need
228 * to reset the handle's parameters for each time it's used. */
229 curl_easy_reset(curl);
230 curl_easy_setopt(curl, CURLOPT_URL, payload->fileurl);
231 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
232 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer);
233 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
234 curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
235 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
236 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
237 curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress);
238 curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void *)payload);
239 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L);
240 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10L);
241 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_headers);
242 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *)payload);
243 curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
245 _alpm_log(handle, ALPM_LOG_DEBUG, "url: %s\n", payload->fileurl);
247 if(payload->max_size) {
248 _alpm_log(handle, ALPM_LOG_DEBUG, "maxsize: %jd\n",
249 (intmax_t)payload->max_size);
250 curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE,
251 (curl_off_t)payload->max_size);
254 if(useragent != NULL) {
255 curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
258 if(!payload->allow_resume && !payload->force && payload->destfile_name &&
259 stat(payload->destfile_name, &st) == 0) {
260 /* start from scratch, but only download if our local is out of date. */
261 curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
262 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, (long)st.st_mtime);
263 _alpm_log(handle, ALPM_LOG_DEBUG,
264 "using time condition: %lu\n", (long)st.st_mtime);
265 } else if(stat(payload->tempfile_name, &st) == 0 && payload->allow_resume) {
266 /* a previous partial download exists, resume from end of file. */
267 payload->tempfile_openmode = "ab";
268 curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)st.st_size);
269 _alpm_log(handle, ALPM_LOG_DEBUG,
270 "tempfile found, attempting continuation from %jd bytes\n",
271 (intmax_t)st.st_size);
272 payload->initial_size = st.st_size;
276 static void mask_signal(int signal, void (*handler)(int),
277 struct sigaction *origaction)
279 struct sigaction newaction;
281 newaction.sa_handler = handler;
282 sigemptyset(&newaction.sa_mask);
283 newaction.sa_flags = 0;
285 sigaction(signal, NULL, origaction);
286 sigaction(signal, &newaction, NULL);
289 static void unmask_signal(int signal, struct sigaction sa)
291 sigaction(signal, &sa, NULL);
294 static FILE *create_tempfile(struct dload_payload *payload, const char *localpath)
296 int fd;
297 FILE *fp;
298 char *randpath;
299 size_t len;
301 /* create a random filename, which is opened with O_EXCL */
302 len = strlen(localpath) + 14 + 1;
303 MALLOC(randpath, len, RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
304 snprintf(randpath, len, "%salpmtmp.XXXXXX", localpath);
305 if((fd = mkstemp(randpath)) == -1 ||
306 fchmod(fd, ~(_getumask()) & 0666) ||
307 !(fp = fdopen(fd, payload->tempfile_openmode))) {
308 unlink(randpath);
309 CLOSE(fd);
310 _alpm_log(payload->handle, ALPM_LOG_ERROR,
311 _("failed to create temporary file for download\n"));
312 return NULL;
314 /* fp now points to our alpmtmp.XXXXXX */
315 free(payload->tempfile_name);
316 payload->tempfile_name = randpath;
317 free(payload->remote_name);
318 STRDUP(payload->remote_name, strrchr(randpath, '/') + 1,
319 RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
321 return fp;
324 /* RFC1123 states applications should support this length */
325 #define HOSTNAME_SIZE 256
327 static int curl_download_internal(struct dload_payload *payload,
328 const char *localpath, char **final_file)
330 int ret = -1;
331 FILE *localf = NULL;
332 char *effective_url;
333 char hostname[HOSTNAME_SIZE];
334 char error_buffer[CURL_ERROR_SIZE] = {0};
335 struct stat st;
336 long timecond, respcode = 0, remote_time = -1;
337 double remote_size, bytes_dl;
338 struct sigaction orig_sig_pipe, orig_sig_int;
339 /* shortcut to our handle within the payload */
340 alpm_handle_t *handle = payload->handle;
341 CURL *curl = get_libcurl_handle(handle);
342 handle->pm_errno = 0;
344 payload->tempfile_openmode = "wb";
345 if(!payload->remote_name) {
346 STRDUP(payload->remote_name, get_filename(payload->fileurl),
347 RET_ERR(handle, ALPM_ERR_MEMORY, -1));
349 if(curl_gethost(payload->fileurl, hostname, sizeof(hostname)) != 0) {
350 _alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), payload->fileurl);
351 RET_ERR(handle, ALPM_ERR_SERVER_BAD_URL, -1);
354 if(strlen(payload->remote_name) > 0 && strcmp(payload->remote_name, ".sig") != 0) {
355 payload->destfile_name = get_fullpath(localpath, payload->remote_name, "");
356 payload->tempfile_name = get_fullpath(localpath, payload->remote_name, ".part");
357 if(!payload->destfile_name || !payload->tempfile_name) {
358 goto cleanup;
360 } else {
361 /* URL doesn't contain a filename, so make a tempfile. We can't support
362 * resuming this kind of download; partial transfers will be destroyed */
363 payload->unlink_on_fail = 1;
365 localf = create_tempfile(payload, localpath);
366 if(localf == NULL) {
367 goto cleanup;
371 curl_set_handle_opts(payload, curl, error_buffer);
373 if(localf == NULL) {
374 localf = fopen(payload->tempfile_name, payload->tempfile_openmode);
375 if(localf == NULL) {
376 goto cleanup;
380 _alpm_log(handle, ALPM_LOG_DEBUG,
381 "opened tempfile for download: %s (%s)\n", payload->tempfile_name,
382 payload->tempfile_openmode);
384 curl_easy_setopt(curl, CURLOPT_WRITEDATA, localf);
386 /* ignore any SIGPIPE signals- these may occur if our FTP socket dies or
387 * something along those lines. Store the old signal handler first. */
388 mask_signal(SIGPIPE, SIG_IGN, &orig_sig_pipe);
389 mask_signal(SIGINT, &inthandler, &orig_sig_int);
391 /* perform transfer */
392 payload->curlerr = curl_easy_perform(curl);
394 /* disconnect relationships from the curl handle for things that might go out
395 * of scope, but could still be touched on connection teardown. This really
396 * only applies to FTP transfers. See FS#26327 for an example. */
397 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
398 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, (char *)NULL);
400 /* was it a success? */
401 switch(payload->curlerr) {
402 case CURLE_OK:
403 /* get http/ftp response code */
404 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &respcode);
405 _alpm_log(handle, ALPM_LOG_DEBUG, "response code: %ld\n", respcode);
406 if(respcode >= 400) {
407 payload->unlink_on_fail = 1;
408 /* non-translated message is same as libcurl */
409 snprintf(error_buffer, sizeof(error_buffer),
410 "The requested URL returned error: %ld", respcode);
411 _alpm_log(handle, ALPM_LOG_ERROR,
412 _("failed retrieving file '%s' from %s : %s\n"),
413 payload->remote_name, hostname, error_buffer);
414 goto cleanup;
416 break;
417 case CURLE_ABORTED_BY_CALLBACK:
418 /* handle the interrupt accordingly */
419 if(dload_interrupted == ABORT_OVER_MAXFILESIZE) {
420 payload->curlerr = CURLE_FILESIZE_EXCEEDED;
421 handle->pm_errno = ALPM_ERR_LIBCURL;
422 /* use the 'size exceeded' message from libcurl */
423 _alpm_log(handle, ALPM_LOG_ERROR,
424 _("failed retrieving file '%s' from %s : %s\n"),
425 payload->remote_name, hostname,
426 curl_easy_strerror(CURLE_FILESIZE_EXCEEDED));
428 goto cleanup;
429 default:
430 /* delete zero length downloads */
431 if(fstat(fileno(localf), &st) == 0 && st.st_size == 0) {
432 payload->unlink_on_fail = 1;
434 if(!payload->errors_ok) {
435 handle->pm_errno = ALPM_ERR_LIBCURL;
436 _alpm_log(handle, ALPM_LOG_ERROR,
437 _("failed retrieving file '%s' from %s : %s\n"),
438 payload->remote_name, hostname, error_buffer);
439 } else {
440 _alpm_log(handle, ALPM_LOG_DEBUG,
441 "failed retrieving file '%s' from %s : %s\n",
442 payload->remote_name, hostname, error_buffer);
444 goto cleanup;
447 /* retrieve info about the state of the transfer */
448 curl_easy_getinfo(curl, CURLINFO_FILETIME, &remote_time);
449 curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &remote_size);
450 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes_dl);
451 curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &timecond);
452 curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
454 /* time condition was met and we didn't download anything. we need to
455 * clean up the 0 byte .part file that's left behind. */
456 if(timecond == 1 && DOUBLE_EQ(bytes_dl, 0)) {
457 _alpm_log(handle, ALPM_LOG_DEBUG, "file met time condition\n");
458 ret = 1;
459 unlink(payload->tempfile_name);
460 goto cleanup;
463 /* remote_size isn't necessarily the full size of the file, just what the
464 * server reported as remaining to download. compare it to what curl reported
465 * as actually being transferred during curl_easy_perform() */
466 if(!DOUBLE_EQ(remote_size, -1) && !DOUBLE_EQ(bytes_dl, -1) &&
467 !DOUBLE_EQ(bytes_dl, remote_size)) {
468 handle->pm_errno = ALPM_ERR_RETRIEVE;
469 _alpm_log(handle, ALPM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
470 payload->remote_name, (intmax_t)bytes_dl, (intmax_t)remote_size);
471 goto cleanup;
474 if(payload->content_disp_name) {
475 /* content-disposition header has a better name for our file */
476 free(payload->destfile_name);
477 payload->destfile_name = get_fullpath(localpath, payload->content_disp_name, "");
478 } else {
479 const char *effective_filename = strrchr(effective_url, '/');
480 if(effective_filename && strlen(effective_filename) > 2) {
481 effective_filename++;
483 /* if destfile was never set, we wrote to a tempfile. even if destfile is
484 * set, we may have followed some redirects and the effective url may
485 * have a better suggestion as to what to name our file. in either case,
486 * refactor destfile to this newly derived name. */
487 if(!payload->destfile_name || strcmp(effective_filename,
488 strrchr(payload->destfile_name, '/') + 1) != 0) {
489 free(payload->destfile_name);
490 payload->destfile_name = get_fullpath(localpath, effective_filename, "");
495 ret = 0;
497 cleanup:
498 if(localf != NULL) {
499 fclose(localf);
500 utimes_long(payload->tempfile_name, remote_time);
503 if(ret == 0) {
504 const char *realname = payload->tempfile_name;
505 if(payload->destfile_name) {
506 realname = payload->destfile_name;
507 if(rename(payload->tempfile_name, payload->destfile_name)) {
508 _alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
509 payload->tempfile_name, payload->destfile_name, strerror(errno));
510 ret = -1;
513 if(ret != -1 && final_file) {
514 STRDUP(*final_file, strrchr(realname, '/') + 1,
515 RET_ERR(handle, ALPM_ERR_MEMORY, -1));
519 if((ret == -1 || dload_interrupted) && payload->unlink_on_fail &&
520 payload->tempfile_name) {
521 unlink(payload->tempfile_name);
524 /* restore the old signal handlers */
525 unmask_signal(SIGINT, orig_sig_int);
526 unmask_signal(SIGPIPE, orig_sig_pipe);
527 /* if we were interrupted, trip the old handler */
528 if(dload_interrupted) {
529 raise(SIGINT);
532 return ret;
534 #endif
536 /** Download a file given by a URL to a local directory.
537 * Does not overwrite an existing file if the download fails.
538 * @param payload the payload context
539 * @param localpath the directory to save the file in
540 * @param final_file the real name of the downloaded file (may be NULL)
541 * @return 0 on success, -1 on error (pm_errno is set accordingly if errors_ok == 0)
543 int _alpm_download(struct dload_payload *payload, const char *localpath,
544 char **final_file)
546 alpm_handle_t *handle = payload->handle;
548 if(handle->fetchcb == NULL) {
549 #ifdef HAVE_LIBCURL
550 return curl_download_internal(payload, localpath, final_file);
551 #else
552 RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
553 #endif
554 } else {
555 int ret = handle->fetchcb(payload->fileurl, localpath, payload->force);
556 if(ret == -1 && !payload->errors_ok) {
557 RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
559 return ret;
563 /** Fetch a remote pkg. */
564 char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
566 char *filepath;
567 const char *cachedir;
568 char *final_file = NULL;
569 struct dload_payload payload;
570 int ret;
572 CHECK_HANDLE(handle, return NULL);
573 ASSERT(url, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, NULL));
575 /* find a valid cache dir to download to */
576 cachedir = _alpm_filecache_setup(handle);
578 memset(&payload, 0, sizeof(struct dload_payload));
579 payload.handle = handle;
580 STRDUP(payload.fileurl, url, RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
581 payload.allow_resume = 1;
583 /* download the file */
584 ret = _alpm_download(&payload, cachedir, &final_file);
585 _alpm_dload_payload_reset(&payload);
586 if(ret == -1) {
587 _alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), url);
588 free(final_file);
589 return NULL;
591 _alpm_log(handle, ALPM_LOG_DEBUG, "successfully downloaded %s\n", url);
593 /* attempt to download the signature */
594 if(ret == 0 && (handle->siglevel & ALPM_SIG_PACKAGE)) {
595 char *sig_final_file = NULL;
596 size_t len;
598 len = strlen(url) + 5;
599 MALLOC(payload.fileurl, len, RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
600 snprintf(payload.fileurl, len, "%s.sig", url);
601 payload.handle = handle;
602 payload.force = 1;
603 payload.errors_ok = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL);
605 /* set hard upper limit of 16KiB */
606 payload.max_size = 16 * 1024;
608 ret = _alpm_download(&payload, cachedir, &sig_final_file);
609 if(ret == -1 && !payload.errors_ok) {
610 _alpm_log(handle, ALPM_LOG_WARNING,
611 _("failed to download %s\n"), payload.fileurl);
612 /* Warn now, but don't return NULL. We will fail later during package
613 * load time. */
614 } else if(ret == 0) {
615 _alpm_log(handle, ALPM_LOG_DEBUG,
616 "successfully downloaded %s\n", payload.fileurl);
618 FREE(sig_final_file);
619 _alpm_dload_payload_reset(&payload);
622 /* we should be able to find the file the second time around */
623 filepath = _alpm_filecache_find(handle, final_file);
624 free(final_file);
626 return filepath;
629 void _alpm_dload_payload_reset(struct dload_payload *payload)
631 ASSERT(payload, return);
633 FREE(payload->remote_name);
634 FREE(payload->tempfile_name);
635 FREE(payload->destfile_name);
636 FREE(payload->content_disp_name);
637 FREE(payload->fileurl);
640 /* vim: set ts=2 sw=2 noet: */