mirror of
https://github.com/moparisthebest/pacman
synced 2024-11-10 03:25:01 -05:00
30c4d53ce5
This allows a frontend to define its own download algorithm so that the libfetch dependency can be omitted without using an external process. The callback will be used when if it is defined, otherwise the old behavior applies. Signed-off-by: Sebastian Nowicki <sebnow@gmail.com> [Dan: minor cleanups] Signed-off-by: Dan McGee <dan@archlinux.org>
373 lines
9.6 KiB
C
373 lines
9.6 KiB
C
/*
|
|
* download.c
|
|
*
|
|
* Copyright (c) 2006-2009 Pacman Development Team <pacman-dev@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 "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
/* the following two are needed on BSD for libfetch */
|
|
#if defined(HAVE_SYS_SYSLIMITS_H)
|
|
#include <sys/syslimits.h> /* PATH_MAX */
|
|
#endif
|
|
#if defined(HAVE_SYS_PARAM_H)
|
|
#include <sys/param.h> /* MAXHOSTNAMELEN */
|
|
#endif
|
|
|
|
#if defined(INTERNAL_DOWNLOAD)
|
|
#include <fetch.h>
|
|
#endif
|
|
|
|
/* libalpm */
|
|
#include "dload.h"
|
|
#include "alpm_list.h"
|
|
#include "alpm.h"
|
|
#include "log.h"
|
|
#include "util.h"
|
|
#include "handle.h"
|
|
|
|
static char *get_filename(const char *url) {
|
|
char *filename = strrchr(url, '/');
|
|
if(filename != NULL) {
|
|
filename++;
|
|
}
|
|
return(filename);
|
|
}
|
|
|
|
static char *get_destfile(const char *path, const char *filename) {
|
|
char *destfile;
|
|
/* len = localpath len + filename len + null */
|
|
int len = strlen(path) + strlen(filename) + 1;
|
|
CALLOC(destfile, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
|
|
snprintf(destfile, len, "%s%s", path, filename);
|
|
|
|
return(destfile);
|
|
}
|
|
|
|
static char *get_tempfile(const char *path, const char *filename) {
|
|
char *tempfile;
|
|
/* len = localpath len + filename len + '.part' len + null */
|
|
int len = strlen(path) + strlen(filename) + 6;
|
|
CALLOC(tempfile, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
|
|
snprintf(tempfile, len, "%s%s.part", path, filename);
|
|
|
|
return(tempfile);
|
|
}
|
|
|
|
#if defined(INTERNAL_DOWNLOAD)
|
|
/* Build a 'struct url' from an url. */
|
|
static struct url *url_for_string(const char *url)
|
|
{
|
|
struct url *ret = NULL;
|
|
ret = fetchParseURL(url);
|
|
if(!ret) {
|
|
_alpm_log(PM_LOG_ERROR, _("url '%s' is invalid\n"), url);
|
|
RET_ERR(PM_ERR_SERVER_BAD_URL, NULL);
|
|
}
|
|
|
|
/* if no URL scheme specified, assume HTTP */
|
|
if(strlen(ret->scheme) == 0) {
|
|
_alpm_log(PM_LOG_WARNING, _("url scheme not specified, assuming HTTP\n"));
|
|
strcpy(ret->scheme, SCHEME_HTTP);
|
|
}
|
|
/* add a user & password for anonymous FTP */
|
|
if(strcmp(ret->scheme,SCHEME_FTP) == 0 && strlen(ret->user) == 0) {
|
|
strcpy(ret->user, "anonymous");
|
|
strcpy(ret->pwd, "libalpm@guest");
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
static int download_internal(const char *url, const char *localpath,
|
|
time_t mtimeold, time_t *mtimenew) {
|
|
fetchIO *dlf = NULL;
|
|
FILE *localf = NULL;
|
|
struct url_stat ust;
|
|
struct stat st;
|
|
int chk_resume = 0, ret = 0;
|
|
size_t dl_thisfile = 0, nread = 0;
|
|
char *tempfile, *destfile, *filename;
|
|
struct sigaction new_action, old_action;
|
|
struct url *fileurl = url_for_string(url);
|
|
char buffer[PM_DLBUF_LEN];
|
|
|
|
if(!fileurl) {
|
|
return(-1);
|
|
}
|
|
|
|
filename = get_filename(url);
|
|
if(!filename) {
|
|
return(-1);
|
|
}
|
|
destfile = get_destfile(localpath, filename);
|
|
tempfile = get_tempfile(localpath, filename);
|
|
|
|
/* pass the raw filename for passing to the callback function */
|
|
_alpm_log(PM_LOG_DEBUG, "using '%s' for download progress\n", filename);
|
|
|
|
if(stat(tempfile, &st) == 0 && st.st_size > 0) {
|
|
_alpm_log(PM_LOG_DEBUG, "existing file found, using it\n");
|
|
fileurl->offset = (off_t)st.st_size;
|
|
dl_thisfile = st.st_size;
|
|
localf = fopen(tempfile, "ab");
|
|
chk_resume = 1;
|
|
} else {
|
|
fileurl->offset = (off_t)0;
|
|
dl_thisfile = 0;
|
|
}
|
|
|
|
/* print proxy info for debug purposes */
|
|
_alpm_log(PM_LOG_DEBUG, "HTTP_PROXY: %s\n", getenv("HTTP_PROXY"));
|
|
_alpm_log(PM_LOG_DEBUG, "http_proxy: %s\n", getenv("http_proxy"));
|
|
_alpm_log(PM_LOG_DEBUG, "FTP_PROXY: %s\n", getenv("FTP_PROXY"));
|
|
_alpm_log(PM_LOG_DEBUG, "ftp_proxy: %s\n", getenv("ftp_proxy"));
|
|
|
|
/* libfetch does not reset the error code */
|
|
fetchLastErrCode = 0;
|
|
|
|
/* 10s timeout - TODO make a config option */
|
|
fetchTimeout = 10000;
|
|
|
|
/* ignore any SIGPIPE signals- these may occur if our FTP socket dies or
|
|
* something along those lines. Store the old signal handler first. */
|
|
new_action.sa_handler = SIG_IGN;
|
|
sigemptyset(&new_action.sa_mask);
|
|
sigaction(SIGPIPE, NULL, &old_action);
|
|
sigaction(SIGPIPE, &new_action, NULL);
|
|
|
|
dlf = fetchXGet(fileurl, &ust, (handle->nopassiveftp ? "" : "p"));
|
|
|
|
if(fetchLastErrCode != 0 || dlf == NULL) {
|
|
const char *host = _("disk");
|
|
if(strcmp(SCHEME_FILE, fileurl->scheme) != 0) {
|
|
host = fileurl->host;
|
|
}
|
|
pm_errno = PM_ERR_LIBFETCH;
|
|
_alpm_log(PM_LOG_ERROR, _("failed retrieving file '%s' from %s : %s\n"),
|
|
filename, host, fetchLastErrString);
|
|
ret = -1;
|
|
goto cleanup;
|
|
} else {
|
|
_alpm_log(PM_LOG_DEBUG, "connected to %s successfully\n", fileurl->host);
|
|
}
|
|
|
|
if(ust.mtime && mtimeold && ust.mtime == mtimeold) {
|
|
_alpm_log(PM_LOG_DEBUG, "mtimes are identical, skipping %s\n", filename);
|
|
ret = 1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(ust.mtime && mtimenew) {
|
|
*mtimenew = ust.mtime;
|
|
}
|
|
|
|
if(chk_resume && fileurl->offset == 0) {
|
|
_alpm_log(PM_LOG_WARNING, _("cannot resume download, starting over\n"));
|
|
if(localf != NULL) {
|
|
fclose(localf);
|
|
localf = NULL;
|
|
}
|
|
}
|
|
|
|
if(localf == NULL) {
|
|
_alpm_rmrf(tempfile);
|
|
fileurl->offset = (off_t)0;
|
|
dl_thisfile = 0;
|
|
localf = fopen(tempfile, "wb");
|
|
if(localf == NULL) { /* still null? */
|
|
_alpm_log(PM_LOG_ERROR, _("cannot write to file '%s'\n"), tempfile);
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Progress 0 - initialize */
|
|
if(handle->dlcb) {
|
|
handle->dlcb(filename, 0, ust.size);
|
|
}
|
|
|
|
while((nread = fetchIO_read(dlf, buffer, PM_DLBUF_LEN)) > 0) {
|
|
size_t nwritten = 0;
|
|
while(nwritten < nread) {
|
|
nwritten += fwrite(buffer, 1, (nread - nwritten), localf);
|
|
if(ferror(localf)) {
|
|
_alpm_log(PM_LOG_ERROR, _("error writing to file '%s': %s\n"),
|
|
destfile, strerror(errno));
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
dl_thisfile += nread;
|
|
|
|
if(handle->dlcb) {
|
|
handle->dlcb(filename, dl_thisfile, ust.size);
|
|
}
|
|
}
|
|
|
|
/* did the transfer complete normally? */
|
|
if (ust.size != -1 && dl_thisfile < ust.size) {
|
|
pm_errno = PM_ERR_LIBFETCH;
|
|
_alpm_log(PM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
|
|
filename, (intmax_t)dl_thisfile, (intmax_t)ust.size);
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* probably safer to close the file descriptors now before renaming the file,
|
|
* for example to make sure the buffers are flushed.
|
|
*/
|
|
fclose(localf);
|
|
localf = NULL;
|
|
fetchIO_close(dlf);
|
|
dlf = NULL;
|
|
|
|
rename(tempfile, destfile);
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
/* restore any existing SIGPIPE signal handler */
|
|
sigaction(SIGPIPE, &old_action, NULL);
|
|
|
|
FREE(tempfile);
|
|
FREE(destfile);
|
|
if(localf != NULL) {
|
|
fclose(localf);
|
|
}
|
|
if(dlf != NULL) {
|
|
fetchIO_close(dlf);
|
|
}
|
|
fetchFreeURL(fileurl);
|
|
return(ret);
|
|
}
|
|
#endif
|
|
|
|
static int download(const char *url, const char *localpath,
|
|
time_t mtimeold, time_t *mtimenew) {
|
|
if(handle->fetchcb == NULL) {
|
|
#if defined(INTERNAL_DOWNLOAD)
|
|
return(download_internal(url, localpath, mtimeold, mtimenew));
|
|
#else
|
|
RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD, -1);
|
|
#endif
|
|
} else {
|
|
int ret = handle->fetchcb(url, localpath, mtimeold, mtimenew);
|
|
if(ret == -1) {
|
|
RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD, -1);
|
|
}
|
|
return(ret);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Download a single file
|
|
* - if mtimeold is non-NULL, then only download the file if it's different
|
|
* than mtimeold.
|
|
* - if *mtimenew is non-NULL, it will be filled with the mtime of the remote
|
|
* file.
|
|
* - servers must be a list of urls WITHOUT trailing slashes.
|
|
*
|
|
* RETURN: 0 for successful download
|
|
* 1 if the mtimes are identical
|
|
* -1 on error
|
|
*/
|
|
int _alpm_download_single_file(const char *filename,
|
|
alpm_list_t *servers, const char *localpath,
|
|
time_t mtimeold, time_t *mtimenew)
|
|
{
|
|
alpm_list_t *i;
|
|
int ret = -1;
|
|
|
|
ASSERT(servers != NULL, RET_ERR(PM_ERR_SERVER_NONE, -1));
|
|
|
|
for(i = servers; i; i = i->next) {
|
|
const char *server = i->data;
|
|
char *fileurl = NULL;
|
|
int len;
|
|
|
|
/* print server + filename into a buffer */
|
|
len = strlen(server) + strlen(filename) + 2;
|
|
CALLOC(fileurl, len, sizeof(char), RET_ERR(PM_ERR_MEMORY, -1));
|
|
snprintf(fileurl, len, "%s/%s", server, filename);
|
|
|
|
ret = download(fileurl, localpath, mtimeold, mtimenew);
|
|
FREE(fileurl);
|
|
if(ret != -1) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
int _alpm_download_files(alpm_list_t *files,
|
|
alpm_list_t *servers, const char *localpath)
|
|
{
|
|
int ret = 0;
|
|
alpm_list_t *lp;
|
|
|
|
for(lp = files; lp; lp = lp->next) {
|
|
char *filename = lp->data;
|
|
if(_alpm_download_single_file(filename, servers,
|
|
localpath, 0, NULL) == -1) {
|
|
ret++;
|
|
}
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
/** Fetch a remote pkg.
|
|
* @param url URL of the package to download
|
|
* @return the downloaded filepath on success, NULL on error
|
|
* @addtogroup alpm_misc
|
|
*/
|
|
char SYMEXPORT *alpm_fetch_pkgurl(const char *url)
|
|
{
|
|
char *filename, *filepath;
|
|
const char *cachedir;
|
|
int ret;
|
|
|
|
ALPM_LOG_FUNC;
|
|
|
|
filename = get_filename(url);
|
|
|
|
/* find a valid cache dir to download to */
|
|
cachedir = _alpm_filecache_setup();
|
|
|
|
/* download the file */
|
|
ret = download(url, cachedir, 0, NULL);
|
|
if(ret == -1) {
|
|
_alpm_log(PM_LOG_WARNING, _("failed to download %s\n"), url);
|
|
return(NULL);
|
|
}
|
|
_alpm_log(PM_LOG_DEBUG, "successfully downloaded %s\n", url);
|
|
|
|
/* we should be able to find the file the second time around */
|
|
filepath = _alpm_filecache_find(filename);
|
|
return(filepath);
|
|
}
|
|
|
|
/* vim: set ts=2 sw=2 noet: */
|