XBPS Library API  0.19
The X Binary Package System
download.c
1 /*-
2  * Copyright (c) 2009-2012 Juan Romero Pardines
3  * Copyright (c) 2000-2004 Dag-Erling Coïdan Smørgrav
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer
11  * in this position and unchanged.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote products
16  * derived from this software without specific prior written permission
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * From FreeBSD fetch(8):
30  * $FreeBSD: src/usr.bin/fetch/fetch.c,v 1.84.2.1 2009/08/03 08:13:06 kensmith Exp $
31  */
32 
33 #include <sys/param.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/time.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 
43 #include "xbps_api_impl.h"
44 #include "fetch.h"
45 
46 /**
47  * @file lib/download.c
48  * @brief Download routines
49  * @defgroup download Download functions
50  *
51  * XBPS download related functions, frontend for NetBSD's libfetch.
52  */
53 static const char *
54 print_time(time_t *t)
55 {
56  struct tm tm;
57  static char buf[255];
58 
59  localtime_r(t, &tm);
60  strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
61  return buf;
62 }
63 
64 void HIDDEN
65 xbps_fetch_set_cache_connection(int global, int per_host)
66 {
67  if (global == 0)
68  global = XBPS_FETCH_CACHECONN;
69  if (per_host == 0)
70  per_host = XBPS_FETCH_CACHECONN_HOST;
71 
72  fetchConnectionCacheInit(global, per_host);
73 }
74 
75 void HIDDEN
76 xbps_fetch_unset_cache_connection(void)
77 {
78  fetchConnectionCacheClose();
79 }
80 
81 const char *
83 {
84  if (fetchLastErrCode == 0 || fetchLastErrCode == FETCH_OK)
85  return NULL;
86 
87  return fetchLastErrString;
88 }
89 
90 int
91 xbps_fetch_file(struct xbps_handle *xhp, const char *uri, const char *flags)
92 {
93  struct stat st, st_tmpfile, *stp;
94  struct url *url = NULL;
95  struct url_stat url_st;
96  struct fetchIO *fio = NULL;
97  struct timespec ts[2];
98  off_t bytes_dload = -1;
99  ssize_t bytes_read = -1, bytes_written = -1;
100  char buf[4096], *filename, *tempfile;
101  int fd = -1, rv = 0;
102  bool refetch = false, restart = false;
103 
104  assert(xhp);
105  assert(uri);
106 
107  /* Extern vars declared in libfetch */
108  fetchLastErrCode = 0;
109  fetchTimeout = xhp->fetch_timeout;
110  fetchRestartCalls = 1;
111  /*
112  * Get the filename specified in URI argument.
113  */
114  filename = strrchr(uri, '/') + 1;
115  if (filename == NULL)
116  return -1;
117 
118  tempfile = xbps_xasprintf("%s.part", filename);
119  /*
120  * Check if we have to resume a transfer.
121  */
122  memset(&st_tmpfile, 0, sizeof(st_tmpfile));
123  if (stat(tempfile, &st_tmpfile) == 0) {
124  if (st_tmpfile.st_size > 0)
125  restart = true;
126  } else {
127  if (errno != ENOENT) {
128  rv = -1;
129  goto out;
130  }
131  }
132  /*
133  * Check if we have to refetch a transfer.
134  */
135  memset(&st, 0, sizeof(st));
136  if (stat(filename, &st) == 0) {
137  refetch = true;
138  restart = true;
139  } else {
140  if (errno != ENOENT) {
141  rv = -1;
142  goto out;
143  }
144  }
145  /*
146  * Prepare stuff for libfetch.
147  */
148  if ((url = fetchParseURL(uri)) == NULL) {
149  rv = -1;
150  goto out;
151 
152  }
153  /*
154  * Check if we want to refetch from scratch a file.
155  */
156  if (refetch) {
157  stp = &st;
158  /*
159  * Issue a HEAD request to know size and mtime.
160  */
161  if ((rv = fetchStat(url, &url_st, NULL)) == -1)
162  goto out;
163 
164  /*
165  * If mtime and size match do nothing.
166  */
167  if (restart && url_st.size && url_st.mtime &&
168  url_st.size == stp->st_size &&
169  url_st.mtime == stp->st_mtime)
170  goto out;
171 
172  /*
173  * If size match do nothing.
174  */
175  if (restart && url_st.size && url_st.size == st.st_size)
176  goto out;
177 
178  /*
179  * Remove current file (if exists).
180  */
181  restart = false;
182  url->offset = 0;
183  /*
184  * Issue the GET request to refetch.
185  */
186  fio = fetchGet(url, flags);
187  } else {
188  /*
189  * Issue a GET and skip the HEAD request, some servers
190  * (googlecode.com) return a 404 in HEAD requests!
191  */
192  stp = &st_tmpfile;
193  url->offset = stp->st_size;
194  fio = fetchXGet(url, &url_st, flags);
195  }
196 
197  /* debug stuff */
198  xbps_dbg_printf(xhp, "st.st_size: %zd\n", (ssize_t)stp->st_size);
199  xbps_dbg_printf(xhp, "st.st_atime: %s\n", print_time(&stp->st_atime));
200  xbps_dbg_printf(xhp, "st.st_mtime: %s\n", print_time(&stp->st_mtime));
201  xbps_dbg_printf(xhp, "url->scheme: %s\n", url->scheme);
202  xbps_dbg_printf(xhp, "url->host: %s\n", url->host);
203  xbps_dbg_printf(xhp, "url->port: %d\n", url->port);
204  xbps_dbg_printf(xhp, "url->doc: %s\n", url->doc);
205  xbps_dbg_printf(xhp, "url->offset: %zd\n", (ssize_t)url->offset);
206  xbps_dbg_printf(xhp, "url->length: %zu\n", url->length);
207  xbps_dbg_printf(xhp, "url->last_modified: %s\n",
208  print_time(&url->last_modified));
209  xbps_dbg_printf(xhp, "url_stat.size: %zd\n", (ssize_t)url_st.size);
210  xbps_dbg_printf(xhp, "url_stat.atime: %s\n", print_time(&url_st.atime));
211  xbps_dbg_printf(xhp, "url_stat.mtime: %s\n", print_time(&url_st.mtime));
212 
213  if (fio == NULL && fetchLastErrCode != FETCH_OK) {
214  if (!refetch && restart && fetchLastErrCode == FETCH_UNAVAIL) {
215  /*
216  * In HTTP when 416 is returned and length==0
217  * means that local and remote file size match.
218  * Because we are requesting offset==st_size! grr,
219  * stupid http servers...
220  */
221  if (url->length == 0)
222  goto out;
223  }
224  rv = -1;
225  goto out;
226  }
227  if (url_st.size == -1) {
228  xbps_dbg_printf(xhp, "Remote file size is unknown, resume "
229  "not possible...\n");
230  restart = false;
231  } else if (stp->st_size > url_st.size) {
232  /*
233  * Remove local file if bigger than remote, and refetch the
234  * whole shit again.
235  */
236  xbps_dbg_printf(xhp, "Local file %s is greater than remote, "
237  "removing local file and refetching...\n", filename);
238  (void)remove(tempfile);
239  restart = false;
240  } else if (restart && url_st.mtime && url_st.size &&
241  url_st.size == stp->st_size &&
242  url_st.mtime == stp->st_mtime) {
243  /* Local and remote size/mtime match, do nothing. */
244  goto out;
245  }
246  /*
247  * If restarting, open the file for appending otherwise create it.
248  */
249  if (restart)
250  fd = open(tempfile, O_WRONLY|O_APPEND|O_CLOEXEC);
251  else
252  fd = open(tempfile, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC, 0644);
253 
254  if (fd == -1) {
255  rv = -1;
256  goto out;
257  }
258  /*
259  * Initialize data for the fetch progress function callback
260  * and let the user know that the transfer is going to start
261  * immediately.
262  */
263  xbps_set_cb_fetch(xhp, url_st.size, url->offset, url->offset,
264  filename, true, false, false);
265  /*
266  * Start fetching requested file.
267  */
268  while ((bytes_read = fetchIO_read(fio, buf, sizeof(buf))) > 0) {
269  bytes_written = write(fd, buf, (size_t)bytes_read);
270  if (bytes_written != bytes_read) {
271  xbps_dbg_printf(xhp,
272  "Couldn't write to %s!\n", tempfile);
273  rv = -1;
274  goto out;
275  }
276  bytes_dload += bytes_read;
277  /*
278  * Let the fetch progress callback know that
279  * we are sucking more bytes from it.
280  */
281  xbps_set_cb_fetch(xhp, url_st.size, url->offset,
282  url->offset + bytes_dload,
283  filename, false, true, false);
284  }
285  if (bytes_read == -1) {
286  xbps_dbg_printf(xhp, "IO error while fetching %s: %s\n",
287  filename, fetchLastErrString);
288  errno = EIO;
289  rv = -1;
290  goto out;
291  }
292  if (fd == -1) {
293  rv = -1;
294  goto out;
295  }
296  /*
297  * Let the fetch progress callback know that the file
298  * has been fetched.
299  */
300  xbps_set_cb_fetch(xhp, url_st.size, url->offset, bytes_dload,
301  filename, false, false, true);
302  /*
303  * Update mtime in local file to match remote file if transfer
304  * was successful.
305  */
306  ts[0].tv_sec = url_st.atime ? url_st.atime : url_st.mtime;
307  ts[1].tv_sec = url_st.mtime;
308  ts[0].tv_nsec = ts[1].tv_nsec = 0;
309  if (futimens(fd, ts) == -1) {
310  rv = -1;
311  goto out;
312  }
313  /* sync and close fd */
314  (void)fsync(fd);
315  (void)close(fd);
316 
317  /* File downloaded successfully, rename to destfile */
318  if (rename(tempfile, filename) == -1) {
319  xbps_dbg_printf(xhp, "failed to rename %s to %s: %s",
320  tempfile, filename, strerror(errno));
321  rv = -1;
322  goto out;
323  }
324  rv = 1;
325 
326 out:
327  if (fd != -1)
328  (void)close(fd);
329  if (fio != NULL)
330  fetchIO_close(fio);
331  if (url != NULL)
332  fetchFreeURL(url);
333  if (tempfile != NULL)
334  free(tempfile);
335 
336  return rv;
337 }