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/>.
29 #include <sys/types.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 */
37 #if defined(HAVE_SYS_PARAM_H)
38 #include <sys/param.h> /* MAXHOSTNAMELEN */
47 #include "alpm_list.h"
53 static char *get_filename(const char *url
) {
54 char *filename
= strrchr(url
, '/');
55 if(filename
!= NULL
) {
62 static char *get_destfile(const char *path
, const char *filename
) {
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
);
72 static char *get_tempfile(const char *path
, const char *filename
) {
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
);
82 static const char *gethost(struct url
*fileurl
)
84 const char *host
= _("disk");
85 if(strcmp(SCHEME_FILE
, fileurl
->scheme
) != 0) {
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
,
105 off_t dl_thisfile
= 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;
117 char buffer
[PM_DLBUF_LEN
];
119 filename
= get_filename(url
);
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
);
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
;
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"));
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
);
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
);
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
);
208 fetchLastErrCode
= 0;
209 dlf
= fetchGet(fileurl
, "");
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
);
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
);
226 } else if(fileurl
->offset
) {
227 _alpm_log(PM_LOG_DEBUG
, "resuming download at position %jd\n", (intmax_t)fileurl
->offset
);
232 _alpm_rmrf(tempfile
);
233 fileurl
->offset
= (off_t
)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
));
245 /* Progress 0 - initialize */
247 handle
->dlcb(filename
, 0, ust
.size
);
250 while((nread
= fetchIO_read(dlf
, buffer
, PM_DLBUF_LEN
)) > 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
));
261 dl_thisfile
+= nread
;
264 handle
->dlcb(filename
, dl_thisfile
, ust
.size
);
268 /* did the transfer complete normally? */
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
));
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
);
286 /* probably safer to close the file descriptors now before renaming the file,
287 * for example to make sure the buffers are flushed.
294 /* set the times on the file to the same as that of the remote file */
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
);
309 /* if we still had a local file open, we got interrupted. set the mtimes on
310 * the file accordingly. */
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
);
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
) {
338 static int download(const char *url
, const char *localpath
,
340 if(handle
->fetchcb
== NULL
) {
342 return(download_internal(url
, localpath
, force
));
344 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD
, -1);
347 int ret
= handle
->fetchcb(url
, localpath
, force
);
349 RET_ERR(PM_ERR_EXTERNAL_DOWNLOAD
, -1);
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
363 int _alpm_download_single_file(const char *filename
,
364 alpm_list_t
*servers
, const char *localpath
,
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
;
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
);
392 int _alpm_download_files(alpm_list_t
*files
,
393 alpm_list_t
*servers
, const char *localpath
)
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) {
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
;
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);
430 _alpm_log(PM_LOG_WARNING
, _("failed to download %s\n"), url
);
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
);
440 /* vim: set ts=2 sw=2 noet: */