libzypp  11.13.5
MediaAria2c.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
13 #include <iostream>
14 #include <list>
15 #include <vector>
16 #include <fstream>
17 #include <boost/lexical_cast.hpp>
18 
19 #include "zypp/base/Logger.h"
20 #include "zypp/base/Regex.h"
21 #include "zypp/ExternalProgram.h"
22 #include "zypp/ProgressData.h"
23 #include "zypp/base/String.h"
24 #include "zypp/base/Gettext.h"
25 #include "zypp/base/Sysconfig.h"
26 #include "zypp/base/Gettext.h"
27 #include "zypp/ZYppCallbacks.h"
28 
29 #include "zypp/Edition.h"
30 #include "zypp/Target.h"
31 #include "zypp/ZYppFactory.h"
32 #include "zypp/ZConfig.h"
33 
34 #include "zypp/TmpPath.h"
35 
36 #include "zypp/media/MediaAria2c.h"
38 #include "zypp/media/ProxyInfo.h"
40 #include "zypp/media/MediaCurl.h"
41 #include "zypp/thread/Once.h"
42 #include <cstdlib>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/mount.h>
46 #include <errno.h>
47 #include <dirent.h>
48 #include <unistd.h>
49 #include <boost/format.hpp>
50 
51 #define DETECT_DIR_INDEX 0
52 #define CONNECT_TIMEOUT 60
53 #define TRANSFER_TIMEOUT 60 * 3
54 #define TRANSFER_TIMEOUT_MAX 60 * 60
55 
56 #define ARIA_BINARY "aria2c"
57 
58 using namespace std;
59 using namespace zypp::base;
60 
61 namespace zypp
62 {
63 namespace media
64 {
65 
66 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
67 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
68 
69 //check if aria2c is present in the system
70 bool
71 MediaAria2c::existsAria2cmd()
72 {
73  static const char* argv[] =
74  {
76  "--version",
77  NULL
78  };
79  ExternalProgram aria( argv, ExternalProgram::Stderr_To_Stdout );
80  return( aria.close() == 0 );
81 }
82 
88 void fillAriaCmdLine( const string &ariaver,
89  const TransferSettings &s,
90  filesystem::TmpPath &credentials,
91  const Url &url,
92  const Pathname &destination,
94 {
95 
96  // options that are not passed in the command line
97  // like credentials, every string is in the
98  // opt=val format
99  list<string> file_options;
100 
101  args.push_back(ARIA_BINARY);
102  args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
103  args.push_back("--summary-interval=1");
104  args.push_back("--follow-metalink=mem");
105  args.push_back("--check-integrity=true");
106  args.push_back("--file-allocation=none");
107 
108  // save the stats of the mirrors and use them as input later
109  Pathname statsFile = ZConfig::instance().repoCachePath() / "aria2.stats";
110  args.push_back(str::form("--server-stat-of=%s", statsFile.c_str()));
111  args.push_back(str::form("--server-stat-if=%s", statsFile.c_str()));
112  args.push_back("--uri-selector=adaptive");
113 
114  // only present in recent aria lets find out the aria version
115  vector<string> fields;
116  // "aria2c version x.x"
117  str::split( ariaver, std::back_inserter(fields));
118  if ( fields.size() == 3 )
119  {
120  if ( Edition(fields[2]) >= Edition("1.1.2") )
121  args.push_back( "--use-head=false");
122  }
123 
124  if ( s.maxDownloadSpeed() > 0 )
125  args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
126  if ( s.minDownloadSpeed() > 0 )
127  args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
128 
129  args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
130 
131  if ( Edition(fields[2]) < Edition("1.2.0") )
132  WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
133 
134  // TODO make this one configurable
135  args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
136 
137  // add the anonymous id.
138  for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
139  it != s.headersEnd();
140  ++it )
141  args.push_back(str::form("--header=%s", it->c_str() ));
142 
143  args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
144 
145  if ( s.username().empty() )
146  {
147  if ( url.getScheme() == "ftp" )
148  {
149  // set anonymous ftp
150  args.push_back(str::form("--ftp-user=%s", "suseuser" ));
151  args.push_back(str::form("--ftp-passwd=%s", VERSION ));
152 
153  string id = "yast2";
154  id += VERSION;
155  DBG << "Anonymous FTP identification: '" << id << "'" << endl;
156  }
157  }
158  else
159  {
160  MIL << "Passing " << url.getScheme() << " credentials '" << s.username() << ':' << (s.password().empty() ? "" : "PASSWORD")<< "'" << endl;
161  if ( url.getScheme() == "ftp" )
162  file_options.push_back(str::form("ftp-user=%s", s.username().c_str() ));
163  else if ( url.getScheme() == "http" ||
164  url.getScheme() == "https" )
165  file_options.push_back(str::form("http-user=%s", s.username().c_str() ));
166 
167  if ( s.password().size() )
168  {
169  if ( url.getScheme() == "ftp" )
170  file_options.push_back(str::form("ftp-passwd=%s", s.password().c_str() ));
171  else if ( url.getScheme() == "http" ||
172  url.getScheme() == "https" )
173  file_options.push_back(str::form("http-passwd=%s", s.password().c_str() ));
174  }
175  }
176 
177  if ( s.proxyEnabled() )
178  {
179  args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
180  if ( ! s.proxyUsername().empty() )
181  {
182  MIL << "Passing " << /*url.getScheme()*/"http" << "-proxy credentials '" << s.proxyUsername() << ':' << (s.proxyPassword().empty() ? "" : "PASSWORD")<< "'" << endl;
183  file_options.push_back(str::form("http-proxy-user=%s", s.proxyUsername().c_str() ));
184  if ( ! s.proxyPassword().empty() )
185  file_options.push_back(str::form("http-proxy-passwd=%s", s.proxyPassword().c_str() ));
186  }
187  }
188 
189  if ( ! destination.empty() )
190  args.push_back(str::form("--dir=%s", destination.c_str()));
191 
192  // now append the file if there are hidden options
193  if ( ! file_options.empty() )
194  {
196  ofstream outs( tmp.path().c_str() );
197  for_( it, file_options.begin(), file_options.end() )
198  outs << *it << endl;
199  outs.close();
200 
201  credentials = tmp;
202  args.push_back(str::form("--conf-path=%s", credentials.path().c_str()));
203  }
204 
205  // Credentials are passed via --{ftp,http}-{user,passwd}.
206  // Aria does not like them being repeated in the url. (bnc #544634)
207  args.push_back(url.asString( url.getViewOptions()
208  - url::ViewOptions::WITH_USERNAME
209  - url::ViewOptions::WITH_PASSWORD ).c_str());
210 }
211 
212 const char *const MediaAria2c::agentString()
213 {
214  // we need to add the release and identifier to the
215  // agent string.
216  // The target could be not initialized, and then this information
217  // is not available.
218  Target_Ptr target = zypp::getZYpp()->getTarget();
219 
220  static const std::string _value(
221  str::form(
222  "ZYpp %s (%s) %s"
223  , VERSION
224  , MediaAria2c::_aria2cVersion.c_str()
225  , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
226  )
227  );
228  return _value.c_str();
229 }
230 
231 
232 
233 MediaAria2c::MediaAria2c( const Url & url_r,
234  const Pathname & attach_point_hint_r )
235  : MediaCurl( url_r, attach_point_hint_r )
236 {
237  MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
238  //Get aria2c version
240 }
241 
242 void MediaAria2c::attachTo (bool next)
243 {
244  MediaCurl::attachTo(next);
246 }
247 
248 bool
249 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
250 {
251  return MediaCurl::checkAttachPoint( apoint );
252 }
253 
255 {
257 }
258 
259 void MediaAria2c::releaseFrom( const std::string & ejectDev )
260 {
261  MediaCurl::releaseFrom(ejectDev);
262 }
263 
264 void MediaAria2c::getFile( const Pathname & filename ) const
265 {
266  // Use absolute file name to prevent access of files outside of the
267  // hierarchy below the attach point.
268  getFileCopy(filename, localPath(filename).absolutename());
269 }
270 
271 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
272 {
274 
275  Url fileurl(getFileUrl(filename));
276 
277  bool retry = false;
278 
280 
281  filesystem::TmpPath credentials;
282  fillAriaCmdLine(_aria2cVersion, _settings, credentials, fileurl, target.dirname(), args);
283 
284  do
285  {
286  try
287  {
288  report->start(fileurl, target.asString() );
289 
291 
292  // extended regex for parsing of progress lines
293  // progress line like: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:6899.88KiB/s]
294  // but since 1.4.0: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:899.8KiBs]
295  // (bnc #513944) [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:3.8MiBs]
296  // [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs]
297  // later got also ETA: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs ETA:02s]
298  static str::regex rxProgress(
299  "^\\[#[0-9]+ SIZE:[0-9\\.]+(|Ki|Mi|Ti)B/[0-9\\.]+(|Ki|Mi|Ti)B\\(?([0-9]+)?%?\\)? CN:[0-9]+ SPD:([0-9\\.]+)(|Ki|Mi|Ti)Bs.*\\]$");
300 
301  // whether we received correct progress line before corresponding FILE line
302  bool gotProgress = false;
303  // download progress in %
304  int progress = 0;
305  // current download speed in bytes
306  double current_speed = 0;
307  // download speed in bytes
308  double average_speed = 0;
309  // number of speed measurements
310  long average_speed_count = 0;
311 
312  // here we capture aria output exceptions
313  vector<string> ariaExceptions;
314 
315  // whether it makes sense to retry with --continue
316  bool partialDownload = false;
317  // whether user request abort of the download
318  bool userAbort = false;
319 
320  //Process response
321  for(std::string ariaResponse( aria.receiveLine());
322  ariaResponse.length();
323  ariaResponse = aria.receiveLine())
324  {
325  string line = str::trim(ariaResponse);
326  // INT << line << endl;
327 
328  // look for the progress line and save parsed values until we find
329  // a string with FILE: later.
330  if ( str::hasPrefix(line, "[#") )
331  {
332  str::smatch progressValues;
333  if (( gotProgress = str::regex_match(line, progressValues, rxProgress) ))
334  {
335  // INT << "got: progress: '" << progressValues[3]
336  // << "' speed: '" << progressValues[4] << " "
337  // << progressValues[5] << "Bs'" << endl;
338 
339  // get the percentage (progress) data
340  progress = std::atoi(progressValues[3].c_str());
341 
342  // get the speed
343 
344  int factor = 1; // B/KiB/MiB multiplication factor
345  if (progressValues[5] == "Ki")
346  factor = 1024;
347  else if (progressValues[5] == "Mi")
348  factor = 0x100000;
349  else if (progressValues[5] == "Ti")
350  factor = 0x40000000;
351 
352  try {
353  current_speed = boost::lexical_cast<double>(progressValues[4]);
354  // convert to and work with bytes everywhere (bnc #537870)
355  current_speed *= factor;
356  }
357  catch (const std::exception&) {
358  ERR << "Can't parse speed from '" << progressValues[4] << "'" << endl;
359  current_speed = 0;
360  }
361  }
362  else
363  ERR << "Can't parse progress line '" << line << "'" << endl;
364  }
365  // save error messages for later
366  else if ( str::hasPrefix(line, "Exception: ") )
367  {
368  // for auth exception, we throw
369  if (!line.substr(0,31).compare("Exception: Authorization failed") )
370  {
372  _url, "Login failed.", "Login failed", "auth hint"
373  ));
374  }
375  // otherwise, remember the error
376  string excpMsg = line.substr(10, line.size());
377  DBG << "aria2c reported: '" << excpMsg << "'" << endl;
378  ariaExceptions.push_back(excpMsg);
379  }
380  // The file line tells which file corresponds to the previous progress,
381  // eg.: FILE: ./packages.FL.gz
382  else if ( str::hasPrefix(line, "FILE: ") )
383  {
384  // get the FILE name
385  string theFile(line.substr(6, line.size()));
386  // is the report about the filename we are downloading?
387  // aria may report progress about metalinks, torrent and
388  // other stuff which is not the main transfer
389  // the reported file is the url before the server emits a response
390  // and then is reported as the target file
391  if ( Pathname(theFile) == target || theFile == fileurl.asCompleteString() )
392  {
393  // once we find the FILE: line, progress has to be
394  // non empty
395  if ( gotProgress )
396  {
397  // we have a new average speed
398  average_speed_count++;
399 
400  // this is basically A: average
401  // ((n-1)A(n-1) + Xn)/n = A(n)
402  average_speed =
403  (((average_speed_count - 1)*average_speed) + current_speed)
404  / average_speed_count;
405 
406  if (!partialDownload && progress > 0)
407  partialDownload = true;
408 
409  if ( ! report->progress ( progress, fileurl, average_speed, current_speed ) )
410  userAbort = true;
411 
412  // clear the flag to detect mismatches between [# and FILE: lines
413  gotProgress = false;
414  }
415  else
416  {
417  WAR << "aria2c reported a file, but no progress data available" << endl;
418  }
419  }
420  else
421  {
422  DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
423  }
424  }
425  else
426  {
427  // other line type, just ignore it.
428  }
429  }
430 
431  int code;
432  if (userAbort)
433  {
434  aria.kill();
435  code = 7;
436  }
437  else
438  code = aria.close();
439 
440  switch ( code )
441  {
442  case 0: // success?
443  if ( ! PathInfo( target ).isExist() )
444  {
445  // bnc #564816: aria2 might return 0 if an error occurred
446  // _before_ the download actually started.
447 
448  // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
449  std::string msg( str::form(_("Failed to download %s from %s"),
450  filename.c_str(), _url.asString().c_str() ) );
451 
452  MediaException e( msg );
453  for_( it, ariaExceptions.begin(), ariaExceptions.end() )
454  e.addHistory( *it );
455 
456  ZYPP_THROW( e );
457  }
458  break;
459 
460  case 2: // timeout
461  {
463  for_(it, ariaExceptions.begin(), ariaExceptions.end())
464  e.addHistory(*it);
465  ZYPP_THROW(e);
466  }
467  break;
468 
469  case 3: // not found
470  case 4: // max notfound reached
471  {
472  MediaFileNotFoundException e(_url, filename);
473  for_(it, ariaExceptions.begin(), ariaExceptions.end())
474  e.addHistory(*it);
475  ZYPP_THROW(e);
476  }
477  break;
478 
479  case 5: // too slow
480  case 6: // network problem
481  case 7: // unfinished downloads (ctr-c)
482  case 1: // unknown
483  default:
484  {
485  if ( partialDownload )
486  {
487  // Ask for retry on partial downloads, when it makes sense to retry with --continue!
488  // Other errors are handled by the layers above.
489  MediaException e(str::form(_("Download interrupted at %d%%"), progress ));
490  for_(it, ariaExceptions.begin(), ariaExceptions.end())
491  e.addHistory(*it);
492 
494  if ( action == DownloadProgressReport::RETRY )
495  {
496  retry = true;
497  continue;
498  }
499  }
500 
501  string msg;
502  if (userAbort)
503  msg = _("Download interrupted by user");
504  else
505  // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
506  msg = str::form(_("Failed to download %s from %s"),
507  filename.c_str(), _url.asString().c_str());
508 
509  MediaException e(msg);
510  for_(it, ariaExceptions.begin(), ariaExceptions.end())
511  e.addHistory(*it);
512 
513  ZYPP_THROW(e);
514  }
515  break;
516  }
517 
518  retry = false;
519  }
520  // retry with proper authentication data
521  catch (MediaUnauthorizedException & ex_r)
522  {
523  if(authenticate(ex_r.hint(), !retry))
524  retry = true;
525  else
526  {
527  report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
528  ZYPP_RETHROW(ex_r);
529  }
530 
531  }
532  // unexpected exception
533  catch (MediaException & excpt_r)
534  {
535  // FIXME: error number fix
536  report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
537  ZYPP_RETHROW(excpt_r);
538  }
539  }
540  while (retry);
541 
542  report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
543 }
544 
545 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
546 {
547  return MediaCurl::getDoesFileExist(filename);
548 }
549 
550 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
551 {
552  return MediaCurl::doGetDoesFileExist(filename);
553 }
554 
555 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
556 {
557  MediaCurl::getDir(dirname, recurse_r);
558 }
559 
560 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
561 {
562  return false;
563 }
564 
565 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
566  const Pathname & dirname, bool dots ) const
567 {
568  getDirectoryYast( retlist, dirname, dots );
569 }
570 
572  const Pathname & dirname, bool dots ) const
573 {
574  getDirectoryYast( retlist, dirname, dots );
575 }
576 
578 {
579  static const char* argv[] =
580  {
581  ARIA_BINARY,
582  "--version",
583  NULL
584  };
586  std::string vResponse( str::trim( aria.receiveLine() ) );
587  aria.close();
588  return vResponse;
589 }
590 } // namespace media
591 } // namespace zypp
592 //