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/>.
29 #include <sys/types.h>
32 /* the following two are needed for FreeBSD's libfetch */
33 #include <limits.h> /* PATH_MAX */
34 #if defined(HAVE_SYS_PARAM_H)
35 #include <sys/param.h> /* MAXHOSTNAMELEN */
44 #include "alpm_list.h"
50 static char *get_filename(const char *url
) {
51 char *filename
= strrchr(url
, '/');
52 if(filename
!= NULL
) {
59 static char *get_destfile(const char *path
, const char *filename
) {
61 /* len = localpath len + filename len + null */
62 size_t len
= strlen(path
) + strlen(filename
) + 1;
63 CALLOC(destfile
, len
, sizeof(char), RET_ERR(PM_ERR_MEMORY
, NULL
));
64 snprintf(destfile
, len
, "%s%s", path
, filename
);
69 static char *get_tempfile(const char *path
, const char *filename
) {
71 /* len = localpath len + filename len + '.part' len + null */
72 size_t len
= strlen(path
) + strlen(filename
) + 6;
73 CALLOC(tempfile
, len
, sizeof(char), RET_ERR(PM_ERR_MEMORY
, NULL
));
74 snprintf(tempfile
, len
, "%s%s.part", path
, filename
);
79 static const char *gethost(struct url
*fileurl
)
81 const char *host
= _("disk");
82 if(strcmp(SCHEME_FILE
, fileurl
->scheme
) != 0) {
88 int dload_interrupted
;
89 static void inthandler(int signum
)
91 dload_interrupted
= 1;
94 #define check_stop() if(dload_interrupted) { ret = -1; goto cleanup; }
95 enum sighandlers
{ OLD
= 0, NEW
= 1 };
97 static int download_internal(const char *url
, const char *localpath
,
102 off_t dl_thisfile
= 0;
104 char *tempfile
, *destfile
, *filename
;
105 struct sigaction sig_pipe
[2], sig_int
[2];
107 off_t local_size
= 0;
108 time_t local_time
= 0;
114 char buffer
[PM_DLBUF_LEN
];
116 filename
= get_filename(url
);
118 _alpm_log(PM_LOG_ERROR
, _("url '%s' is invalid\n"), url
);
119 RET_ERR(PM_ERR_SERVER_BAD_URL
, -1);
122 fileurl
= fetchParseURL(url
);
124 _alpm_log(PM_LOG_ERROR
, _("url '%s' is invalid\n"), url
);
125 RET_ERR(PM_ERR_LIBFETCH
, -1);
128 destfile
= get_destfile(localpath
, filename
);
129 tempfile
= get_tempfile(localpath
, filename
);
131 if(stat(tempfile
, &st
) == 0 && st
.st_size
> 0) {
132 _alpm_log(PM_LOG_DEBUG
, "tempfile found, attempting continuation\n");
133 local_time
= fileurl
->last_modified
= st
.st_mtime
;
134 local_size
= fileurl
->offset
= (off_t
)st
.st_size
;
135 dl_thisfile
= st
.st_size
;
136 localf
= fopen(tempfile
, "ab");
137 } else if(!force
&& stat(destfile
, &st
) == 0 && st
.st_size
> 0) {
138 _alpm_log(PM_LOG_DEBUG
, "destfile found, using mtime only\n");
139 local_time
= fileurl
->last_modified
= st
.st_mtime
;
140 local_size
= /* no fu->off here */ (off_t
)st
.st_size
;
142 _alpm_log(PM_LOG_DEBUG
, "no file found matching criteria, starting from scratch\n");
145 /* pass the raw filename for passing to the callback function */
146 _alpm_log(PM_LOG_DEBUG
, "using '%s' for download progress\n", filename
);
148 /* print proxy info for debug purposes */
149 _alpm_log(PM_LOG_DEBUG
, "HTTP_PROXY: %s\n", getenv("HTTP_PROXY"));
150 _alpm_log(PM_LOG_DEBUG
, "http_proxy: %s\n", getenv("http_proxy"));
151 _alpm_log(PM_LOG_DEBUG
, "FTP_PROXY: %s\n", getenv("FTP_PROXY"));
152 _alpm_log(PM_LOG_DEBUG
, "ftp_proxy: %s\n", getenv("ftp_proxy"));
157 /* ignore any SIGPIPE signals- these may occur if our FTP socket dies or
158 * something along those lines. Store the old signal handler first. */
159 sig_pipe
[NEW
].sa_handler
= SIG_IGN
;
160 sigemptyset(&sig_pipe
[NEW
].sa_mask
);
161 sig_pipe
[NEW
].sa_flags
= 0;
162 sigaction(SIGPIPE
, NULL
, &sig_pipe
[OLD
]);
163 sigaction(SIGPIPE
, &sig_pipe
[NEW
], NULL
);
165 dload_interrupted
= 0;
166 sig_int
[NEW
].sa_handler
= &inthandler
;
167 sigemptyset(&sig_int
[NEW
].sa_mask
);
168 sig_int
[NEW
].sa_flags
= 0;
169 sigaction(SIGINT
, NULL
, &sig_int
[OLD
]);
170 sigaction(SIGINT
, &sig_int
[NEW
], NULL
);
172 /* NOTE: libfetch does not reset the error code, be sure to do it before
173 * calls into the library */
175 /* find out the remote size *and* mtime in one go. there is a lot of
176 * trouble in trying to do both size and "if-modified-since" logic in a
177 * non-stat request, so avoid it. */
178 fetchLastErrCode
= 0;
179 if(fetchStat(fileurl
, &ust
, "") == -1) {
180 pm_errno
= PM_ERR_LIBFETCH
;
181 _alpm_log(PM_LOG_ERROR
, _("failed retrieving file '%s' from %s : %s\n"),
182 filename
, gethost(fileurl
), fetchLastErrString
);
188 _alpm_log(PM_LOG_DEBUG
, "ust.mtime: %ld local_time: %ld compare: %ld\n",
189 ust
.mtime
, local_time
, local_time
- ust
.mtime
);
190 _alpm_log(PM_LOG_DEBUG
, "ust.size: %jd local_size: %jd compare: %jd\n",
191 (intmax_t)ust
.size
, (intmax_t)local_size
, (intmax_t)(local_size
- ust
.size
));
192 if(!force
&& ust
.mtime
&& ust
.mtime
== local_time
193 && ust
.size
&& ust
.size
== local_size
) {
194 /* the remote time and size values agreed with what we have, so move on
195 * because there is nothing more to do. */
196 _alpm_log(PM_LOG_DEBUG
, "files are identical, skipping %s\n", filename
);
200 if(!ust
.mtime
|| ust
.mtime
!= local_time
) {
201 _alpm_log(PM_LOG_DEBUG
, "mtimes were different or unavailable, downloading %s from beginning\n", filename
);
205 fetchLastErrCode
= 0;
206 dlf
= fetchGet(fileurl
, "");
209 if(fetchLastErrCode
!= 0 || dlf
== NULL
) {
210 pm_errno
= PM_ERR_LIBFETCH
;
211 _alpm_log(PM_LOG_ERROR
, _("failed retrieving file '%s' from %s : %s\n"),
212 filename
, gethost(fileurl
), fetchLastErrString
);
216 _alpm_log(PM_LOG_DEBUG
, "connected to %s successfully\n", fileurl
->host
);
219 if(localf
&& fileurl
->offset
== 0) {
220 _alpm_log(PM_LOG_WARNING
, _("resuming download of %s not possible; starting over\n"), filename
);
223 } else if(fileurl
->offset
) {
224 _alpm_log(PM_LOG_DEBUG
, "resuming download at position %jd\n", (intmax_t)fileurl
->offset
);
229 _alpm_rmrf(tempfile
);
230 fileurl
->offset
= (off_t
)0;
232 localf
= fopen(tempfile
, "wb");
233 if(localf
== NULL
) { /* still null? */
234 pm_errno
= PM_ERR_RETRIEVE
;
235 _alpm_log(PM_LOG_ERROR
, _("error writing to file '%s': %s\n"),
236 tempfile
, strerror(errno
));
242 /* Progress 0 - initialize */
244 handle
->dlcb(filename
, 0, ust
.size
);
247 while((nread
= fetchIO_read(dlf
, buffer
, PM_DLBUF_LEN
)) > 0) {
250 nwritten
= fwrite(buffer
, 1, (size_t)nread
, localf
);
251 if((nwritten
!= (size_t)nread
) || ferror(localf
)) {
252 pm_errno
= PM_ERR_RETRIEVE
;
253 _alpm_log(PM_LOG_ERROR
, _("error writing to file '%s': %s\n"),
254 tempfile
, strerror(errno
));
258 dl_thisfile
+= nread
;
261 handle
->dlcb(filename
, dl_thisfile
, ust
.size
);
265 /* did the transfer complete normally? */
267 /* not PM_ERR_LIBFETCH here because libfetch error string might be empty */
268 pm_errno
= PM_ERR_RETRIEVE
;
269 _alpm_log(PM_LOG_ERROR
, _("failed retrieving file '%s' from %s\n"),
270 filename
, gethost(fileurl
));
275 if (ust
.size
!= -1 && dl_thisfile
< ust
.size
) {
276 pm_errno
= PM_ERR_RETRIEVE
;
277 _alpm_log(PM_LOG_ERROR
, _("%s appears to be truncated: %jd/%jd bytes\n"),
278 filename
, (intmax_t)dl_thisfile
, (intmax_t)ust
.size
);
283 /* probably safer to close the file descriptors now before renaming the file,
284 * for example to make sure the buffers are flushed.
291 /* set the times on the file to the same as that of the remote file */
293 struct timeval tv
[2];
294 memset(&tv
, 0, sizeof(tv
));
295 tv
[0].tv_sec
= ust
.atime
;
296 tv
[1].tv_sec
= ust
.mtime
;
297 utimes(tempfile
, tv
);
299 rename(tempfile
, destfile
);
306 /* if we still had a local file open, we got interrupted. set the mtimes on
307 * the file accordingly. */
310 struct timeval tv
[2];
311 memset(&tv
, 0, sizeof(tv
));
312 tv
[0].tv_sec
= ust
.atime
;
313 tv
[1].tv_sec
= ust
.mtime
;
314 futimes(fileno(localf
), tv
);
321 fetchFreeURL(fileurl
);
323 /* restore the old signal handlers */
324 sigaction(SIGINT
, &sig_int
[OLD
], NULL
);
325 sigaction(SIGPIPE
, &sig_pipe
[OLD
], NULL
);
326 /* if we were interrupted, trip the old handler */
327 if(dload_interrupted
) {
335 static int download(const char *url
, const char *localpath
,
337 if(handle
->fetchcb
== NULL
) {
339 return(download_internal(url
, localpath
, force
));
341 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD
, -1);
344 int ret
= handle
->fetchcb(url
, localpath
, force
);
346 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD
, -1);
353 * Download a single file
354 * - servers must be a list of urls WITHOUT trailing slashes.
356 * RETURN: 0 for successful download
357 * 1 if the files are identical
360 int _alpm_download_single_file(const char *filename
,
361 alpm_list_t
*servers
, const char *localpath
,
367 ASSERT(servers
!= NULL
, RET_ERR(PM_ERR_SERVER_NONE
, -1));
369 for(i
= servers
; i
; i
= i
->next
) {
370 const char *server
= i
->data
;
371 char *fileurl
= NULL
;
374 /* print server + filename into a buffer */
375 len
= strlen(server
) + strlen(filename
) + 2;
376 CALLOC(fileurl
, len
, sizeof(char), RET_ERR(PM_ERR_MEMORY
, -1));
377 snprintf(fileurl
, len
, "%s/%s", server
, filename
);
379 ret
= download(fileurl
, localpath
, force
);
389 int _alpm_download_files(alpm_list_t
*files
,
390 alpm_list_t
*servers
, const char *localpath
)
395 for(lp
= files
; lp
; lp
= lp
->next
) {
396 char *filename
= lp
->data
;
397 if(_alpm_download_single_file(filename
, servers
,
398 localpath
, 0) == -1) {
406 /** Fetch a remote pkg.
407 * @param url URL of the package to download
408 * @return the downloaded filepath on success, NULL on error
409 * @addtogroup alpm_misc
411 char SYMEXPORT
*alpm_fetch_pkgurl(const char *url
)
413 char *filename
, *filepath
;
414 const char *cachedir
;
419 filename
= get_filename(url
);
421 /* find a valid cache dir to download to */
422 cachedir
= _alpm_filecache_setup();
424 /* download the file */
425 ret
= download(url
, cachedir
, 0);
427 _alpm_log(PM_LOG_WARNING
, _("failed to download %s\n"), url
);
430 _alpm_log(PM_LOG_DEBUG
, "successfully downloaded %s\n", url
);
432 /* we should be able to find the file the second time around */
433 filepath
= _alpm_filecache_find(filename
);
437 /* vim: set ts=2 sw=2 noet: */