1467 lines
42 KiB
C
1467 lines
42 KiB
C
/*
|
|
* dload.c
|
|
*
|
|
* Copyright (c) 2006-2025 Pacman Development Team <pacman-dev@lists.archlinux.org>
|
|
* Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h> /* setsockopt, SO_KEEPALIVE */
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#include <dirent.h>
|
|
#include <pwd.h>
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h> /* IPPROTO_TCP */
|
|
#endif
|
|
#ifdef HAVE_NETINET_TCP_H
|
|
#include <netinet/tcp.h> /* TCP_KEEPINTVL, TCP_KEEPIDLE */
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBCURL
|
|
#include <curl/curl.h>
|
|
#endif
|
|
|
|
/* libalpm */
|
|
#include "dload.h"
|
|
#include "alpm_list.h"
|
|
#include "alpm.h"
|
|
#include "log.h"
|
|
#include "util.h"
|
|
#include "handle.h"
|
|
#include "sandbox.h"
|
|
|
|
|
|
static const char *get_filename(const char *url)
|
|
{
|
|
char *filename = strrchr(url, '/');
|
|
if(filename != NULL) {
|
|
return filename + 1;
|
|
}
|
|
|
|
/* no slash found, it's a filename */
|
|
return url;
|
|
}
|
|
|
|
/* prefix to avoid possible future clash with getumask(3) */
|
|
static mode_t _getumask(void)
|
|
{
|
|
mode_t mask = umask(0);
|
|
umask(mask);
|
|
return mask;
|
|
}
|
|
|
|
static int finalize_download_file(const char *filename)
|
|
{
|
|
struct stat st;
|
|
uid_t myuid = getuid();
|
|
ASSERT(filename != NULL, return -1);
|
|
ASSERT(stat(filename, &st) == 0, return -1);
|
|
if(st.st_size == 0) {
|
|
unlink(filename);
|
|
return 1;
|
|
}
|
|
if(myuid == 0) {
|
|
ASSERT(chown(filename, 0, 0) != -1, return -1);
|
|
}
|
|
ASSERT(chmod(filename, ~(_getumask()) & 0666) != -1, return -1);
|
|
return 0;
|
|
}
|
|
|
|
static FILE *create_tempfile(struct dload_payload *payload, const char *localpath)
|
|
{
|
|
int fd;
|
|
FILE *fp;
|
|
char *randpath;
|
|
size_t len;
|
|
|
|
/* create a random filename, which is opened with O_EXCL */
|
|
len = strlen(localpath) + 14 + 1;
|
|
MALLOC(randpath, len, RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
|
|
snprintf(randpath, len, "%salpmtmp.XXXXXX", localpath);
|
|
if((fd = mkstemp(randpath)) == -1 ||
|
|
fchmod(fd, ~(_getumask()) & 0666) ||
|
|
!(fp = fdopen(fd, payload->tempfile_openmode))) {
|
|
unlink(randpath);
|
|
close(fd);
|
|
_alpm_log(payload->handle, ALPM_LOG_ERROR,
|
|
_("failed to create temporary file for download\n"));
|
|
free(randpath);
|
|
return NULL;
|
|
}
|
|
/* fp now points to our alpmtmp.XXXXXX */
|
|
free(payload->tempfile_name);
|
|
payload->tempfile_name = randpath;
|
|
free(payload->remote_name);
|
|
STRDUP(payload->remote_name, strrchr(randpath, '/') + 1,
|
|
fclose(fp); RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
|
|
|
|
return fp;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_LIBCURL
|
|
|
|
/* RFC1123 states applications should support this length */
|
|
#define HOSTNAME_SIZE 256
|
|
|
|
static int curl_add_payload(alpm_handle_t *handle, CURLM *curlm,
|
|
struct dload_payload *payload);
|
|
static int curl_gethost(const char *url, char *buffer, size_t buf_len);
|
|
|
|
/* number of "soft" errors required to blacklist a server, set to 0 to disable
|
|
* server blacklisting */
|
|
const int server_error_limit = 3;
|
|
|
|
struct server_error_count {
|
|
char server[HOSTNAME_SIZE];
|
|
int errors;
|
|
};
|
|
|
|
static struct server_error_count *find_server_errors(alpm_handle_t *handle, const char *server)
|
|
{
|
|
alpm_list_t *i;
|
|
struct server_error_count *h;
|
|
char hostname[HOSTNAME_SIZE];
|
|
/* key off the hostname because a host may serve multiple repos under
|
|
* different url's and errors are likely to be host-wide */
|
|
if(curl_gethost(server, hostname, sizeof(hostname)) != 0) {
|
|
return NULL;
|
|
}
|
|
for(i = handle->server_errors; i; i = i->next) {
|
|
h = i->data;
|
|
if(strcmp(hostname, h->server) == 0) {
|
|
return h;
|
|
}
|
|
}
|
|
if((h = calloc(sizeof(struct server_error_count), 1))
|
|
&& alpm_list_append(&handle->server_errors, h)) {
|
|
strcpy(h->server, hostname);
|
|
h->errors = 0;
|
|
return h;
|
|
} else {
|
|
free(h);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* skip for hard errors or too many soft errors */
|
|
static int should_skip_server(alpm_handle_t *handle, const char *server)
|
|
{
|
|
struct server_error_count *h;
|
|
if(server_error_limit && (h = find_server_errors(handle, server)) ) {
|
|
return h->errors < 0 || h->errors >= server_error_limit;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* only skip for hard errors */
|
|
static int should_skip_cache_server(alpm_handle_t *handle, const char *server)
|
|
{
|
|
struct server_error_count *h;
|
|
if(server_error_limit && (h = find_server_errors(handle, server)) ) {
|
|
return h->errors < 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* block normal servers after too many errors */
|
|
static void server_soft_error(alpm_handle_t *handle, const char *server)
|
|
{
|
|
struct server_error_count *h;
|
|
if(server_error_limit
|
|
&& (h = find_server_errors(handle, server))
|
|
&& !should_skip_server(handle, server) ) {
|
|
h->errors++;
|
|
|
|
if(should_skip_server(handle, server)) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING,
|
|
_("too many errors from %s, skipping for the remainder of this transaction\n"),
|
|
h->server);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* immediate block for both servers and cache servers */
|
|
static void server_hard_error(alpm_handle_t *handle, const char *server)
|
|
{
|
|
struct server_error_count *h;
|
|
if(server_error_limit && (h = find_server_errors(handle, server))) {
|
|
if(h->errors != -1) {
|
|
/* always set even if already skipped for soft errors
|
|
* to disable cache servers too */
|
|
h->errors = -1;
|
|
|
|
_alpm_log(handle, ALPM_LOG_WARNING,
|
|
_("fatal error from %s, skipping for the remainder of this transaction\n"),
|
|
h->server);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *payload_next_server(struct dload_payload *payload)
|
|
{
|
|
while(payload->cache_servers
|
|
&& should_skip_cache_server(payload->handle, payload->cache_servers->data)) {
|
|
payload->cache_servers = payload->cache_servers->next;
|
|
}
|
|
if(payload->cache_servers) {
|
|
const char *server = payload->cache_servers->data;
|
|
payload->cache_servers = payload->cache_servers->next;
|
|
payload->request_errors_ok = 1;
|
|
return server;
|
|
}
|
|
while(payload->servers
|
|
&& should_skip_server(payload->handle, payload->servers->data)) {
|
|
payload->servers = payload->servers->next;
|
|
}
|
|
if(payload->servers) {
|
|
const char *server = payload->servers->data;
|
|
payload->servers = payload->servers->next;
|
|
payload->request_errors_ok = payload->errors_ok;
|
|
return server;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
enum {
|
|
ABORT_OVER_MAXFILESIZE = 1,
|
|
};
|
|
|
|
static int dload_interrupted;
|
|
|
|
static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow,
|
|
curl_off_t UNUSED ultotal, curl_off_t UNUSED ulnow)
|
|
{
|
|
struct dload_payload *payload = (struct dload_payload *)file;
|
|
off_t current_size, total_size;
|
|
alpm_download_event_progress_t cb_data = {0};
|
|
|
|
/* avoid displaying progress bar for redirects with a body */
|
|
if(payload->respcode >= 300) {
|
|
return 0;
|
|
}
|
|
|
|
/* SIGINT sent, abort by alerting curl */
|
|
if(dload_interrupted) {
|
|
return 1;
|
|
}
|
|
|
|
if(dlnow < 0 || dltotal <= 0 || dlnow > dltotal) {
|
|
/* bogus values : stop here */
|
|
return 0;
|
|
}
|
|
|
|
current_size = payload->initial_size + dlnow;
|
|
|
|
/* is our filesize still under any set limit? */
|
|
if(payload->max_size && current_size > payload->max_size) {
|
|
dload_interrupted = ABORT_OVER_MAXFILESIZE;
|
|
return 1;
|
|
}
|
|
|
|
/* none of what follows matters if the front end has no callback */
|
|
if(payload->handle->dlcb == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
total_size = payload->initial_size + dltotal;
|
|
|
|
if(payload->prevprogress == total_size) {
|
|
return 0;
|
|
}
|
|
|
|
/* do NOT include initial_size since it wasn't part of the package's
|
|
* download_size (nor included in the total download size callback) */
|
|
cb_data.total = dltotal;
|
|
cb_data.downloaded = dlnow;
|
|
payload->handle->dlcb(payload->handle->dlcb_ctx,
|
|
payload->remote_name, ALPM_DOWNLOAD_PROGRESS, &cb_data);
|
|
payload->prevprogress = current_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int curl_gethost(const char *url, char *buffer, size_t buf_len)
|
|
{
|
|
size_t hostlen;
|
|
char *p, *q;
|
|
|
|
if(strncmp(url, "file://", 7) == 0) {
|
|
p = _("disk");
|
|
hostlen = strlen(p);
|
|
} else {
|
|
p = strstr(url, "//");
|
|
if(!p) {
|
|
return 1;
|
|
}
|
|
p += 2; /* jump over the found // */
|
|
hostlen = strcspn(p, "/");
|
|
|
|
/* there might be a user:pass@ on the URL. hide it. avoid using memrchr()
|
|
* for portability concerns. */
|
|
q = p + hostlen;
|
|
while(--q > p) {
|
|
if(*q == '@') {
|
|
break;
|
|
}
|
|
}
|
|
if(*q == '@' && p != q) {
|
|
hostlen -= q - p + 1;
|
|
p = q + 1;
|
|
}
|
|
}
|
|
|
|
if(hostlen > buf_len - 1) {
|
|
/* buffer overflow imminent */
|
|
return 1;
|
|
}
|
|
memcpy(buffer, p, hostlen);
|
|
buffer[hostlen] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int utimes_long(const char *path, long seconds)
|
|
{
|
|
if(seconds != -1) {
|
|
struct timeval tv[2] = {
|
|
{ .tv_sec = seconds, },
|
|
{ .tv_sec = seconds, },
|
|
};
|
|
return utimes(path, tv);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static size_t dload_parseheader_cb(void *ptr, size_t size, size_t nmemb, void *user)
|
|
{
|
|
size_t realsize = size * nmemb;
|
|
struct dload_payload *payload = (struct dload_payload *)user;
|
|
long respcode;
|
|
(void) ptr;
|
|
|
|
curl_easy_getinfo(payload->curl, CURLINFO_RESPONSE_CODE, &respcode);
|
|
if(payload->respcode != respcode) {
|
|
payload->respcode = respcode;
|
|
}
|
|
|
|
return realsize;
|
|
}
|
|
|
|
static void curl_set_handle_opts(CURL *curl, struct dload_payload *payload)
|
|
{
|
|
alpm_handle_t *handle = payload->handle;
|
|
const char *useragent = getenv("HTTP_USER_AGENT");
|
|
struct stat st;
|
|
|
|
/* the curl_easy handle is initialized with the alpm handle, so we only need
|
|
* to reset the handle's parameters for each time it's used. */
|
|
curl_easy_reset(curl);
|
|
curl_easy_setopt(curl, CURLOPT_URL, payload->fileurl);
|
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, payload->error_buffer);
|
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L);
|
|
curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, dload_progress_cb);
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, (void *)payload);
|
|
if(!handle->disable_dl_timeout) {
|
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10L);
|
|
}
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, dload_parseheader_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)payload);
|
|
curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
|
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L);
|
|
curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
|
curl_easy_setopt(curl, CURLOPT_PRIVATE, (void *)payload);
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s: url is %s\n",
|
|
payload->remote_name, payload->fileurl);
|
|
|
|
if(payload->max_size) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s: maxsize %jd\n",
|
|
payload->remote_name, (intmax_t)payload->max_size);
|
|
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE,
|
|
(curl_off_t)payload->max_size);
|
|
}
|
|
|
|
if(useragent != NULL) {
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
|
}
|
|
|
|
if(!payload->force && payload->mtime_existing_file) {
|
|
/* start from scratch, but only download if our local is out of date. */
|
|
curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
|
|
curl_easy_setopt(curl, CURLOPT_TIMEVALUE, payload->mtime_existing_file);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"%s: using time condition %ld\n",
|
|
payload->remote_name, (long)payload->mtime_existing_file);
|
|
} else if(stat(payload->tempfile_name, &st) == 0 && payload->allow_resume) {
|
|
/* a previous partial download exists, resume from end of file. */
|
|
payload->tempfile_openmode = "ab";
|
|
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)st.st_size);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"%s: tempfile found, attempting continuation from %jd bytes\n",
|
|
payload->remote_name, (intmax_t)st.st_size);
|
|
payload->initial_size = st.st_size;
|
|
}
|
|
}
|
|
|
|
/* Return 0 if retry was successful, -1 otherwise */
|
|
static int curl_retry_next_server(CURLM *curlm, CURL *curl, struct dload_payload *payload)
|
|
{
|
|
const char *server = NULL;
|
|
size_t len;
|
|
struct stat st;
|
|
alpm_handle_t *handle = payload->handle;
|
|
|
|
if((server = payload_next_server(payload)) == NULL) {
|
|
_alpm_log(payload->handle, ALPM_LOG_DEBUG,
|
|
"%s: no more servers to retry\n", payload->remote_name);
|
|
return -1;
|
|
}
|
|
|
|
/* regenerate a new fileurl */
|
|
FREE(payload->fileurl);
|
|
len = strlen(server) + strlen(payload->filepath) + 2;
|
|
MALLOC(payload->fileurl, len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
snprintf(payload->fileurl, len, "%s/%s", server, payload->filepath);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"%s: retrying from %s\n",
|
|
payload->remote_name, payload->fileurl);
|
|
|
|
fflush(payload->localf);
|
|
|
|
if(payload->allow_resume && stat(payload->tempfile_name, &st) == 0) {
|
|
/* a previous partial download exists, resume from end of file. */
|
|
payload->tempfile_openmode = "ab";
|
|
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)st.st_size);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"%s: tempfile found, attempting continuation from %jd bytes\n",
|
|
payload->remote_name, (intmax_t)st.st_size);
|
|
payload->initial_size = st.st_size;
|
|
} else {
|
|
/* we keep the file for a new retry but remove its data if any */
|
|
if(ftruncate(fileno(payload->localf), 0)) {
|
|
RET_ERR(handle, ALPM_ERR_SYSTEM, -1);
|
|
}
|
|
fseek(payload->localf, 0, SEEK_SET);
|
|
}
|
|
|
|
if(handle->dlcb) {
|
|
alpm_download_event_retry_t cb_data;
|
|
cb_data.resume = payload->allow_resume;
|
|
handle->dlcb(handle->dlcb_ctx, payload->remote_name, ALPM_DOWNLOAD_RETRY, &cb_data);
|
|
}
|
|
|
|
/* Set curl with the new URL */
|
|
curl_easy_setopt(curl, CURLOPT_URL, payload->fileurl);
|
|
|
|
curl_multi_remove_handle(curlm, curl);
|
|
curl_multi_add_handle(curlm, curl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Returns 2 if download retry happened
|
|
* Returns 1 if the file is up-to-date
|
|
* Returns 0 if current payload is completed successfully
|
|
* Returns -1 if an error happened for a required file
|
|
* Returns -2 if an error happened for an optional file
|
|
*/
|
|
static int curl_check_finished_download(alpm_handle_t *handle, CURLM *curlm, CURLMsg *msg,
|
|
int *active_downloads_num)
|
|
{
|
|
struct dload_payload *payload = NULL;
|
|
CURL *curl = msg->easy_handle;
|
|
CURLcode curlerr;
|
|
char *effective_url;
|
|
long timecond;
|
|
curl_off_t remote_size;
|
|
curl_off_t bytes_dl = 0;
|
|
long remote_time = -1;
|
|
struct stat st;
|
|
char hostname[HOSTNAME_SIZE];
|
|
int ret = -1;
|
|
|
|
curlerr = curl_easy_getinfo(curl, CURLINFO_PRIVATE, &payload);
|
|
ASSERT(curlerr == CURLE_OK, RET_ERR(handle, ALPM_ERR_LIBCURL, -1));
|
|
|
|
curl_gethost(payload->fileurl, hostname, sizeof(hostname));
|
|
curlerr = msg->data.result;
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s: %s returned result %d from transfer\n",
|
|
payload->remote_name, "curl", curlerr);
|
|
|
|
/* was it a success? */
|
|
switch(curlerr) {
|
|
case CURLE_OK:
|
|
/* get http/ftp response code */
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s: response code %ld\n",
|
|
payload->remote_name, payload->respcode);
|
|
if(payload->respcode >= 400) {
|
|
if(!payload->request_errors_ok) {
|
|
handle->pm_errno = ALPM_ERR_RETRIEVE;
|
|
/* non-translated message is same as libcurl */
|
|
snprintf(payload->error_buffer, sizeof(payload->error_buffer),
|
|
"The requested URL returned error: %ld", payload->respcode);
|
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
|
_("failed retrieving file '%s' from %s : %s\n"),
|
|
payload->remote_name, hostname, payload->error_buffer);
|
|
server_soft_error(handle, payload->fileurl);
|
|
}
|
|
|
|
fflush(payload->localf);
|
|
if(fstat(fileno(payload->localf), &st) == 0 && st.st_size != payload->initial_size) {
|
|
/* an html error page was written to the file, reset it */
|
|
if(ftruncate(fileno(payload->localf), payload->initial_size)) {
|
|
RET_ERR(handle, ALPM_ERR_SYSTEM, -1);
|
|
}
|
|
fseek(payload->localf, payload->initial_size, SEEK_SET);
|
|
}
|
|
|
|
if(curl_retry_next_server(curlm, curl, payload) == 0) {
|
|
(*active_downloads_num)++;
|
|
return 2;
|
|
} else {
|
|
payload->unlink_on_fail = 1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
break;
|
|
case CURLE_ABORTED_BY_CALLBACK:
|
|
/* handle the interrupt accordingly */
|
|
if(dload_interrupted == ABORT_OVER_MAXFILESIZE) {
|
|
curlerr = CURLE_FILESIZE_EXCEEDED;
|
|
payload->unlink_on_fail = 1;
|
|
handle->pm_errno = ALPM_ERR_LIBCURL;
|
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
|
_("failed retrieving file '%s' from %s : expected download size exceeded\n"),
|
|
payload->remote_name, hostname);
|
|
server_soft_error(handle, payload->fileurl);
|
|
}
|
|
goto cleanup;
|
|
case CURLE_COULDNT_RESOLVE_HOST:
|
|
handle->pm_errno = ALPM_ERR_SERVER_BAD_URL;
|
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
|
_("failed retrieving file '%s' from %s : %s\n"),
|
|
payload->remote_name, hostname, payload->error_buffer);
|
|
server_hard_error(handle, payload->fileurl);
|
|
if(curl_retry_next_server(curlm, curl, payload) == 0) {
|
|
(*active_downloads_num)++;
|
|
return 2;
|
|
} else {
|
|
goto cleanup;
|
|
}
|
|
default:
|
|
if(!payload->request_errors_ok) {
|
|
handle->pm_errno = ALPM_ERR_LIBCURL;
|
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
|
_("failed retrieving file '%s' from %s : %s\n"),
|
|
payload->remote_name, hostname, payload->error_buffer);
|
|
server_soft_error(handle, payload->fileurl);
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"failed retrieving file '%s' from %s : %s\n",
|
|
payload->remote_name, hostname, payload->error_buffer);
|
|
}
|
|
if(curl_retry_next_server(curlm, curl, payload) == 0) {
|
|
(*active_downloads_num)++;
|
|
return 2;
|
|
} else {
|
|
/* delete zero length downloads */
|
|
if(fstat(fileno(payload->localf), &st) == 0 && st.st_size == 0) {
|
|
payload->unlink_on_fail = 1;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* retrieve info about the state of the transfer */
|
|
curl_easy_getinfo(curl, CURLINFO_FILETIME, &remote_time);
|
|
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &remote_size);
|
|
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &bytes_dl);
|
|
curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &timecond);
|
|
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
|
|
|
/* Let's check if client requested downloading accompanion *.sig file */
|
|
if(!payload->signature && payload->download_signature && curlerr == CURLE_OK && payload->respcode < 400) {
|
|
struct dload_payload *sig = NULL;
|
|
char *url = payload->fileurl;
|
|
char *_effective_filename;
|
|
const char *effective_filename;
|
|
char *query;
|
|
const char *dbext = alpm_option_get_dbext(handle);
|
|
const char* realname = payload->destfile_name ? payload->destfile_name : payload->tempfile_name;
|
|
int len;
|
|
|
|
STRDUP(_effective_filename, effective_url, GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
effective_filename = get_filename(_effective_filename);
|
|
query = strrchr(effective_filename, '?');
|
|
|
|
if(query) {
|
|
query[0] = '\0';
|
|
}
|
|
|
|
/* Only use the effective url for sig downloads if the effective_url contains .dbext or .pkg */
|
|
if(strstr(effective_filename, dbext) || strstr(effective_filename, ".pkg")) {
|
|
url = effective_url;
|
|
}
|
|
|
|
free(_effective_filename);
|
|
|
|
len = strlen(url) + 5;
|
|
CALLOC(sig, 1, sizeof(*sig), GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
MALLOC(sig->fileurl, len, FREE(sig); GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
snprintf(sig->fileurl, len, "%s.sig", url);
|
|
|
|
int remote_name_len = strlen(payload->remote_name) + 5;
|
|
MALLOC(sig->remote_name, remote_name_len, _alpm_dload_payload_reset(sig);
|
|
FREE(sig); GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
snprintf(sig->remote_name, remote_name_len, "%s.sig", payload->remote_name);
|
|
|
|
/* force the filename to be realname + ".sig" */
|
|
int destfile_name_len = strlen(realname) + 5;
|
|
MALLOC(sig->destfile_name, destfile_name_len, _alpm_dload_payload_reset(sig);
|
|
FREE(sig); GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
snprintf(sig->destfile_name, destfile_name_len, "%s.sig", realname);
|
|
|
|
int tempfile_name_len = strlen(realname) + 10;
|
|
MALLOC(sig->tempfile_name, tempfile_name_len, _alpm_dload_payload_reset(sig);
|
|
FREE(sig); GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
snprintf(sig->tempfile_name, tempfile_name_len, "%s.sig.part", realname);
|
|
|
|
|
|
sig->signature = 1;
|
|
sig->handle = handle;
|
|
sig->force = payload->force;
|
|
sig->unlink_on_fail = payload->unlink_on_fail;
|
|
sig->errors_ok = payload->signature_optional;
|
|
/* set hard upper limit of 16KiB */
|
|
sig->max_size = 16 * 1024;
|
|
|
|
curl_add_payload(handle, curlm, sig);
|
|
(*active_downloads_num)++;
|
|
}
|
|
|
|
/* time condition was met and we didn't download anything. we need to
|
|
* clean up the 0 byte .part file that's left behind. */
|
|
if(timecond == 1 && bytes_dl == 0) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s: file met time condition\n",
|
|
payload->remote_name);
|
|
ret = 1;
|
|
unlink(payload->tempfile_name);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* remote_size isn't necessarily the full size of the file, just what the
|
|
* server reported as remaining to download. compare it to what curl reported
|
|
* as actually being transferred during curl_easy_perform() */
|
|
if(remote_size != -1 && bytes_dl != -1 &&
|
|
bytes_dl != remote_size) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
|
|
payload->remote_name, (intmax_t)bytes_dl, (intmax_t)remote_size);
|
|
GOTO_ERR(handle, ALPM_ERR_RETRIEVE, cleanup);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
/* disconnect relationships from the curl handle for things that might go out
|
|
* of scope, but could still be touched on connection teardown. This really
|
|
* only applies to FTP transfers. */
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, (char *)NULL);
|
|
if(payload->localf != NULL) {
|
|
fclose(payload->localf);
|
|
payload->localf = NULL;
|
|
utimes_long(payload->tempfile_name, remote_time);
|
|
}
|
|
|
|
if(ret == 0) {
|
|
if(payload->destfile_name) {
|
|
if(rename(payload->tempfile_name, payload->destfile_name)) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
|
|
payload->tempfile_name, payload->destfile_name, strerror(errno));
|
|
ret = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if((ret == -1 || dload_interrupted) && payload->unlink_on_fail &&
|
|
payload->tempfile_name) {
|
|
unlink(payload->tempfile_name);
|
|
}
|
|
|
|
if(handle->dlcb) {
|
|
alpm_download_event_completed_t cb_data = {0};
|
|
cb_data.total = bytes_dl;
|
|
cb_data.result = ret;
|
|
handle->dlcb(handle->dlcb_ctx, payload->remote_name, ALPM_DOWNLOAD_COMPLETED, &cb_data);
|
|
}
|
|
|
|
curl_multi_remove_handle(curlm, curl);
|
|
curl_easy_cleanup(curl);
|
|
payload->curl = NULL;
|
|
|
|
FREE(payload->fileurl);
|
|
|
|
if(ret == -1 && payload->errors_ok) {
|
|
ret = -2;
|
|
}
|
|
|
|
if(payload->signature) {
|
|
/* free signature payload memory that was allocated earlier in dload.c */
|
|
_alpm_dload_payload_reset(payload);
|
|
FREE(payload);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Returns 0 in case if a new download transaction has been successfully started
|
|
* Returns -1 if am error happened while starting a new download
|
|
*/
|
|
static int curl_add_payload(alpm_handle_t *handle, CURLM *curlm,
|
|
struct dload_payload *payload)
|
|
{
|
|
size_t len;
|
|
CURL *curl = NULL;
|
|
char hostname[HOSTNAME_SIZE];
|
|
int ret = -1;
|
|
|
|
curl = curl_easy_init();
|
|
payload->curl = curl;
|
|
|
|
if(payload->fileurl) {
|
|
ASSERT(!payload->servers, GOTO_ERR(handle, ALPM_ERR_WRONG_ARGS, cleanup));
|
|
ASSERT(!payload->filepath, GOTO_ERR(handle, ALPM_ERR_WRONG_ARGS, cleanup));
|
|
payload->request_errors_ok = payload->errors_ok;
|
|
} else {
|
|
const char *server = payload_next_server(payload);
|
|
|
|
ASSERT(server, GOTO_ERR(handle, ALPM_ERR_SERVER_NONE, cleanup));
|
|
ASSERT(payload->filepath, GOTO_ERR(handle, ALPM_ERR_WRONG_ARGS, cleanup));
|
|
|
|
len = strlen(server) + strlen(payload->filepath) + 2;
|
|
MALLOC(payload->fileurl, len, GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
|
|
snprintf(payload->fileurl, len, "%s/%s", server, payload->filepath);
|
|
}
|
|
|
|
payload->tempfile_openmode = "wb";
|
|
if(curl_gethost(payload->fileurl, hostname, sizeof(hostname)) != 0) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), payload->fileurl);
|
|
GOTO_ERR(handle, ALPM_ERR_SERVER_BAD_URL, cleanup);
|
|
}
|
|
|
|
curl_set_handle_opts(curl, payload);
|
|
|
|
if(payload->max_size == payload->initial_size && payload->max_size != 0) {
|
|
/* .part file is complete */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(payload->localf == NULL) {
|
|
payload->localf = fopen(payload->tempfile_name, payload->tempfile_openmode);
|
|
if(payload->localf == NULL) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
|
_("could not open file %s: %s\n"),
|
|
payload->tempfile_name, strerror(errno));
|
|
GOTO_ERR(handle, ALPM_ERR_RETRIEVE, cleanup);
|
|
}
|
|
}
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"%s: opened tempfile for download: %s (%s)\n",
|
|
payload->remote_name,
|
|
payload->tempfile_name,
|
|
payload->tempfile_openmode);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, payload->localf);
|
|
curl_multi_add_handle(curlm, curl);
|
|
|
|
if(handle->dlcb) {
|
|
alpm_download_event_init_t cb_data = {.optional = payload->errors_ok};
|
|
handle->dlcb(handle->dlcb_ctx, payload->remote_name, ALPM_DOWNLOAD_INIT, &cb_data);
|
|
}
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
curl_easy_cleanup(curl);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Use to sort payloads by max size in descending order (largest -> smallest)
|
|
*/
|
|
static int compare_dload_payload_sizes(const void *left_ptr, const void *right_ptr)
|
|
{
|
|
struct dload_payload *left, *right;
|
|
|
|
left = (struct dload_payload *) left_ptr;
|
|
right = (struct dload_payload *) right_ptr;
|
|
|
|
return right->max_size - left->max_size;
|
|
}
|
|
|
|
/* Returns -1 if an error happened for a required file
|
|
* Returns 0 if a payload was actually downloaded
|
|
* Returns 1 if no files were downloaded and all errors were non-fatal
|
|
*/
|
|
static int curl_download_internal(alpm_handle_t *handle,
|
|
alpm_list_t *payloads /* struct dload_payload */)
|
|
{
|
|
int active_downloads_num = 0;
|
|
int err = 0;
|
|
int max_streams = handle->parallel_downloads;
|
|
int updated = 0; /* was a file actually updated */
|
|
CURLM *curlm = handle->curlm;
|
|
size_t payloads_size = alpm_list_count(payloads);
|
|
alpm_list_t *p;
|
|
|
|
/* Sort payloads by package size */
|
|
payloads = alpm_list_copy(payloads);
|
|
payloads = alpm_list_msort(payloads, payloads_size, &compare_dload_payload_sizes);
|
|
p = payloads;
|
|
|
|
while(active_downloads_num > 0 || p) {
|
|
CURLMcode mc;
|
|
|
|
for(; active_downloads_num < max_streams && p; active_downloads_num++) {
|
|
struct dload_payload *payload = p->data;
|
|
|
|
if(curl_add_payload(handle, curlm, payload) == 0) {
|
|
p = p->next;
|
|
} else {
|
|
/* The payload failed to start. Do not start any new downloads.
|
|
* Wait until all active downloads complete.
|
|
*/
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("failed to setup a download payload for %s\n"), payload->remote_name);
|
|
p = NULL;
|
|
err = -1;
|
|
}
|
|
}
|
|
|
|
mc = curl_multi_perform(curlm, &active_downloads_num);
|
|
if(mc == CURLM_OK) {
|
|
mc = curl_multi_wait(curlm, NULL, 0, 1000, NULL);
|
|
}
|
|
|
|
if(mc != CURLM_OK) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("curl returned error %d from transfer\n"), mc);
|
|
p = NULL;
|
|
err = -1;
|
|
}
|
|
while(true) {
|
|
int msgs_left = 0;
|
|
CURLMsg *msg = curl_multi_info_read(curlm, &msgs_left);
|
|
if(!msg) {
|
|
break;
|
|
}
|
|
if(msg->msg == CURLMSG_DONE) {
|
|
int ret = curl_check_finished_download(handle, curlm, msg,
|
|
&active_downloads_num);
|
|
if(ret == -1) {
|
|
/* if current payload failed to download then stop adding new payloads but wait for the
|
|
* current ones
|
|
*/
|
|
p = NULL;
|
|
err = -1;
|
|
} else if(ret == 0) {
|
|
updated = 1;
|
|
}
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("curl transfer error: %d\n"), msg->msg);
|
|
}
|
|
}
|
|
}
|
|
int ret = err ? -1 : updated ? 0 : 1;
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "curl_download_internal return code is %d\n", ret);
|
|
alpm_list_free(payloads);
|
|
return ret;
|
|
}
|
|
|
|
/* Download the requested files by launching a process inside a sandbox.
|
|
* Returns -1 if an error happened for a required file
|
|
* Returns 0 if a payload was actually downloaded
|
|
* Returns 1 if no files were downloaded and all errors were non-fatal
|
|
*/
|
|
static int curl_download_internal_sandboxed(alpm_handle_t *handle,
|
|
alpm_list_t *payloads /* struct dload_payload */,
|
|
const char *localpath,
|
|
int *childsig)
|
|
{
|
|
int pid, err = 0, ret = -1, callbacks_fd[2];
|
|
sigset_t oldblock;
|
|
struct sigaction sa_ign = { .sa_handler = SIG_IGN }, oldint, oldquit;
|
|
_alpm_sandbox_callback_context callbacks_ctx;
|
|
|
|
sigemptyset(&sa_ign.sa_mask);
|
|
|
|
if(pipe(callbacks_fd) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
sigaction(SIGINT, &sa_ign, &oldint);
|
|
sigaction(SIGQUIT, &sa_ign, &oldquit);
|
|
sigaddset(&sa_ign.sa_mask, SIGCHLD);
|
|
sigprocmask(SIG_BLOCK, &sa_ign.sa_mask, &oldblock);
|
|
|
|
pid = fork();
|
|
if(pid == -1) {
|
|
/* fork failed, make sure errno is preserved after cleanup */
|
|
err = errno;
|
|
}
|
|
|
|
/* child */
|
|
if(pid == 0) {
|
|
close(callbacks_fd[0]);
|
|
fcntl(callbacks_fd[1], F_SETFD, FD_CLOEXEC);
|
|
callbacks_ctx.callback_pipe = callbacks_fd[1];
|
|
alpm_option_set_logcb(handle, _alpm_sandbox_cb_log, &callbacks_ctx);
|
|
alpm_option_set_dlcb(handle, _alpm_sandbox_cb_dl, &callbacks_ctx);
|
|
alpm_option_set_fetchcb(handle, NULL, NULL);
|
|
alpm_option_set_eventcb(handle, NULL, NULL);
|
|
alpm_option_set_questioncb(handle, NULL, NULL);
|
|
alpm_option_set_progresscb(handle, NULL, NULL);
|
|
|
|
/* restore default signal handling in the child */
|
|
_alpm_reset_signals();
|
|
|
|
/* cwd to the download directory */
|
|
ret = chdir(localpath);
|
|
if(ret != 0) {
|
|
handle->pm_errno = ALPM_ERR_NOT_A_DIR;
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("could not chdir to download directory %s\n"), localpath);
|
|
ret = -1;
|
|
} else {
|
|
ret = alpm_sandbox_setup_child(handle, handle->sandboxuser, localpath, true);
|
|
if (ret != 0) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("switching to sandbox user '%s' failed!\n"), handle->sandboxuser);
|
|
_Exit(2);
|
|
}
|
|
|
|
ret = curl_download_internal(handle, payloads);
|
|
}
|
|
|
|
/* pass the result back to the parent */
|
|
if(ret == 0) {
|
|
/* a payload was actually downloaded */
|
|
_Exit(0);
|
|
}
|
|
else if(ret == 1) {
|
|
/* no files were downloaded and all errors were non-fatal */
|
|
_Exit(1);
|
|
}
|
|
else {
|
|
/* an error happened for a required file */
|
|
_Exit(2);
|
|
}
|
|
}
|
|
|
|
/* parent */
|
|
close(callbacks_fd[1]);
|
|
|
|
if(pid != -1) {
|
|
bool had_error = false;
|
|
while(true) {
|
|
_alpm_sandbox_callback_t callback_type;
|
|
ssize_t got = read(callbacks_fd[0], &callback_type, sizeof(callback_type));
|
|
if(got < 0 || (size_t)got != sizeof(callback_type)) {
|
|
had_error = true;
|
|
break;
|
|
}
|
|
|
|
if(callback_type == ALPM_SANDBOX_CB_DOWNLOAD) {
|
|
if(!_alpm_sandbox_process_cb_download(handle, callbacks_fd[0])) {
|
|
had_error = true;
|
|
break;
|
|
}
|
|
}
|
|
else if(callback_type == ALPM_SANDBOX_CB_LOG) {
|
|
if(!_alpm_sandbox_process_cb_log(handle, callbacks_fd[0])) {
|
|
had_error = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(had_error) {
|
|
kill(pid, SIGTERM);
|
|
}
|
|
|
|
int wret;
|
|
while((wret = waitpid(pid, &ret, 0)) == -1 && errno == EINTR);
|
|
if(wret > 0) {
|
|
if(WIFSIGNALED(ret)) {
|
|
*childsig = WTERMSIG(ret);
|
|
}
|
|
if(!WIFEXITED(ret)) {
|
|
/* the child did not terminate normally */
|
|
handle->pm_errno = ALPM_ERR_RETRIEVE;
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = WEXITSTATUS(ret);
|
|
if(ret != 0) {
|
|
if(ret == 2) {
|
|
/* an error happened for a required file, or unexpected exit status */
|
|
handle->pm_errno = ALPM_ERR_RETRIEVE;
|
|
ret = -1;
|
|
}
|
|
else {
|
|
handle->pm_errno = ALPM_ERR_RETRIEVE;
|
|
ret = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* waitpid failed */
|
|
err = errno;
|
|
}
|
|
}
|
|
|
|
close(callbacks_fd[0]);
|
|
|
|
sigaction(SIGINT, &oldint, NULL);
|
|
sigaction(SIGQUIT, &oldquit, NULL);
|
|
sigprocmask(SIG_SETMASK, &oldblock, NULL);
|
|
|
|
if(err) {
|
|
errno = err;
|
|
ret = -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int payload_download_fetchcb(struct dload_payload *payload,
|
|
const char *server, const char *localpath)
|
|
{
|
|
int ret;
|
|
char *fileurl;
|
|
alpm_handle_t *handle = payload->handle;
|
|
|
|
size_t len = strlen(server) + strlen(payload->filepath) + 2;
|
|
MALLOC(fileurl, len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
snprintf(fileurl, len, "%s/%s", server, payload->filepath);
|
|
|
|
ret = handle->fetchcb(handle->fetchcb_ctx, fileurl, localpath, payload->force);
|
|
free(fileurl);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int move_file(const char *filepath, const char *directory)
|
|
{
|
|
ASSERT(filepath != NULL, return -1);
|
|
ASSERT(directory != NULL, return -1);
|
|
int ret = finalize_download_file(filepath);
|
|
if(ret != 0) {
|
|
return ret;
|
|
}
|
|
const char *filename = mbasename(filepath);
|
|
char *dest = _alpm_get_fullpath(directory, filename, "");
|
|
if(rename(filepath, dest)) {
|
|
FREE(dest);
|
|
return -1;
|
|
}
|
|
FREE(dest);
|
|
return 0;
|
|
}
|
|
|
|
static int finalize_download_locations(alpm_list_t *payloads, const char *localpath)
|
|
{
|
|
ASSERT(payloads != NULL, return -1);
|
|
ASSERT(localpath != NULL, return -1);
|
|
alpm_list_t *p;
|
|
struct stat st;
|
|
int returnvalue = 0;
|
|
for(p = payloads; p; p = p->next) {
|
|
struct dload_payload *payload = p->data;
|
|
const char *filename = NULL;
|
|
|
|
if(payload->destfile_name && stat(payload->destfile_name, &st) == 0) {
|
|
filename = payload->destfile_name;
|
|
} else if(stat(payload->tempfile_name, &st) == 0) {
|
|
filename = payload->tempfile_name;
|
|
}
|
|
|
|
if(filename) {
|
|
int ret = move_file(filename, localpath);
|
|
|
|
if(ret == -1) {
|
|
if(payload->mtime_existing_file == 0) {
|
|
_alpm_log(payload->handle, ALPM_LOG_ERROR, _("could not move %s into %s (%s)\n"),
|
|
filename, localpath, strerror(errno));
|
|
returnvalue = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (payload->download_signature) {
|
|
char *sig_filename;
|
|
int ret;
|
|
|
|
filename = payload->destfile_name ? payload->destfile_name : payload->tempfile_name;
|
|
sig_filename = _alpm_get_fullpath("", filename, ".sig");
|
|
ASSERT(sig_filename, RET_ERR(payload->handle, ALPM_ERR_MEMORY, -1));
|
|
ret = move_file(sig_filename, localpath);
|
|
free(sig_filename);
|
|
|
|
if(ret == -1) {
|
|
sig_filename = _alpm_get_fullpath("", filename, ".sig.part");
|
|
ASSERT(sig_filename, RET_ERR(payload->handle, ALPM_ERR_MEMORY, -1));
|
|
move_file(sig_filename, localpath);
|
|
free(sig_filename);
|
|
}
|
|
}
|
|
}
|
|
return returnvalue;
|
|
}
|
|
|
|
static void prepare_resumable_downloads(alpm_list_t *payloads, const char *localpath,
|
|
const char *user)
|
|
{
|
|
struct passwd const *pw = NULL;
|
|
ASSERT(payloads != NULL, return);
|
|
ASSERT(localpath != NULL, return);
|
|
if(user != NULL) {
|
|
ASSERT((pw = getpwnam(user)) != NULL, return);
|
|
}
|
|
alpm_list_t *p;
|
|
for(p = payloads; p; p = p->next) {
|
|
struct dload_payload *payload = p->data;
|
|
if(payload->destfile_name) {
|
|
const char *destfilename = mbasename(payload->destfile_name);
|
|
char *dest = _alpm_get_fullpath(localpath, destfilename, "");
|
|
struct stat deststat;
|
|
if(stat(dest, &deststat) == 0 && deststat.st_size != 0) {
|
|
payload->mtime_existing_file = deststat.st_mtime;
|
|
}
|
|
FREE(dest);
|
|
}
|
|
if(!payload->tempfile_name) {
|
|
continue;
|
|
}
|
|
const char *filename = mbasename(payload->tempfile_name);
|
|
char *src = _alpm_get_fullpath(localpath, filename, "");
|
|
struct stat st;
|
|
if(stat(src, &st) != 0 || st.st_size == 0) {
|
|
FREE(src);
|
|
continue;
|
|
}
|
|
if(rename(src, payload->tempfile_name) != 0) {
|
|
FREE(src);
|
|
continue;
|
|
}
|
|
if(pw != NULL) {
|
|
ASSERT(chown(payload->tempfile_name, pw->pw_uid, pw->pw_gid), return);
|
|
}
|
|
FREE(src);
|
|
}
|
|
}
|
|
|
|
/* Returns -1 if an error happened for a required file
|
|
* Returns 0 if a payload was actually downloaded
|
|
* Returns 1 if no files were downloaded and all errors were non-fatal
|
|
*/
|
|
int _alpm_download(alpm_handle_t *handle,
|
|
alpm_list_t *payloads /* struct dload_payload */,
|
|
const char *localpath,
|
|
const char *temporary_localpath)
|
|
{
|
|
int ret;
|
|
int finalize_ret;
|
|
int childsig = 0;
|
|
prepare_resumable_downloads(payloads, localpath, handle->sandboxuser);
|
|
|
|
if(handle->fetchcb == NULL) {
|
|
#ifdef HAVE_LIBCURL
|
|
if(handle->sandboxuser) {
|
|
ret = curl_download_internal_sandboxed(handle, payloads, temporary_localpath, &childsig);
|
|
} else {
|
|
ret = curl_download_internal(handle, payloads);
|
|
}
|
|
#else
|
|
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
|
|
#endif
|
|
} else {
|
|
alpm_list_t *p;
|
|
int updated = 0;
|
|
for(p = payloads; p; p = p->next) {
|
|
struct dload_payload *payload = p->data;
|
|
alpm_list_t *s;
|
|
ret = -1;
|
|
|
|
if(payload->fileurl) {
|
|
ret = handle->fetchcb(handle->fetchcb_ctx, payload->fileurl, temporary_localpath, payload->force);
|
|
if (ret != -1 && payload->download_signature) {
|
|
/* Download signature if requested */
|
|
char *sig_fileurl;
|
|
size_t sig_len = strlen(payload->fileurl) + 5;
|
|
int retsig = -1;
|
|
|
|
MALLOC(sig_fileurl, sig_len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
snprintf(sig_fileurl, sig_len, "%s.sig", payload->fileurl);
|
|
|
|
retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, temporary_localpath, payload->force);
|
|
free(sig_fileurl);
|
|
|
|
if(!payload->signature_optional) {
|
|
ret = retsig;
|
|
}
|
|
}
|
|
} else {
|
|
for(s = payload->cache_servers; s; s = s->next) {
|
|
ret = payload_download_fetchcb(payload, s->data, temporary_localpath);
|
|
if (ret != -1) {
|
|
goto download_signature;
|
|
}
|
|
}
|
|
for(s = payload->servers; s; s = s->next) {
|
|
ret = payload_download_fetchcb(payload, s->data, temporary_localpath);
|
|
if (ret != -1) {
|
|
goto download_signature;
|
|
}
|
|
}
|
|
|
|
download_signature:
|
|
if (ret != -1 && payload->download_signature) {
|
|
/* Download signature if requested */
|
|
char *sig_fileurl;
|
|
size_t sig_len = strlen(s->data) + strlen(payload->filepath) + 6;
|
|
int retsig = -1;
|
|
|
|
MALLOC(sig_fileurl, sig_len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
snprintf(sig_fileurl, sig_len, "%s/%s.sig", (const char *)(s->data), payload->filepath);
|
|
|
|
retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, temporary_localpath, payload->force);
|
|
free(sig_fileurl);
|
|
|
|
if(!payload->signature_optional) {
|
|
ret = retsig;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ret == -1 && !payload->errors_ok) {
|
|
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
|
|
} else if(ret == 0) {
|
|
updated = 1;
|
|
}
|
|
}
|
|
ret = updated ? 0 : 1;
|
|
}
|
|
|
|
finalize_ret = finalize_download_locations(payloads, localpath);
|
|
_alpm_remove_temporary_download_dir(temporary_localpath);
|
|
|
|
/* propagate after finalizing so .part files get copied over */
|
|
if(childsig != 0) {
|
|
kill(getpid(), childsig);
|
|
}
|
|
if(finalize_ret != 0 && ret == 0) {
|
|
RET_ERR(handle, ALPM_ERR_RETRIEVE, -1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *url_basename(const char *url)
|
|
{
|
|
const char *filebase = strrchr(url, '/');
|
|
|
|
if(filebase == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
filebase++;
|
|
if(*filebase == '\0') {
|
|
return NULL;
|
|
}
|
|
|
|
return filebase;
|
|
}
|
|
|
|
int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls,
|
|
alpm_list_t **fetched)
|
|
{
|
|
const char *cachedir;
|
|
char *temporary_cachedir = NULL;
|
|
alpm_list_t *payloads = NULL;
|
|
const alpm_list_t *i;
|
|
alpm_event_t event = {0};
|
|
|
|
CHECK_HANDLE(handle, return -1);
|
|
ASSERT(*fetched == NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
|
|
|
|
/* find a valid cache dir to download to */
|
|
cachedir = _alpm_filecache_setup(handle);
|
|
temporary_cachedir = _alpm_temporary_download_dir_setup(cachedir, handle->sandboxuser);
|
|
ASSERT(temporary_cachedir != NULL, return -1);
|
|
|
|
for(i = urls; i; i = i->next) {
|
|
char *url = i->data;
|
|
char *filepath = NULL;
|
|
const char *urlbase = url_basename(url);
|
|
|
|
if(urlbase) {
|
|
/* attempt to find the file in our pkgcache */
|
|
filepath = _alpm_filecache_find(handle, urlbase);
|
|
|
|
if(filepath && (handle->siglevel & ALPM_SIG_PACKAGE)) {
|
|
char *sig_filename = _alpm_get_fullpath("", urlbase, ".sig");
|
|
|
|
/* if there's no .sig file then forget about the pkg file and go for download */
|
|
if(!_alpm_filecache_exists(handle, sig_filename)) {
|
|
free(filepath);
|
|
filepath = NULL;
|
|
}
|
|
|
|
free(sig_filename);
|
|
}
|
|
}
|
|
|
|
if(filepath) {
|
|
/* the file is locally cached so add it to the output right away */
|
|
alpm_list_append(fetched, filepath);
|
|
} else {
|
|
struct dload_payload *payload = NULL;
|
|
char *c;
|
|
|
|
ASSERT(url, GOTO_ERR(handle, ALPM_ERR_WRONG_ARGS, err));
|
|
CALLOC(payload, 1, sizeof(*payload), GOTO_ERR(handle, ALPM_ERR_MEMORY, err));
|
|
STRDUP(payload->fileurl, url, FREE(payload); GOTO_ERR(handle, ALPM_ERR_MEMORY, err));
|
|
|
|
STRDUP(payload->remote_name, get_filename(payload->fileurl),
|
|
GOTO_ERR(handle, ALPM_ERR_MEMORY, err));
|
|
|
|
c = strrchr(url, '/');
|
|
if(c != NULL && strstr(c, ".pkg") && payload->remote_name && strlen(payload->remote_name) > 0) {
|
|
/* we probably have a usable package filename to download to */
|
|
payload->destfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, "");
|
|
payload->tempfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, ".part");
|
|
payload->allow_resume = 1;
|
|
|
|
if(!payload->destfile_name || !payload->tempfile_name) {
|
|
goto err;
|
|
}
|
|
|
|
} else {
|
|
/* The URL does not contain a filename, so download to a temporary location.
|
|
* We can not support resuming this kind of download; any partial transfers
|
|
* will be destroyed */
|
|
payload->unlink_on_fail = 1;
|
|
|
|
payload->tempfile_openmode = "wb";
|
|
payload->localf = create_tempfile(payload, temporary_cachedir);
|
|
if(payload->localf == NULL) {
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
payload->handle = handle;
|
|
payload->download_signature = (handle->siglevel & ALPM_SIG_PACKAGE);
|
|
payload->signature_optional = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL);
|
|
payloads = alpm_list_add(payloads, payload);
|
|
}
|
|
}
|
|
|
|
if(payloads) {
|
|
event.type = ALPM_EVENT_PKG_RETRIEVE_START;
|
|
event.pkg_retrieve.num = alpm_list_count(payloads);
|
|
event.pkg_retrieve.total_size = 0;
|
|
EVENT(handle, &event);
|
|
if(_alpm_download(handle, payloads, cachedir, temporary_cachedir) == -1) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("failed to retrieve some files\n"));
|
|
event.type = ALPM_EVENT_PKG_RETRIEVE_FAILED;
|
|
EVENT(handle, &event);
|
|
GOTO_ERR(handle, ALPM_ERR_RETRIEVE, err);
|
|
} else {
|
|
event.type = ALPM_EVENT_PKG_RETRIEVE_DONE;
|
|
EVENT(handle, &event);
|
|
}
|
|
|
|
for(i = payloads; i; i = i->next) {
|
|
struct dload_payload *payload = i->data;
|
|
char *filepath;
|
|
|
|
if(payload->destfile_name) {
|
|
const char *filename = mbasename(payload->destfile_name);
|
|
filepath = _alpm_filecache_find(handle, filename);
|
|
} else {
|
|
const char *filename = mbasename(payload->tempfile_name);
|
|
filepath = _alpm_filecache_find(handle, filename);
|
|
}
|
|
if(filepath) {
|
|
alpm_list_append(fetched, filepath);
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("download completed successfully but no file in the cache\n"));
|
|
GOTO_ERR(handle, ALPM_ERR_RETRIEVE, err);
|
|
}
|
|
}
|
|
|
|
alpm_list_free_inner(payloads, (alpm_list_fn_free)_alpm_dload_payload_reset);
|
|
FREELIST(payloads);
|
|
}
|
|
|
|
FREE(temporary_cachedir);
|
|
return 0;
|
|
|
|
err:
|
|
alpm_list_free_inner(payloads, (alpm_list_fn_free)_alpm_dload_payload_reset);
|
|
FREE(temporary_cachedir);
|
|
FREELIST(payloads);
|
|
FREELIST(*fetched);
|
|
|
|
return -1;
|
|
}
|
|
|
|
void _alpm_dload_payload_reset(struct dload_payload *payload)
|
|
{
|
|
ASSERT(payload, return);
|
|
|
|
if(payload->localf != NULL) {
|
|
fclose(payload->localf);
|
|
payload->localf = NULL;
|
|
}
|
|
|
|
FREE(payload->remote_name);
|
|
FREE(payload->tempfile_name);
|
|
FREE(payload->destfile_name);
|
|
FREE(payload->fileurl);
|
|
FREE(payload->filepath);
|
|
*payload = (struct dload_payload){0};
|
|
}
|