1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
17 #include <boost/lexical_cast.hpp>
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"
29 #include "zypp/Edition.h"
30 #include "zypp/Target.h"
31 #include "zypp/ZYppFactory.h"
32 #include "zypp/ZConfig.h"
34 #include "zypp/TmpPath.h"
36 #include "zypp/media/MediaAria2c.h"
37 #include "zypp/media/proxyinfo/ProxyInfos.h"
38 #include "zypp/media/ProxyInfo.h"
39 #include "zypp/media/MediaUserAuth.h"
40 #include "zypp/media/MediaCurl.h"
41 #include "zypp/thread/Once.h"
43 #include <sys/types.h>
45 #include <sys/mount.h>
49 #include <boost/format.hpp>
51 #define DETECT_DIR_INDEX 0
52 #define CONNECT_TIMEOUT 60
53 #define TRANSFER_TIMEOUT 60 * 3
54 #define TRANSFER_TIMEOUT_MAX 60 * 60
56 #define ARIA_BINARY "aria2c"
59 using namespace zypp::base;
66 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
67 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
69 //check if aria2c is present in the system
71 MediaAria2c::existsAria2cmd()
73 static const char* argv[] =
79 ExternalProgram aria( argv, ExternalProgram::Stderr_To_Stdout );
80 return( aria.close() == 0 );
84 * comannd line for aria.
85 * The argument list gets passed as reference
88 void fillAriaCmdLine( const string &ariaver,
89 const TransferSettings &s,
90 filesystem::TmpPath &credentials,
92 const Pathname &destination,
93 ExternalProgram::Arguments &args )
96 // options that are not passed in the command line
97 // like credentials, every string is in the
99 list<string> file_options;
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");
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");
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 )
120 if ( Edition(fields[2]) >= Edition("1.1.2") )
121 args.push_back( "--use-head=false");
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()));
129 args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
131 if ( Edition(fields[2]) < Edition("1.2.0") )
132 WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
134 // TODO make this one configurable
135 args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
137 // add the anonymous id.
138 for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
139 it != s.headersEnd();
141 args.push_back(str::form("--header=%s", it->c_str() ));
143 args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
145 if ( s.username().empty() )
147 if ( url.getScheme() == "ftp" )
150 args.push_back(str::form("--ftp-user=%s", "suseuser" ));
151 args.push_back(str::form("--ftp-passwd=%s", VERSION ));
155 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
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() ));
167 if ( s.password().size() )
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() ));
177 if ( s.proxyEnabled() )
179 args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
180 if ( ! s.proxyUsername().empty() )
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() ));
189 if ( ! destination.empty() )
190 args.push_back(str::form("--dir=%s", destination.c_str()));
192 // now append the file if there are hidden options
193 if ( ! file_options.empty() )
195 filesystem::TmpFile tmp;
196 ofstream outs( tmp.path().c_str() );
197 for_( it, file_options.begin(), file_options.end() )
202 args.push_back(str::form("--conf-path=%s", credentials.path().c_str()));
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());
212 const char *const MediaAria2c::agentString()
214 // we need to add the release and identifier to the
216 // The target could be not initialized, and then this information
218 Target_Ptr target = zypp::getZYpp()->getTarget();
220 static const std::string _value(
224 , MediaAria2c::_aria2cVersion.c_str()
225 , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
228 return _value.c_str();
233 MediaAria2c::MediaAria2c( const Url & url_r,
234 const Pathname & attach_point_hint_r )
235 : MediaCurl( url_r, attach_point_hint_r )
237 MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
239 _aria2cVersion = getAria2cVersion();
242 void MediaAria2c::attachTo (bool next)
244 MediaCurl::attachTo(next);
245 _settings.setUserAgentString(agentString());
249 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
251 return MediaCurl::checkAttachPoint( apoint );
254 void MediaAria2c::disconnectFrom()
256 MediaCurl::disconnectFrom();
259 void MediaAria2c::releaseFrom( const std::string & ejectDev )
261 MediaCurl::releaseFrom(ejectDev);
264 void MediaAria2c::getFile( const Pathname & filename ) const
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());
271 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
273 callback::SendReport<DownloadProgressReport> report;
275 Url fileurl(getFileUrl(filename));
279 ExternalProgram::Arguments args;
281 filesystem::TmpPath credentials;
282 fillAriaCmdLine(_aria2cVersion, _settings, credentials, fileurl, target.dirname(), args);
288 report->start(fileurl, target.asString() );
290 ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
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.*\\]$");
301 // whether we received correct progress line before corresponding FILE line
302 bool gotProgress = false;
303 // download progress in %
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;
312 // here we capture aria output exceptions
313 vector<string> ariaExceptions;
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;
321 for(std::string ariaResponse( aria.receiveLine());
322 ariaResponse.length();
323 ariaResponse = aria.receiveLine())
325 string line = str::trim(ariaResponse);
326 // INT << line << endl;
328 // look for the progress line and save parsed values until we find
329 // a string with FILE: later.
330 if ( str::hasPrefix(line, "[#") )
332 str::smatch progressValues;
333 if (( gotProgress = str::regex_match(line, progressValues, rxProgress) ))
335 // INT << "got: progress: '" << progressValues[3]
336 // << "' speed: '" << progressValues[4] << " "
337 // << progressValues[5] << "Bs'" << endl;
339 // get the percentage (progress) data
340 progress = std::atoi(progressValues[3].c_str());
344 int factor = 1; // B/KiB/MiB multiplication factor
345 if (progressValues[5] == "Ki")
347 else if (progressValues[5] == "Mi")
349 else if (progressValues[5] == "Ti")
353 current_speed = boost::lexical_cast<double>(progressValues[4]);
354 // convert to and work with bytes everywhere (bnc #537870)
355 current_speed *= factor;
357 catch (const std::exception&) {
358 ERR << "Can't parse speed from '" << progressValues[4] << "'" << endl;
363 ERR << "Can't parse progress line '" << line << "'" << endl;
365 // save error messages for later
366 else if ( str::hasPrefix(line, "Exception: ") )
368 // for auth exception, we throw
369 if (!line.substr(0,31).compare("Exception: Authorization failed") )
371 ZYPP_THROW(MediaUnauthorizedException(
372 _url, "Login failed.", "Login failed", "auth hint"
375 // otherwise, remember the error
376 string excpMsg = line.substr(10, line.size());
377 DBG << "aria2c reported: '" << excpMsg << "'" << endl;
378 ariaExceptions.push_back(excpMsg);
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: ") )
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() )
393 // once we find the FILE: line, progress has to be
397 // we have a new average speed
398 average_speed_count++;
400 // this is basically A: average
401 // ((n-1)A(n-1) + Xn)/n = A(n)
403 (((average_speed_count - 1)*average_speed) + current_speed)
404 / average_speed_count;
406 if (!partialDownload && progress > 0)
407 partialDownload = true;
409 if ( ! report->progress ( progress, fileurl, average_speed, current_speed ) )
412 // clear the flag to detect mismatches between [# and FILE: lines
417 WAR << "aria2c reported a file, but no progress data available" << endl;
422 DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
427 // other line type, just ignore it.
443 if ( ! PathInfo( target ).isExist() )
445 // bnc #564816: aria2 might return 0 if an error occurred
446 // _before_ the download actually started.
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() ) );
452 MediaException e( msg );
453 for_( it, ariaExceptions.begin(), ariaExceptions.end() )
462 MediaTimeoutException e(_url);
463 for_(it, ariaExceptions.begin(), ariaExceptions.end())
470 case 4: // max notfound reached
472 MediaFileNotFoundException e(_url, filename);
473 for_(it, ariaExceptions.begin(), ariaExceptions.end())
480 case 6: // network problem
481 case 7: // unfinished downloads (ctr-c)
485 if ( partialDownload )
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())
493 DownloadProgressReport::Action action = report->problem( _url, DownloadProgressReport::ERROR, e.asUserHistory() );
494 if ( action == DownloadProgressReport::RETRY )
503 msg = _("Download interrupted by user");
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());
509 MediaException e(msg);
510 for_(it, ariaExceptions.begin(), ariaExceptions.end())
520 // retry with proper authentication data
521 catch (MediaUnauthorizedException & ex_r)
523 if(authenticate(ex_r.hint(), !retry))
527 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
532 // unexpected exception
533 catch (MediaException & excpt_r)
535 // FIXME: error number fix
536 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
537 ZYPP_RETHROW(excpt_r);
542 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
545 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
547 return MediaCurl::getDoesFileExist(filename);
550 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
552 return MediaCurl::doGetDoesFileExist(filename);
555 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
557 MediaCurl::getDir(dirname, recurse_r);
560 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
565 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
566 const Pathname & dirname, bool dots ) const
568 getDirectoryYast( retlist, dirname, dots );
571 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
572 const Pathname & dirname, bool dots ) const
574 getDirectoryYast( retlist, dirname, dots );
577 std::string MediaAria2c::getAria2cVersion()
579 static const char* argv[] =
585 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
586 std::string vResponse( str::trim( aria.receiveLine() ) );