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