libzypp  10.5.0
MediaAria2c.cc
Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00013 #include <iostream>
00014 #include <list>
00015 #include <vector>
00016 #include <fstream>
00017 #include <boost/lexical_cast.hpp>
00018 
00019 #include "zypp/base/Logger.h"
00020 #include "zypp/base/Regex.h"
00021 #include "zypp/ExternalProgram.h"
00022 #include "zypp/ProgressData.h"
00023 #include "zypp/base/String.h"
00024 #include "zypp/base/Gettext.h"
00025 #include "zypp/base/Sysconfig.h"
00026 #include "zypp/base/Gettext.h"
00027 #include "zypp/ZYppCallbacks.h"
00028 
00029 #include "zypp/Edition.h"
00030 #include "zypp/Target.h"
00031 #include "zypp/ZYppFactory.h"
00032 #include "zypp/ZConfig.h"
00033 
00034 #include "zypp/TmpPath.h"
00035 
00036 #include "zypp/media/MediaAria2c.h"
00037 #include "zypp/media/proxyinfo/ProxyInfos.h"
00038 #include "zypp/media/ProxyInfo.h"
00039 #include "zypp/media/MediaUserAuth.h"
00040 #include "zypp/media/MediaCurl.h"
00041 #include "zypp/thread/Once.h"
00042 #include <cstdlib>
00043 #include <sys/types.h>
00044 #include <sys/stat.h>
00045 #include <sys/mount.h>
00046 #include <errno.h>
00047 #include <dirent.h>
00048 #include <unistd.h>
00049 #include <boost/format.hpp>
00050 
00051 #define  DETECT_DIR_INDEX       0
00052 #define  CONNECT_TIMEOUT        60
00053 #define  TRANSFER_TIMEOUT       60 * 3
00054 #define  TRANSFER_TIMEOUT_MAX   60 * 60
00055 
00056 #define  ARIA_BINARY "aria2c"
00057 
00058 using namespace std;
00059 using namespace zypp::base;
00060 
00061 namespace zypp
00062 {
00063 namespace media
00064 {
00065 
00066 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
00067 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
00068 
00069 //check if aria2c is present in the system
00070 bool
00071 MediaAria2c::existsAria2cmd()
00072 {
00073   static const char* argv[] =
00074   {
00075     ARIA_BINARY,
00076     "--version",
00077     NULL
00078   };
00079   ExternalProgram aria( argv, ExternalProgram::Stderr_To_Stdout );
00080   return( aria.close() == 0 );
00081 }
00082 
00088 void fillAriaCmdLine( const string &ariaver,
00089                       const TransferSettings &s,
00090                       filesystem::TmpPath &credentials,
00091                       const Url &url,
00092                       const Pathname &destination,
00093                       ExternalProgram::Arguments &args )
00094 {
00095 
00096     // options that are not passed in the command line
00097     // like credentials, every string is in the
00098     // opt=val format
00099     list<string> file_options;
00100 
00101     args.push_back(ARIA_BINARY);
00102     args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
00103     args.push_back("--summary-interval=1");
00104     args.push_back("--follow-metalink=mem");
00105     args.push_back("--check-integrity=true");
00106     args.push_back("--file-allocation=none");
00107 
00108     // save the stats of the mirrors and use them as input later
00109     Pathname statsFile = ZConfig::instance().repoCachePath() / "aria2.stats";
00110     args.push_back(str::form("--server-stat-of=%s", statsFile.c_str()));
00111     args.push_back(str::form("--server-stat-if=%s", statsFile.c_str()));
00112     args.push_back("--uri-selector=adaptive");
00113 
00114     // only present in recent aria lets find out the aria version
00115     vector<string> fields;
00116     // "aria2c version x.x"
00117     str::split( ariaver, std::back_inserter(fields));
00118     if ( fields.size() == 3 )
00119     {
00120         if ( Edition(fields[2]) >= Edition("1.1.2") )
00121             args.push_back( "--use-head=false");
00122     }
00123 
00124     if ( s.maxDownloadSpeed() > 0 )
00125         args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
00126     if ( s.minDownloadSpeed() > 0 )
00127         args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
00128 
00129     args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
00130 
00131     if ( Edition(fields[2]) < Edition("1.2.0") )
00132         WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
00133 
00134     // TODO make this one configurable
00135     args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
00136 
00137     // add the anonymous id.
00138     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
00139           it != s.headersEnd();
00140           ++it )
00141         args.push_back(str::form("--header=%s", it->c_str() ));
00142 
00143     args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
00144 
00145     if ( s.username().empty() )
00146     {
00147         if ( url.getScheme() == "ftp" )
00148         {
00149             // set anonymous ftp
00150             args.push_back(str::form("--ftp-user=%s", "suseuser" ));
00151             args.push_back(str::form("--ftp-passwd=%s", VERSION ));
00152 
00153             string id = "yast2";
00154             id += VERSION;
00155             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
00156         }
00157     }
00158     else
00159     {
00160         MIL << "Passing " << url.getScheme() << " credentials '" << s.username() << ':' << (s.password().empty() ? "" : "PASSWORD")<< "'" << endl;
00161         if ( url.getScheme() == "ftp" )
00162             file_options.push_back(str::form("ftp-user=%s", s.username().c_str() ));
00163         else if ( url.getScheme() == "http" ||
00164                   url.getScheme() == "https" )
00165             file_options.push_back(str::form("http-user=%s", s.username().c_str() ));
00166 
00167         if ( s.password().size() )
00168         {
00169             if ( url.getScheme() == "ftp" )
00170                 file_options.push_back(str::form("ftp-passwd=%s", s.password().c_str() ));
00171             else if ( url.getScheme() == "http" ||
00172                       url.getScheme() == "https" )
00173                 file_options.push_back(str::form("http-passwd=%s", s.password().c_str() ));
00174         }
00175     }
00176 
00177     if ( s.proxyEnabled() )
00178     {
00179         args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
00180         if ( ! s.proxyUsername().empty() )
00181         {
00182             MIL << "Passing " << /*url.getScheme()*/"http" << "-proxy credentials '" << s.proxyUsername() << ':' << (s.proxyPassword().empty() ? "" : "PASSWORD")<< "'" << endl;
00183             file_options.push_back(str::form("http-proxy-user=%s", s.proxyUsername().c_str() ));
00184             if ( ! s.proxyPassword().empty() )
00185                 file_options.push_back(str::form("http-proxy-passwd=%s", s.proxyPassword().c_str() ));
00186         }
00187     }
00188 
00189     if ( ! destination.empty() )
00190         args.push_back(str::form("--dir=%s", destination.c_str()));
00191 
00192     // now append the file if there are hidden options
00193     if ( ! file_options.empty() )
00194     {
00195         filesystem::TmpFile tmp;
00196         ofstream outs( tmp.path().c_str() );
00197         for_( it, file_options.begin(), file_options.end() )
00198             outs << *it << endl;
00199         outs.close();
00200 
00201         credentials = tmp;
00202         args.push_back(str::form("--conf-path=%s", credentials.path().c_str()));
00203     }
00204 
00205     // Credentials are passed via --{ftp,http}-{user,passwd}.
00206     // Aria does not like them being repeated in the url. (bnc #544634)
00207     args.push_back(url.asString( url.getViewOptions()
00208                                - url::ViewOptions::WITH_USERNAME
00209                                - url::ViewOptions::WITH_PASSWORD ).c_str());
00210 }
00211 
00212 const char *const MediaAria2c::agentString()
00213 {
00214   // we need to add the release and identifier to the
00215   // agent string.
00216   // The target could be not initialized, and then this information
00217   // is not available.
00218   Target_Ptr target = zypp::getZYpp()->getTarget();
00219 
00220   static const std::string _value(
00221     str::form(
00222        "ZYpp %s (%s) %s"
00223        , VERSION
00224        , MediaAria2c::_aria2cVersion.c_str()
00225        , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
00226     )
00227   );
00228   return _value.c_str();
00229 }
00230 
00231 
00232 
00233 MediaAria2c::MediaAria2c( const Url &      url_r,
00234                       const Pathname & attach_point_hint_r )
00235     : MediaCurl( url_r, attach_point_hint_r )
00236 {
00237   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
00238   //Get aria2c version
00239   _aria2cVersion = getAria2cVersion();
00240 }
00241 
00242 void MediaAria2c::attachTo (bool next)
00243 {
00244   MediaCurl::attachTo(next);
00245   _settings.setUserAgentString(agentString());
00246 }
00247 
00248 bool
00249 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
00250 {
00251     return MediaCurl::checkAttachPoint( apoint );
00252 }
00253 
00254 void MediaAria2c::disconnectFrom()
00255 {
00256     MediaCurl::disconnectFrom();
00257 }
00258 
00259 void MediaAria2c::releaseFrom( const std::string & ejectDev )
00260 {
00261   MediaCurl::releaseFrom(ejectDev);
00262 }
00263 
00264 void MediaAria2c::getFile( const Pathname & filename ) const
00265 {
00266     // Use absolute file name to prevent access of files outside of the
00267     // hierarchy below the attach point.
00268     getFileCopy(filename, localPath(filename).absolutename());
00269 }
00270 
00271 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
00272 {
00273   callback::SendReport<DownloadProgressReport> report;
00274 
00275   Url fileurl(getFileUrl(filename));
00276 
00277   bool retry = false;
00278 
00279   ExternalProgram::Arguments args;
00280 
00281   filesystem::TmpPath credentials;
00282   fillAriaCmdLine(_aria2cVersion, _settings, credentials, fileurl, target.dirname(), args);
00283 
00284   do
00285   {
00286     try
00287     {
00288       report->start(fileurl, target.asString() );
00289 
00290       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
00291 
00292       // extended regex for parsing of progress lines
00293       // progress line like: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:6899.88KiB/s]
00294       // but since 1.4.0:    [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:899.8KiBs]
00295       //       (bnc #513944) [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:3.8MiBs]
00296       //                     [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs]
00297       // later got also ETA: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs ETA:02s]
00298       static str::regex rxProgress(
00299           "^\\[#[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.*\\]$");
00300 
00301       // whether we received correct progress line before corresponding FILE line
00302       bool gotProgress = false;
00303       // download progress in %
00304       int progress = 0;
00305       // current download speed in bytes
00306       double current_speed = 0;
00307       // download speed in bytes
00308       double average_speed = 0;
00309       // number of speed measurements
00310       long average_speed_count = 0;
00311 
00312       // here we capture aria output exceptions
00313       vector<string> ariaExceptions;
00314 
00315       // whether it makes sense to retry with --continue
00316       bool partialDownload = false;
00317       // whether user request abort of the download
00318       bool userAbort = false;
00319 
00320       //Process response
00321       for(std::string ariaResponse( aria.receiveLine());
00322           ariaResponse.length();
00323           ariaResponse = aria.receiveLine())
00324       {
00325         string line = str::trim(ariaResponse);
00326         // INT << line << endl;
00327 
00328         // look for the progress line and save parsed values until we find
00329         // a string with FILE: later.
00330         if ( str::hasPrefix(line, "[#") )
00331         {
00332           str::smatch progressValues;
00333           if (( gotProgress = str::regex_match(line, progressValues, rxProgress) ))
00334           {
00335             // INT << "got: progress: '" << progressValues[3]
00336             //     << "' speed: '" << progressValues[4] << " "
00337             //     << progressValues[5] << "Bs'" << endl;
00338 
00339             // get the percentage (progress) data
00340             progress = std::atoi(progressValues[3].c_str());
00341 
00342             // get the speed
00343 
00344             int factor = 1; // B/KiB/MiB multiplication factor
00345             if (progressValues[5] == "Ki")
00346               factor = 1024;
00347             else if (progressValues[5] == "Mi")
00348               factor = 0x100000;
00349             else if (progressValues[5] == "Ti")
00350               factor = 0x40000000;
00351 
00352             try {
00353               current_speed = boost::lexical_cast<double>(progressValues[4]);
00354               // convert to and work with bytes everywhere (bnc #537870)
00355               current_speed *= factor;
00356             }
00357             catch (const std::exception&) {
00358               ERR << "Can't parse speed from '" << progressValues[4] << "'" << endl;
00359               current_speed = 0;
00360             }
00361           }
00362           else
00363             ERR << "Can't parse progress line '" << line << "'" << endl;
00364         }
00365         // save error messages for later
00366         else if ( str::hasPrefix(line, "Exception: ") )
00367         {
00368           // for auth exception, we throw
00369           if (!line.substr(0,31).compare("Exception: Authorization failed") )
00370           {
00371             ZYPP_THROW(MediaUnauthorizedException(
00372                        _url, "Login failed.", "Login failed", "auth hint"
00373             ));
00374           }
00375           // otherwise, remember the error
00376           string excpMsg = line.substr(10, line.size());
00377           DBG << "aria2c reported: '" << excpMsg << "'" << endl;
00378           ariaExceptions.push_back(excpMsg);
00379         }
00380         // The file line tells which file corresponds to the previous progress,
00381         // eg.: FILE: ./packages.FL.gz
00382         else if ( str::hasPrefix(line, "FILE: ") )
00383         {
00384           // get the FILE name
00385           string theFile(line.substr(6, line.size()));
00386           // is the report about the filename we are downloading?
00387           // aria may report progress about metalinks, torrent and
00388           // other stuff which is not the main transfer
00389           // the reported file is the url before the server emits a response
00390           // and then is reported as the target file
00391           if ( Pathname(theFile) == target || theFile == fileurl.asCompleteString() )
00392           {
00393             // once we find the FILE: line, progress has to be
00394             // non empty
00395             if ( gotProgress )
00396             {
00397               // we have a new average speed
00398               average_speed_count++;
00399 
00400               // this is basically A: average
00401               // ((n-1)A(n-1) + Xn)/n = A(n)
00402               average_speed =
00403                 (((average_speed_count - 1)*average_speed) + current_speed)
00404                 / average_speed_count;
00405 
00406               if (!partialDownload && progress > 0)
00407                 partialDownload = true;
00408 
00409               if ( ! report->progress ( progress, fileurl, average_speed, current_speed ) )
00410                 userAbort = true;
00411 
00412               // clear the flag to detect mismatches between [# and FILE: lines
00413               gotProgress = false;
00414             }
00415             else
00416             {
00417               WAR << "aria2c reported a file, but no progress data available" << endl;
00418             }
00419           }
00420           else
00421           {
00422             DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
00423           }
00424         }
00425         else
00426         {
00427             // other line type, just ignore it.
00428         }
00429       }
00430 
00431       int code;
00432       if (userAbort)
00433       {
00434         aria.kill();
00435         code = 7;
00436       }
00437       else
00438         code = aria.close();
00439 
00440       switch ( code )
00441       {
00442         case 0: // success?
00443           if ( ! PathInfo( target ).isExist() )
00444           {
00445             // bnc #564816: aria2 might return 0 if an error occurred
00446             // _before_ the download actually started.
00447 
00448             // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
00449             std::string msg( str::form(_("Failed to download %s from %s"),
00450                              filename.c_str(), _url.asString().c_str() ) );
00451 
00452             MediaException e( msg );
00453             for_( it, ariaExceptions.begin(), ariaExceptions.end() )
00454               e.addHistory( *it );
00455 
00456             ZYPP_THROW( e );
00457           }
00458           break;
00459 
00460         case 2: // timeout
00461         {
00462           MediaTimeoutException e(_url);
00463           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00464               e.addHistory(*it);
00465           ZYPP_THROW(e);
00466         }
00467         break;
00468 
00469         case 3: // not found
00470         case 4: // max notfound reached
00471         {
00472           MediaFileNotFoundException e(_url, filename);
00473           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00474               e.addHistory(*it);
00475           ZYPP_THROW(e);
00476         }
00477         break;
00478 
00479         case 5: // too slow
00480         case 6: // network problem
00481         case 7: // unfinished downloads (ctr-c)
00482         case 1: // unknown
00483         default:
00484         {
00485           if ( partialDownload )
00486           {
00487             // Ask for retry on partial downloads, when it makes sense to retry with --continue!
00488             // Other errors are handled by the layers above.
00489             MediaException e(str::form(_("Download interrupted at %d%%"), progress ));
00490             for_(it, ariaExceptions.begin(), ariaExceptions.end())
00491               e.addHistory(*it);
00492 
00493             DownloadProgressReport::Action action = report->problem( _url, DownloadProgressReport::ERROR, e.asUserHistory() );
00494             if ( action == DownloadProgressReport::RETRY )
00495             {
00496               retry = true;
00497               continue;
00498             }
00499           }
00500 
00501           string msg;
00502           if (userAbort)
00503             msg = _("Download interrupted by user");
00504           else
00505             // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
00506             msg = str::form(_("Failed to download %s from %s"),
00507                 filename.c_str(), _url.asString().c_str());
00508 
00509           MediaException e(msg);
00510           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00511               e.addHistory(*it);
00512 
00513           ZYPP_THROW(e);
00514         }
00515         break;
00516       }
00517 
00518       retry = false;
00519     }
00520     // retry with proper authentication data
00521     catch (MediaUnauthorizedException & ex_r)
00522     {
00523       if(authenticate(ex_r.hint(), !retry))
00524         retry = true;
00525       else
00526       {
00527         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
00528         ZYPP_RETHROW(ex_r);
00529       }
00530 
00531     }
00532     // unexpected exception
00533     catch (MediaException & excpt_r)
00534     {
00535       // FIXME: error number fix
00536       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
00537       ZYPP_RETHROW(excpt_r);
00538     }
00539   }
00540   while (retry);
00541 
00542   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
00543 }
00544 
00545 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
00546 {
00547     return MediaCurl::getDoesFileExist(filename);
00548 }
00549 
00550 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
00551 {
00552     return MediaCurl::doGetDoesFileExist(filename);
00553 }
00554 
00555 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
00556 {
00557     MediaCurl::getDir(dirname, recurse_r);
00558 }
00559 
00560 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
00561 {
00562     return false;
00563 }
00564 
00565 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
00566                                const Pathname & dirname, bool dots ) const
00567 {
00568   getDirectoryYast( retlist, dirname, dots );
00569 }
00570 
00571 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
00572                             const Pathname & dirname, bool dots ) const
00573 {
00574   getDirectoryYast( retlist, dirname, dots );
00575 }
00576 
00577 std::string MediaAria2c::getAria2cVersion()
00578 {
00579     static const char* argv[] =
00580     {
00581       ARIA_BINARY,
00582       "--version",
00583       NULL
00584     };
00585     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
00586     std::string vResponse( str::trim( aria.receiveLine() ) );
00587     aria.close();
00588     return vResponse;
00589 }
00590 } // namespace media
00591 } // namespace zypp
00592 //