0e2d550df2b87117b772d79aa158e36c7744549d
[platform/upstream/libzypp.git] / zypp / media / MediaMultiCurl.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaMultiCurl.cc
10  *
11 */
12
13 #include <ctype.h>
14 #include <sys/types.h>
15 #include <signal.h>
16 #include <sys/wait.h>
17 #include <netdb.h>
18 #include <arpa/inet.h>
19
20 #include <vector>
21 #include <iostream>
22 #include <algorithm>
23
24
25 #include "zypp/ZConfig.h"
26 #include "zypp/base/Logger.h"
27 #include "zypp/media/MediaMultiCurl.h"
28 #include "zypp/media/MetaLinkParser.h"
29
30 using namespace std;
31 using namespace zypp::base;
32
33 namespace zypp {
34   namespace media {
35
36
37 //////////////////////////////////////////////////////////////////////
38
39
40 class multifetchrequest;
41
42 // Hack: we derive from MediaCurl just to get the storage space for
43 // settings, url, curlerrors and the like
44
45 class multifetchworker : MediaCurl {
46   friend class multifetchrequest;
47
48 public:
49   multifetchworker(int no, multifetchrequest &request, const Url &url);
50   ~multifetchworker();
51   void nextjob();
52   void run();
53   bool checkChecksum();
54   bool recheckChecksum();
55   void disableCompetition();
56
57   void checkdns();
58   void adddnsfd(fd_set &rset, int &maxfd);
59   void dnsevent(fd_set &rset);
60
61   int _workerno;
62
63   int _state;
64   bool _competing;
65
66   size_t _blkno;
67   off_t _blkstart;
68   size_t _blksize;
69   bool _noendrange;
70
71   double _blkstarttime;
72   size_t _blkreceived;
73   off_t  _received;
74
75   double _avgspeed;
76   double _maxspeed;
77
78   double _sleepuntil;
79
80 private:
81   void stealjob();
82
83   size_t writefunction(void *ptr, size_t size);
84   static size_t _writefunction(void *ptr, size_t size, size_t nmemb, void *stream);
85
86   size_t headerfunction(char *ptr, size_t size);
87   static size_t _headerfunction(void *ptr, size_t size, size_t nmemb, void *stream);
88
89   multifetchrequest *_request;
90   int _pass;
91   string _urlbuf;
92   off_t _off;
93   size_t _size;
94   Digest _dig;
95
96   pid_t _pid;
97   int _dnspipe;
98 };
99
100 #define WORKER_STARTING 0
101 #define WORKER_LOOKUP   1
102 #define WORKER_FETCH    2
103 #define WORKER_DISCARD  3
104 #define WORKER_DONE     4
105 #define WORKER_SLEEP    5
106 #define WORKER_BROKEN   6
107
108
109
110 class multifetchrequest {
111 public:
112   multifetchrequest(const MediaMultiCurl *context, const Pathname &filename, const Url &baseurl, CURLM *multi, FILE *fp, callback::SendReport<DownloadProgressReport> *report, MediaBlockList *blklist, off_t filesize);
113   ~multifetchrequest();
114
115   void run(std::vector<Url> &urllist);
116
117 protected:
118   friend class multifetchworker;
119
120   const MediaMultiCurl *_context;
121   const Pathname _filename;
122   Url _baseurl;
123
124   FILE *_fp;
125   callback::SendReport<DownloadProgressReport> *_report;
126   MediaBlockList *_blklist;
127   off_t _filesize;
128
129   CURLM *_multi;
130
131   std::list<multifetchworker *> _workers;
132   bool _stealing;
133   bool _havenewjob;
134
135   size_t _blkno;
136   off_t _blkoff;
137   size_t _activeworkers;
138   size_t _lookupworkers;
139   size_t _sleepworkers;
140   double _minsleepuntil;
141   bool _finished;
142   off_t _totalsize;
143   off_t _fetchedsize;
144   off_t _fetchedgoodsize;
145
146   double _starttime;
147   double _lastprogress;
148
149   double _lastperiodstart;
150   double _lastperiodfetched;
151   double _periodavg;
152
153 public:
154   double _timeout;
155   double _connect_timeout;
156   double _maxspeed;
157 };
158
159 #define BLKSIZE         131072
160 #define MAXWORKERS      5
161 #define MAXURLS         10
162
163
164 //////////////////////////////////////////////////////////////////////
165
166 static double
167 currentTime()
168 {
169   struct timeval tv;
170   if (gettimeofday(&tv, NULL))
171     return 0;
172   return tv.tv_sec + tv.tv_usec / 1000000.;
173 }
174
175 size_t
176 multifetchworker::writefunction(void *ptr, size_t size)
177 {
178   size_t len, cnt;
179   if (_state == WORKER_BROKEN)
180     return size ? 0 : 1;
181
182   double now = currentTime();
183
184   len = size > _size ? _size : size;
185   if (!len)
186     {
187       // kill this job?
188       return size;
189     }
190
191   _blkreceived += len;
192   _received += len;
193
194   _request->_lastprogress = now;
195     
196   if (_state == WORKER_DISCARD || !_request->_fp)
197     {
198       // block is no longer needed
199       // still calculate the checksum so that we can throw out bad servers
200       if (_request->_blklist)
201         _dig.update((const char *)ptr, len);
202       _off += len;
203       _size -= len;
204       return size;
205     }
206   if (fseeko(_request->_fp, _off, SEEK_SET))
207     return size ? 0 : 1;
208   cnt = fwrite(ptr, 1, len, _request->_fp);
209   if (cnt > 0)
210     {
211       _request->_fetchedsize += cnt;
212       if (_request->_blklist)
213         _dig.update((const char *)ptr, cnt);
214       _off += cnt;
215       _size -= cnt;
216       if (cnt == len)
217         return size;
218     }
219   return cnt;
220 }
221
222 size_t
223 multifetchworker::_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
224 {
225   multifetchworker *me = reinterpret_cast<multifetchworker *>(stream);
226   return me->writefunction(ptr, size * nmemb);
227 }
228
229 size_t
230 multifetchworker::headerfunction(char *p, size_t size)
231 {
232   size_t l = size;
233   if (l > 9 && !strncasecmp(p, "Location:", 9))
234     {
235       string line(p + 9, l - 9);
236       if (line[l - 10] == '\r')
237         line.erase(l - 10, 1);
238       DBG << "#" << _workerno << ": redirecting to" << line << endl;
239       return size;
240     }
241   if (l <= 14 || l >= 128 || strncasecmp(p, "Content-Range:", 14) != 0)
242     return size;
243   p += 14; 
244   l -= 14; 
245   while (l && (*p == ' ' || *p == '\t'))
246     p++, l--;
247   if (l < 6 || strncasecmp(p, "bytes", 5)) 
248     return size;
249   p += 5;
250   l -= 5;
251   char buf[128];
252   memcpy(buf, p, l); 
253   buf[l] = 0;
254   unsigned long long start, off, filesize;
255   if (sscanf(buf, "%llu-%llu/%llu", &start, &off, &filesize) != 3)
256     return size;
257   if (_request->_filesize == (off_t)-1)
258     {
259       WAR << "#" << _workerno << ": setting request filesize to " << filesize << endl;
260       _request->_filesize = filesize;
261       if (_request->_totalsize == 0 && !_request->_blklist)
262         _request->_totalsize = filesize;
263     }
264   if (_request->_filesize != (off_t)filesize)
265     {
266       DBG << "#" << _workerno << ": filesize mismatch" << endl;
267       _state = WORKER_BROKEN;
268       strncpy(_curlError, "filesize mismatch", CURL_ERROR_SIZE);
269     }
270   return size;
271 }
272
273 size_t
274 multifetchworker::_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
275 {
276   multifetchworker *me = reinterpret_cast<multifetchworker *>(stream);
277   return me->headerfunction((char *)ptr, size * nmemb);
278 }
279
280 multifetchworker::multifetchworker(int no, multifetchrequest &request, const Url &url)
281 : MediaCurl(url, Pathname())
282 {
283   _workerno = no;
284   _request = &request;
285   _state = WORKER_STARTING;
286   _competing = false;
287   _off = _blkstart = 0;
288   _size = _blksize = 0;
289   _pass = 0;
290   _blkno = 0;
291   _pid = 0;
292   _dnspipe = -1;
293   _blkreceived = 0;
294   _received = 0;
295   _blkstarttime = 0;
296   _avgspeed = 0;
297   _sleepuntil = 0;
298   _maxspeed = _request->_maxspeed;
299   _noendrange = false;
300
301   Url curlUrl( clearQueryString(url) );
302   _urlbuf = curlUrl.asString();
303   _curl = _request->_context->fromEasyPool(_url.getHost());
304   if (_curl)
305     DBG << "reused worker from pool" << endl;
306   if (!_curl && !(_curl = curl_easy_init()))
307     {
308       _state = WORKER_BROKEN;
309       strncpy(_curlError, "curl_easy_init failed", CURL_ERROR_SIZE);
310       return;
311     }
312   try
313     {
314       setupEasy();
315     }
316   catch (Exception &ex)
317     {
318       curl_easy_cleanup(_curl);
319       _curl = 0;
320       _state = WORKER_BROKEN;
321       strncpy(_curlError, "curl_easy_setopt failed", CURL_ERROR_SIZE);
322       return;
323     }
324   curl_easy_setopt(_curl, CURLOPT_PRIVATE, this);
325   curl_easy_setopt(_curl, CURLOPT_URL, _urlbuf.c_str());
326   curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, &_writefunction);
327   curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this);
328   if (_request->_filesize == off_t(-1) || !_request->_blklist || !_request->_blklist->haveChecksum(0))
329     {
330       curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, &_headerfunction);
331       curl_easy_setopt(_curl, CURLOPT_HEADERDATA, this);
332     }
333   // if this is the same host copy authorization
334   // (the host check is also what curl does when doing a redirect)
335   // (note also that unauthorized exceptions are thrown with the request host)
336   if (url.getHost() == _request->_context->_url.getHost())
337     {
338       _settings.setUsername(_request->_context->_settings.username());
339       _settings.setPassword(_request->_context->_settings.password());
340       _settings.setAuthType(_request->_context->_settings.authType());
341       if ( _settings.userPassword().size() )
342         {
343           curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
344           string use_auth = _settings.authType();
345           if (use_auth.empty())
346             use_auth = "digest,basic";        // our default
347           long auth = CurlAuthData::auth_type_str2long(use_auth);
348           if( auth != CURLAUTH_NONE)
349           {    
350             DBG << "#" << _workerno << ": Enabling HTTP authentication methods: " << use_auth
351                 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
352             curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, auth);
353           }
354         }
355     }
356   checkdns();
357 }
358
359 multifetchworker::~multifetchworker()
360 {
361   if (_curl)
362     {
363       if (_state == WORKER_FETCH || _state == WORKER_DISCARD)
364         curl_multi_remove_handle(_request->_multi, _curl);
365       if (_state == WORKER_DONE || _state == WORKER_SLEEP)
366         {
367           curl_easy_setopt(_curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)0);
368           curl_easy_setopt(_curl, CURLOPT_PRIVATE, (void *)0);
369           curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, (void *)0);
370           curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)0);
371           curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, (void *)0);
372           curl_easy_setopt(_curl, CURLOPT_HEADERDATA, (void *)0);
373           _request->_context->toEasyPool(_url.getHost(), _curl);
374         }
375       else
376         curl_easy_cleanup(_curl);
377       _curl = 0;
378     }
379   if (_pid)
380     {
381       kill(_pid, SIGKILL);
382       int status;
383       while (waitpid(_pid, &status, 0) == -1)
384         if (errno != EINTR)
385           break;
386       _pid = 0;
387     }
388   if (_dnspipe != -1)
389     {
390       close(_dnspipe);
391       _dnspipe = -1;
392     }
393   // the destructor in MediaCurl doesn't call disconnect() if
394   // the media is not attached, so we do it here manually
395   disconnectFrom();
396 }
397
398 static inline bool env_isset(string name)
399 {
400   const char *s = getenv(name.c_str());
401   return s && *s ? true : false;
402 }
403
404 void
405 multifetchworker::checkdns()
406 {
407   string host = _url.getHost();
408
409   if (host.empty())
410     return;
411
412   if (_request->_context->isDNSok(host))
413     return;
414
415   // no need to do dns checking for numeric hosts
416   char addrbuf[128];
417   if (inet_pton(AF_INET, host.c_str(), addrbuf) == 1)
418     return;
419   if (inet_pton(AF_INET6, host.c_str(), addrbuf) == 1)
420     return;
421
422   // no need to do dns checking if we use a proxy
423   if (!_settings.proxy().empty())
424     return;
425   if (env_isset("all_proxy") || env_isset("ALL_PROXY"))
426     return;
427   string schemeproxy = _url.getScheme() + "_proxy";
428   if (env_isset(schemeproxy))
429     return;
430   if (schemeproxy != "http_proxy")
431     {
432       std::transform(schemeproxy.begin(), schemeproxy.end(), schemeproxy.begin(), ::toupper);
433       if (env_isset(schemeproxy))
434         return;
435     }
436
437   DBG << "checking DNS lookup of " << host << endl;
438   int pipefds[2];
439   if (pipe(pipefds))
440     {
441       _state = WORKER_BROKEN;
442       strncpy(_curlError, "DNS pipe creation failed", CURL_ERROR_SIZE);
443       return;
444     }
445   _pid = fork();
446   if (_pid == pid_t(-1))
447     {
448       close(pipefds[0]);
449       close(pipefds[1]);
450       _pid = 0;
451       _state = WORKER_BROKEN;
452       strncpy(_curlError, "DNS checker fork failed", CURL_ERROR_SIZE);
453       return;
454     }
455   else if (_pid == 0)
456     {
457       close(pipefds[0]);
458       // XXX: close all other file descriptors
459       struct addrinfo *ai, aihints;
460       memset(&aihints, 0, sizeof(aihints));
461       aihints.ai_family = PF_UNSPEC;
462       int tstsock = socket(PF_INET6, SOCK_DGRAM, 0);
463       if (tstsock == -1)
464         aihints.ai_family = PF_INET;
465       else
466         close(tstsock);
467       aihints.ai_socktype = SOCK_STREAM;
468       aihints.ai_flags = AI_CANONNAME;
469       unsigned int connecttimeout = _request->_connect_timeout;
470       if (connecttimeout)
471         alarm(connecttimeout);
472       signal(SIGALRM, SIG_DFL);
473       if (getaddrinfo(host.c_str(), NULL, &aihints, &ai))
474         _exit(1);
475       _exit(0);
476     }
477   close(pipefds[1]);
478   _dnspipe = pipefds[0];
479   _state = WORKER_LOOKUP;
480 }
481
482 void
483 multifetchworker::adddnsfd(fd_set &rset, int &maxfd)
484 {
485   if (_state != WORKER_LOOKUP)
486     return;
487   FD_SET(_dnspipe, &rset);
488   if (maxfd < _dnspipe)
489     maxfd = _dnspipe;
490 }
491
492 void
493 multifetchworker::dnsevent(fd_set &rset)
494 {
495   
496   if (_state != WORKER_LOOKUP || !FD_ISSET(_dnspipe, &rset))
497     return;
498   int status;
499   while (waitpid(_pid, &status, 0) == -1)
500     {
501       if (errno != EINTR)
502         return;
503     }
504   _pid = 0;
505   if (_dnspipe != -1)
506     {
507       close(_dnspipe);
508       _dnspipe = -1;
509     }
510   if (!WIFEXITED(status))
511     {
512       _state = WORKER_BROKEN;
513       strncpy(_curlError, "DNS lookup failed", CURL_ERROR_SIZE);
514       _request->_activeworkers--;
515       return;
516     }
517   int exitcode = WEXITSTATUS(status);
518   DBG << "#" << _workerno << ": DNS lookup returned " << exitcode << endl;
519   if (exitcode != 0)
520     {
521       _state = WORKER_BROKEN;
522       strncpy(_curlError, "DNS lookup failed", CURL_ERROR_SIZE);
523       _request->_activeworkers--;
524       return;
525     }
526   _request->_context->setDNSok(_url.getHost());
527   nextjob();
528 }
529
530 bool
531 multifetchworker::checkChecksum()
532 {
533   // DBG << "checkChecksum block " << _blkno << endl;
534   if (!_blksize || !_request->_blklist)
535     return true;
536   return _request->_blklist->verifyDigest(_blkno, _dig);
537 }
538
539 bool
540 multifetchworker::recheckChecksum()
541 {
542   // DBG << "recheckChecksum block " << _blkno << endl;
543   if (!_request->_fp || !_blksize || !_request->_blklist)
544     return true;
545   if (fseeko(_request->_fp, _blkstart, SEEK_SET))
546     return false;
547   char buf[4096];
548   size_t l = _blksize;
549   _request->_blklist->createDigest(_dig);       // resets digest
550   while (l)
551     {
552       size_t cnt = l > sizeof(buf) ? sizeof(buf) : l;
553       if (fread(buf, cnt, 1, _request->_fp) != 1)
554         return false;
555       _dig.update(buf, cnt);
556       l -= cnt;
557     }
558   return _request->_blklist->verifyDigest(_blkno, _dig);
559 }
560
561
562 void
563 multifetchworker::stealjob()
564 {
565   if (!_request->_stealing)
566     {
567       DBG << "start stealing!" << endl;
568       _request->_stealing = true;
569     }
570   multifetchworker *best = 0;
571   std::list<multifetchworker *>::iterator workeriter = _request->_workers.begin();
572   double now = 0;
573   for (; workeriter != _request->_workers.end(); ++workeriter)
574     {
575       multifetchworker *worker = *workeriter;
576       if (worker == this)
577         continue;
578       if (worker->_pass == -1)
579         continue;       // do not steal!
580       if (worker->_state == WORKER_DISCARD || worker->_state == WORKER_DONE || worker->_state == WORKER_SLEEP || !worker->_blksize)
581         continue;       // do not steal finished jobs
582       if (!worker->_avgspeed && worker->_blkreceived)
583         {
584           if (!now)
585             now = currentTime();
586           if (now > worker->_blkstarttime)
587             worker->_avgspeed = worker->_blkreceived / (now - worker->_blkstarttime);
588         }
589       if (!best || best->_pass > worker->_pass)
590         {
591           best = worker;
592           continue;
593         }
594       if (best->_pass < worker->_pass)
595         continue;
596       // if it is the same block, we want to know the best worker, otherwise the worst
597       if (worker->_blkstart == best->_blkstart)
598         {
599           if ((worker->_blksize - worker->_blkreceived) * best->_avgspeed < (best->_blksize - best->_blkreceived) * worker->_avgspeed)
600             best = worker;
601         }
602       else
603         {
604           if ((worker->_blksize - worker->_blkreceived) * best->_avgspeed > (best->_blksize - best->_blkreceived) * worker->_avgspeed)
605             best = worker;
606         }
607     }
608   if (!best)
609     {
610       _state = WORKER_DONE;
611       _request->_activeworkers--;
612       _request->_finished = true;
613       return;
614     }
615   // do not sleep twice
616   if (_state != WORKER_SLEEP)
617     {
618       if (!_avgspeed && _blkreceived)
619         {
620           if (!now)
621             now = currentTime();
622           if (now > _blkstarttime)
623             _avgspeed = _blkreceived / (now - _blkstarttime);
624         }
625
626       // lets see if we should sleep a bit
627       DBG << "me #" << _workerno << ": " << _avgspeed << ", size " << best->_blksize << endl;
628       DBG << "best #" << best->_workerno << ": " << best->_avgspeed << ", size " << (best->_blksize - best->_blkreceived) << endl;
629       if (_avgspeed && best->_avgspeed && (best->_blksize - best->_blkreceived) * _avgspeed < best->_blksize * best->_avgspeed)
630         {
631           if (!now)
632             now = currentTime();
633           double sl = (best->_blksize - best->_blkreceived) / best->_avgspeed * 2;
634           if (sl > 1)
635             sl = 1;
636           DBG << "#" << _workerno << ": going to sleep for " << sl * 1000 << " ms" << endl;
637           _sleepuntil = now + sl;
638           _state = WORKER_SLEEP;
639           _request->_sleepworkers++;
640           return;
641         }
642     }
643
644   _competing = true;
645   best->_competing = true;
646   _blkstart = best->_blkstart;
647   _blksize = best->_blksize;
648   best->_pass++;
649   _pass = best->_pass;
650   _blkno = best->_blkno;
651   run();
652 }
653
654 void
655 multifetchworker::disableCompetition()
656 {
657   std::list<multifetchworker *>::iterator workeriter = _request->_workers.begin();
658   for (; workeriter != _request->_workers.end(); ++workeriter)
659     {
660       multifetchworker *worker = *workeriter;
661       if (worker == this)
662         continue;
663       if (worker->_blkstart == _blkstart)
664         {
665           if (worker->_state == WORKER_FETCH)
666             worker->_state = WORKER_DISCARD;
667           worker->_pass = -1;   /* do not steal this one, we already have it */
668         }
669     }
670 }
671
672
673 void
674 multifetchworker::nextjob()
675 {
676   _noendrange = false;
677   if (_request->_stealing)
678     {
679       stealjob();
680       return;
681     }
682   
683   MediaBlockList *blklist = _request->_blklist;
684   if (!blklist)
685     {
686       _blksize = BLKSIZE;
687       if (_request->_filesize != off_t(-1))
688         {
689           if (_request->_blkoff >= _request->_filesize)
690             {
691               stealjob();
692               return;
693             }
694           _blksize = _request->_filesize - _request->_blkoff;
695           if (_blksize > BLKSIZE)
696             _blksize = BLKSIZE;
697         }
698     }
699   else
700     {
701       MediaBlock blk = blklist->getBlock(_request->_blkno);
702       while (_request->_blkoff >= blk.off + blk.size)
703         {
704           if (++_request->_blkno == blklist->numBlocks())
705             {
706               stealjob();
707               return;
708             }
709           blk = blklist->getBlock(_request->_blkno);
710           _request->_blkoff = blk.off;
711         }
712       _blksize = blk.off + blk.size - _request->_blkoff;
713       if (_blksize > BLKSIZE && !blklist->haveChecksum(_request->_blkno))
714         _blksize = BLKSIZE;
715     }
716   _blkno = _request->_blkno;
717   _blkstart = _request->_blkoff;
718   _request->_blkoff += _blksize;
719   run();
720 }
721
722 void
723 multifetchworker::run()
724 {
725   char rangebuf[128];
726
727   if (_state == WORKER_BROKEN || _state == WORKER_DONE)
728      return;    // just in case...
729   if (_noendrange)
730     sprintf(rangebuf, "%llu-", (unsigned long long)_blkstart);
731   else
732     sprintf(rangebuf, "%llu-%llu", (unsigned long long)_blkstart, (unsigned long long)_blkstart + _blksize - 1);
733   DBG << "#" << _workerno << ": BLK " << _blkno << ":" << rangebuf << " " << _url << endl;
734   if (curl_easy_setopt(_curl, CURLOPT_RANGE, !_noendrange || _blkstart != 0 ? rangebuf : (char *)0) != CURLE_OK)
735     {
736       _request->_activeworkers--;
737       _state = WORKER_BROKEN;
738       strncpy(_curlError, "curl_easy_setopt range failed", CURL_ERROR_SIZE);
739       return;
740     }
741   if (curl_multi_add_handle(_request->_multi, _curl) != CURLM_OK)
742     {
743       _request->_activeworkers--;
744       _state = WORKER_BROKEN;
745       strncpy(_curlError, "curl_multi_add_handle failed", CURL_ERROR_SIZE);
746       return;
747     }
748   _request->_havenewjob = true;
749   _off = _blkstart;
750   _size = _blksize;
751   if (_request->_blklist)
752     _request->_blklist->createDigest(_dig);     // resets digest
753   _state = WORKER_FETCH;
754   
755   double now = currentTime();
756   _blkstarttime = now;
757   _blkreceived = 0;
758 }
759
760
761 //////////////////////////////////////////////////////////////////////
762
763
764 multifetchrequest::multifetchrequest(const MediaMultiCurl *context, const Pathname &filename, const Url &baseurl, CURLM *multi, FILE *fp, callback::SendReport<DownloadProgressReport> *report, MediaBlockList *blklist, off_t filesize) : _context(context), _filename(filename), _baseurl(baseurl)
765 {
766   _fp = fp;
767   _report = report;
768   _blklist = blklist;
769   _filesize = filesize;
770   _multi = multi;
771   _stealing = false;
772   _havenewjob = false;
773   _blkno = 0;
774   if (_blklist)
775     _blkoff = _blklist->getBlock(0).off;
776   else
777     _blkoff = 0;
778   _activeworkers = 0;
779   _lookupworkers = 0;
780   _sleepworkers = 0;
781   _minsleepuntil = 0;
782   _finished = false;
783   _fetchedsize = 0;
784   _fetchedgoodsize = 0;
785   _totalsize = 0;
786   _lastperiodstart = _lastprogress = _starttime = currentTime();
787   _lastperiodfetched = 0;
788   _periodavg = 0;
789   _timeout = 0;
790   _connect_timeout = 0;
791   _maxspeed = 0;
792   if (blklist)
793     {
794       for (size_t blkno = 0; blkno < blklist->numBlocks(); blkno++)
795         {
796           MediaBlock blk = blklist->getBlock(blkno);
797           _totalsize += blk.size;
798         }
799     }
800   else if (filesize != off_t(-1))
801     _totalsize = filesize;
802 }
803
804 multifetchrequest::~multifetchrequest()
805 {
806   for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
807     {
808       multifetchworker *worker = *workeriter;
809       *workeriter = NULL;
810       delete worker;
811     }
812   _workers.clear();
813 }
814
815 void
816 multifetchrequest::run(std::vector<Url> &urllist)
817 {
818   int workerno = 0;
819   std::vector<Url>::iterator urliter = urllist.begin();
820   for (;;)
821     {
822       fd_set rset, wset, xset;
823       int maxfd, nqueue;
824
825       if (_finished)
826         {
827           DBG << "finished!" << endl;
828           break;
829         }
830
831       if (_activeworkers < MAXWORKERS && urliter != urllist.end() && _workers.size() < MAXURLS)
832         {
833           // spawn another worker!
834           multifetchworker *worker = new multifetchworker(workerno++, *this, *urliter);
835           _workers.push_back(worker);
836           if (worker->_state != WORKER_BROKEN)
837             {
838               _activeworkers++;
839               if (worker->_state != WORKER_LOOKUP)
840                 {
841                   worker->nextjob();
842                 }
843               else
844                 _lookupworkers++;
845             }
846           ++urliter;
847           continue;
848         }
849       if (!_activeworkers)
850         {
851           WAR << "No more active workers!" << endl;
852           // show the first worker error we find
853           for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
854             {
855               if ((*workeriter)->_state != WORKER_BROKEN)
856                 continue;
857               ZYPP_THROW(MediaCurlException(_baseurl, "Server error", (*workeriter)->_curlError));
858             }
859           break;
860         }
861
862       FD_ZERO(&rset);
863       FD_ZERO(&wset);
864       FD_ZERO(&xset);
865
866       curl_multi_fdset(_multi, &rset, &wset, &xset, &maxfd);
867
868       if (_lookupworkers)
869         for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
870           (*workeriter)->adddnsfd(rset, maxfd);
871
872       timeval tv;
873       // if we added a new job we have to call multi_perform once
874       // to make it show up in the fd set. do not sleep in this case.
875       tv.tv_sec = 0;
876       tv.tv_usec = _havenewjob ? 0 : 200000;
877       if (_sleepworkers && !_havenewjob)
878         {
879           if (_minsleepuntil == 0)
880             {
881               for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
882                 {
883                   multifetchworker *worker = *workeriter;
884                   if (worker->_state != WORKER_SLEEP)
885                     continue;
886                   if (!_minsleepuntil || _minsleepuntil > worker->_sleepuntil)
887                     _minsleepuntil = worker->_sleepuntil;
888                 }
889             }
890           double sl = _minsleepuntil - currentTime();
891           if (sl < 0)
892             {
893               sl = 0;
894               _minsleepuntil = 0;
895             }
896           if (sl < .2)
897             tv.tv_usec = sl * 1000000;
898         }
899       int r = select(maxfd + 1, &rset, &wset, &xset, &tv);
900       if (r == -1 && errno != EINTR)
901         ZYPP_THROW(MediaCurlException(_baseurl, "select() failed", "unknown error"));
902       if (r != 0 && _lookupworkers)
903         for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
904           {
905             multifetchworker *worker = *workeriter;
906             if (worker->_state != WORKER_LOOKUP)
907               continue;
908             (*workeriter)->dnsevent(rset);
909             if (worker->_state != WORKER_LOOKUP)
910               _lookupworkers--;
911           }
912       _havenewjob = false;
913
914       // run curl
915       for (;;)
916         {
917           CURLMcode mcode;
918           int tasks;
919           mcode = curl_multi_perform(_multi, &tasks);
920           if (mcode == CURLM_CALL_MULTI_PERFORM)
921             continue;
922           if (mcode != CURLM_OK)
923             ZYPP_THROW(MediaCurlException(_baseurl, "curl_multi_perform", "unknown error"));
924           break;
925         }
926
927       double now = currentTime();
928
929       // update periodavg
930       if (now > _lastperiodstart + .5)
931         {
932           if (!_periodavg)
933             _periodavg = (_fetchedsize - _lastperiodfetched) / (now - _lastperiodstart);
934           else
935             _periodavg = (_periodavg + (_fetchedsize - _lastperiodfetched) / (now - _lastperiodstart)) / 2;
936           _lastperiodfetched = _fetchedsize;
937           _lastperiodstart = now;
938         }
939
940       // wake up sleepers
941       if (_sleepworkers)
942         {
943           for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
944             {
945               multifetchworker *worker = *workeriter;
946               if (worker->_state != WORKER_SLEEP)
947                 continue;
948               if (worker->_sleepuntil > now)
949                 continue;
950               if (_minsleepuntil == worker->_sleepuntil)
951                 _minsleepuntil = 0;
952               DBG << "#" << worker->_workerno << ": sleep done, wake up" << endl;
953               _sleepworkers--;
954               // nextjob chnages the state
955               worker->nextjob();
956             }
957         }
958
959       // collect all curl results, reschedule new jobs
960       CURLMsg *msg;
961       while ((msg = curl_multi_info_read(_multi, &nqueue)) != 0)
962         {
963           if (msg->msg != CURLMSG_DONE)
964             continue;
965           multifetchworker *worker;
966           if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &worker) != CURLE_OK)
967             ZYPP_THROW(MediaCurlException(_baseurl, "curl_easy_getinfo", "unknown error"));
968           CURLcode cc = msg->data.result;
969           if (worker->_blkreceived && now > worker->_blkstarttime)
970             {
971               if (worker->_avgspeed)
972                 worker->_avgspeed = (worker->_avgspeed + worker->_blkreceived / (now - worker->_blkstarttime)) / 2;
973               else
974                 worker->_avgspeed = worker->_blkreceived / (now - worker->_blkstarttime);
975             }
976           DBG << "#" << worker->_workerno << ": BLK " << worker->_blkno << " done code " << cc << " speed " << worker->_avgspeed << endl;
977           curl_multi_remove_handle(_multi, msg->easy_handle);
978           if (cc == CURLE_HTTP_RETURNED_ERROR)
979             {
980               long statuscode = 0;
981               (void)curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &statuscode);
982               DBG << "HTTP status " << statuscode << endl;
983               if (statuscode == 416 && !_blklist)       /* Range error */
984                 {
985                   if (_filesize == off_t(-1))
986                     {
987                       if (!worker->_noendrange)
988                         {
989                           DBG << "#" << worker->_workerno << ": retrying with no end range" << endl;
990                           worker->_noendrange = true;
991                           worker->run();
992                           continue;
993                         }
994                       worker->_noendrange = false;
995                       worker->stealjob();
996                       continue;
997                     }
998                   if (worker->_blkstart >= _filesize)
999                     {
1000                       worker->nextjob();
1001                       continue;
1002                     }
1003                 }
1004             }
1005           if (cc == 0)
1006             {
1007               if (!worker->checkChecksum())
1008                 {
1009                   WAR << "#" << worker->_workerno << ": checksum error, disable worker" << endl;
1010                   worker->_state = WORKER_BROKEN;
1011                   strncpy(worker->_curlError, "checksum error", CURL_ERROR_SIZE);
1012                   _activeworkers--;
1013                   continue;
1014                 }
1015               if (worker->_state == WORKER_FETCH)
1016                 {
1017                   if (worker->_competing)
1018                     {
1019                       worker->disableCompetition();
1020                       // multiple workers wrote into this block. We already know that our
1021                       // data was correct, but maybe some other worker overwrote our data
1022                       // with something broken. Thus we have to re-check the block.
1023                       if (!worker->recheckChecksum())
1024                         {
1025                           DBG << "#" << worker->_workerno << ": recheck checksum error, refetch block" << endl;
1026                           // re-fetch! No need to worry about the bad workers,
1027                           // they will now be set to DISCARD. At the end of their block
1028                           // they will notice that they wrote bad data and go into BROKEN.
1029                           worker->run();
1030                           continue;
1031                         }
1032                     }
1033                   _fetchedgoodsize += worker->_blksize;
1034                 }
1035
1036               // make bad workers sleep a little
1037               double maxavg = 0;
1038               int maxworkerno = 0;
1039               int numbetter = 0;
1040               for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
1041                 {
1042                   multifetchworker *oworker = *workeriter;
1043                   if (oworker->_state == WORKER_BROKEN)
1044                     continue;
1045                   if (oworker->_avgspeed > maxavg)
1046                     {
1047                       maxavg = oworker->_avgspeed;
1048                       maxworkerno = oworker->_workerno;
1049                     }
1050                   if (oworker->_avgspeed > worker->_avgspeed)
1051                     numbetter++;
1052                 }
1053               if (maxavg && !_stealing)
1054                 {
1055                   double ratio = worker->_avgspeed / maxavg;
1056                   ratio = 1 - ratio;
1057                   if (numbetter < 3)    // don't sleep that much if we're in the top two
1058                     ratio = ratio * ratio;
1059                   if (ratio > .01)
1060                     {
1061                       DBG << "#" << worker->_workerno << ": too slow ("<< ratio << ", " << worker->_avgspeed << ", #" << maxworkerno << ": " << maxavg << "), going to sleep for " << ratio * 1000 << " ms" << endl;
1062                       worker->_sleepuntil = now + ratio;
1063                       worker->_state = WORKER_SLEEP;
1064                       _sleepworkers++;
1065                       continue;
1066                     }
1067                 }
1068
1069               // do rate control (if requested)
1070               // should use periodavg, but that's not what libcurl does
1071               if (_maxspeed && now > _starttime)
1072                 {
1073                   double avg = _fetchedsize / (now - _starttime);
1074                   avg = worker->_maxspeed * _maxspeed / avg;
1075                   if (avg < _maxspeed / MAXWORKERS)
1076                     avg = _maxspeed / MAXWORKERS;
1077                   if (avg > _maxspeed)
1078                     avg = _maxspeed;
1079                   if (avg < 1024)
1080                     avg = 1024;
1081                   worker->_maxspeed = avg;
1082                   curl_easy_setopt(worker->_curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)(avg));
1083                 }
1084
1085               worker->nextjob();
1086             }
1087           else
1088             {
1089               worker->_state = WORKER_BROKEN;
1090               _activeworkers--;
1091               if (!_activeworkers && !(urliter != urllist.end() && _workers.size() < MAXURLS))
1092                 {
1093                   // end of workers reached! goodbye!
1094                   worker->evaluateCurlCode(Pathname(), cc, false);
1095                 }
1096             }
1097         }
1098
1099       // send report
1100       if (_report)
1101         {
1102           int percent = _totalsize ? (100 * (_fetchedgoodsize + _fetchedsize)) / (_totalsize + _fetchedsize) : 0;
1103           double avg = 0;
1104           if (now > _starttime)
1105             avg = _fetchedsize / (now - _starttime);
1106           if (!(*(_report))->progress(percent, _baseurl, avg, _lastperiodstart == _starttime ? avg : _periodavg))
1107             ZYPP_THROW(MediaCurlException(_baseurl, "User abort", "cancelled"));
1108         }
1109
1110       if (_timeout && now - _lastprogress > _timeout)
1111         break;
1112     }
1113
1114   if (!_finished)
1115     ZYPP_THROW(MediaTimeoutException(_baseurl));
1116
1117   // print some download stats
1118   WAR << "overall result" << endl;
1119   for (std::list<multifetchworker *>::iterator workeriter = _workers.begin(); workeriter != _workers.end(); ++workeriter)
1120     {
1121       multifetchworker *worker = *workeriter;
1122       WAR << "#" << worker->_workerno << ": state: " << worker->_state << " received: " << worker->_received << " url: " << worker->_url << endl;
1123     }
1124 }
1125
1126
1127 //////////////////////////////////////////////////////////////////////
1128
1129
1130 MediaMultiCurl::MediaMultiCurl(const Url &url_r, const Pathname & attach_point_hint_r)
1131     : MediaCurl(url_r, attach_point_hint_r)
1132 {
1133   MIL << "MediaMultiCurl::MediaMultiCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
1134   _multi = 0;
1135   _customHeadersMetalink = 0;
1136 }
1137
1138 MediaMultiCurl::~MediaMultiCurl()
1139 {
1140   if (_customHeadersMetalink)
1141     {
1142       curl_slist_free_all(_customHeadersMetalink);
1143       _customHeadersMetalink = 0;
1144     }
1145   if (_multi)
1146     {
1147       curl_multi_cleanup(_multi);
1148       _multi = 0;
1149     }
1150   std::map<std::string, CURL *>::iterator it;
1151   for (it = _easypool.begin(); it != _easypool.end(); it++)
1152     {
1153       CURL *easy = it->second;
1154       if (easy)
1155         {
1156           curl_easy_cleanup(easy);
1157           it->second = NULL;
1158         }
1159     }
1160 }
1161
1162 void MediaMultiCurl::setupEasy()
1163 {
1164   MediaCurl::setupEasy();
1165
1166   if (_customHeadersMetalink)
1167     {
1168       curl_slist_free_all(_customHeadersMetalink);
1169       _customHeadersMetalink = 0;
1170     }
1171   struct curl_slist *sl = _customHeaders;
1172   for (; sl; sl = sl->next)
1173     _customHeadersMetalink = curl_slist_append(_customHeadersMetalink, sl->data);
1174   _customHeadersMetalink = curl_slist_append(_customHeadersMetalink, "Accept: */*, application/metalink+xml, application/metalink4+xml");
1175 }
1176
1177
1178 void MediaMultiCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
1179 {
1180   Pathname dest = target.absolutename();
1181   if( assert_dir( dest.dirname() ) )
1182   {
1183     DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1184     Url url(getFileUrl(filename));
1185     ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1186   }
1187   string destNew = target.asString() + ".new.zypp.XXXXXX";
1188   char *buf = ::strdup( destNew.c_str());
1189   if( !buf)
1190   {
1191     ERR << "out of memory for temp file name" << endl;
1192     Url url(getFileUrl(filename));
1193     ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
1194   }
1195
1196   int tmp_fd = ::mkstemp( buf );
1197   if( tmp_fd == -1)
1198   {
1199     free( buf);
1200     ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1201     ZYPP_THROW(MediaWriteException(destNew));
1202   }
1203   destNew = buf;
1204   free( buf);
1205
1206   FILE *file = ::fdopen( tmp_fd, "w" );
1207   if ( !file ) {
1208     ::close( tmp_fd);
1209     filesystem::unlink( destNew );
1210     ERR << "fopen failed for file '" << destNew << "'" << endl;
1211     ZYPP_THROW(MediaWriteException(destNew));
1212   }
1213   DBG << "dest: " << dest << endl;
1214   DBG << "temp: " << destNew << endl;
1215
1216   // set IFMODSINCE time condition (no download if not modified)
1217   if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) )
1218   {
1219     curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1220     curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime());
1221   }
1222   else
1223   {
1224     curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1225     curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1226   }
1227   // change header to include Accept: metalink
1228   curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, _customHeadersMetalink);
1229   try
1230     {
1231       MediaCurl::doGetFileCopyFile(filename, dest, file, report, options);
1232     }
1233   catch (Exception &ex)
1234     {
1235       ::fclose(file);
1236       filesystem::unlink(destNew);
1237       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1238       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1239       curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, _customHeaders);
1240       ZYPP_RETHROW(ex);
1241     }
1242   curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1243   curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1244   curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, _customHeaders);
1245   long httpReturnCode = 0;
1246   CURLcode infoRet = curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpReturnCode);
1247   if (infoRet == CURLE_OK)
1248   {
1249     DBG << "HTTP response: " + str::numstring(httpReturnCode) << endl;
1250     if ( httpReturnCode == 304
1251          || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) ) // not modified
1252     {
1253       DBG << "not modified: " << PathInfo(dest) << endl;
1254       return;
1255     }
1256   }
1257   else
1258   {
1259     WAR << "Could not get the reponse code." << endl;
1260   }
1261   char *ptr = NULL;
1262   if (curl_easy_getinfo(_curl, CURLINFO_CONTENT_TYPE, &ptr) == CURLE_OK && ptr)
1263     {
1264       string ct = string(ptr);
1265       if (ct.find("application/metalink+xml") == 0 || ct.find("application/metalink4+xml") == 0)
1266         {
1267           bool userabort = false;
1268           fclose(file);
1269           file = NULL;
1270           Pathname failedFile = ZConfig::instance().repoCachePath() / "MultiCurl.failed";
1271           try
1272             {
1273               MetaLinkParser mlp;
1274               mlp.parse(Pathname(destNew));
1275               MediaBlockList bl = mlp.getBlockList();
1276               vector<Url> urls = mlp.getUrls();
1277               DBG << bl << endl;
1278               file = fopen(destNew.c_str(), "w+");
1279               if (!file)
1280                 ZYPP_THROW(MediaWriteException(destNew));
1281               if (PathInfo(target).isExist())
1282                 {
1283                   DBG << "reusing blocks from file " << target << endl;
1284                   bl.reuseBlocks(file, target.asString());
1285                   DBG << bl << endl;
1286                 }
1287               if (bl.haveChecksum(1) && PathInfo(failedFile).isExist())
1288                 {
1289                   DBG << "reusing blocks from file " << failedFile << endl;
1290                   bl.reuseBlocks(file, failedFile.asString());
1291                   DBG << bl << endl;
1292                   filesystem::unlink(failedFile);
1293                 }
1294               Pathname df = deltafile();
1295               if (!df.empty())
1296                 {
1297                   DBG << "reusing blocks from file " << df << endl;
1298                   bl.reuseBlocks(file, df.asString());
1299                   DBG << bl << endl;
1300                 }
1301               try
1302                 {
1303                   multifetch(filename, file, &urls, &report, &bl);
1304                 }
1305               catch (MediaCurlException &ex)
1306                 {
1307                   userabort = ex.errstr() == "User abort";
1308                   ZYPP_RETHROW(ex);
1309                 }
1310             }
1311           catch (Exception &ex)
1312             {
1313               // something went wrong. fall back to normal download
1314               if (file)
1315                 fclose(file);
1316               file = NULL;
1317               if (PathInfo(destNew).size() >= 63336)
1318                 {
1319                   ::unlink(failedFile.asString().c_str());
1320                   filesystem::hardlinkCopy(destNew, failedFile);
1321                 }
1322               if (userabort)
1323                 {
1324                   filesystem::unlink(destNew);
1325                   ZYPP_RETHROW(ex);
1326                 }
1327               file = fopen(destNew.c_str(), "w+");
1328               if (!file)
1329                 ZYPP_THROW(MediaWriteException(destNew));
1330               MediaCurl::doGetFileCopyFile(filename, dest, file, report, options | OPTION_NO_REPORT_START);
1331             }
1332         }
1333     }
1334   if (::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 )))
1335     {
1336       ERR << "Failed to chmod file " << destNew << endl;
1337     }
1338   if (::fclose(file))
1339     {
1340       filesystem::unlink(destNew);
1341       ERR << "Fclose failed for file '" << destNew << "'" << endl;
1342       ZYPP_THROW(MediaWriteException(destNew));
1343     }
1344   if ( rename( destNew, dest ) != 0 )
1345     {
1346       ERR << "Rename failed" << endl;
1347       ZYPP_THROW(MediaWriteException(dest));
1348     }
1349   DBG << "done: " << PathInfo(dest) << endl;
1350 }
1351
1352 void MediaMultiCurl::multifetch(const Pathname & filename, FILE *fp, std::vector<Url> *urllist, callback::SendReport<DownloadProgressReport> *report, MediaBlockList *blklist, off_t filesize) const
1353 {
1354   Url baseurl(getFileUrl(filename));
1355   if (blklist && filesize == off_t(-1) && blklist->haveFilesize())
1356     filesize = blklist->getFilesize();
1357   if (blklist && !blklist->haveBlocks() && filesize != 0)
1358     blklist = 0;
1359   if (blklist && (filesize == 0 || !blklist->numBlocks()))
1360     {
1361       checkFileDigest(baseurl, fp, blklist);
1362       return;
1363     }
1364   if (filesize == 0)
1365     return;
1366   if (!_multi)
1367     {
1368       _multi = curl_multi_init();
1369       if (!_multi)
1370         ZYPP_THROW(MediaCurlInitException(baseurl));
1371     }
1372   multifetchrequest req(this, filename, baseurl, _multi, fp, report, blklist, filesize);
1373   req._timeout = _settings.timeout();
1374   req._connect_timeout = _settings.connectTimeout();
1375   req._maxspeed = _settings.maxDownloadSpeed();
1376   std::vector<Url> myurllist;
1377   for (std::vector<Url>::iterator urliter = urllist->begin(); urliter != urllist->end(); ++urliter)
1378     {
1379       try
1380         {
1381           string scheme = urliter->getScheme();
1382           if (scheme == "http" || scheme == "https" || scheme == "ftp")
1383             {
1384               checkProtocol(*urliter);
1385               myurllist.push_back(*urliter);
1386             }
1387         }
1388       catch (...)
1389         {
1390         }
1391     }
1392   if (!myurllist.size())
1393     myurllist.push_back(baseurl);
1394   req.run(myurllist);
1395   checkFileDigest(baseurl, fp, blklist);
1396 }
1397
1398 void MediaMultiCurl::checkFileDigest(Url &url, FILE *fp, MediaBlockList *blklist) const
1399 {
1400   if (!blklist || !blklist->haveFileChecksum())
1401     return;
1402   if (fseeko(fp, off_t(0), SEEK_SET))
1403     ZYPP_THROW(MediaCurlException(url, "fseeko", "seek error"));
1404   Digest dig;
1405   blklist->createFileDigest(dig);
1406   char buf[4096];
1407   size_t l;
1408   while ((l = fread(buf, 1, sizeof(buf), fp)) > 0)
1409     dig.update(buf, l);
1410   if (!blklist->verifyFileDigest(dig))
1411     ZYPP_THROW(MediaCurlException(url, "file verification failed", "checksum error"));
1412 }
1413
1414 bool MediaMultiCurl::isDNSok(const string &host) const
1415 {
1416   return _dnsok.find(host) == _dnsok.end() ? false : true;
1417 }
1418
1419 void MediaMultiCurl::setDNSok(const string &host) const
1420 {
1421   _dnsok.insert(host);
1422 }
1423
1424 CURL *MediaMultiCurl::fromEasyPool(const string &host) const
1425 {
1426   if (_easypool.find(host) == _easypool.end())
1427     return 0;
1428   CURL *ret = _easypool[host];
1429   _easypool.erase(host);
1430   return ret;
1431 }
1432
1433 void MediaMultiCurl::toEasyPool(const std::string &host, CURL *easy) const
1434 {
1435   CURL *oldeasy = _easypool[host];
1436   _easypool[host] = easy;
1437   if (oldeasy)
1438     curl_easy_cleanup(oldeasy);
1439 }
1440
1441   } // namespace media
1442 } // namespace zypp
1443