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 static Url getFileUrl(const Url & url, const Pathname & filename)
00265 {
00266   Url newurl(url);
00267   string path = url.getPathName();
00268   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00269        filename.absolute() )
00270   {
00271     // If url has a path with trailing slash, remove the leading slash from
00272     // the absolute file name
00273     path += filename.asString().substr( 1, filename.asString().size() - 1 );
00274   }
00275   else if ( filename.relative() )
00276   {
00277     // Add trailing slash to path, if not already there
00278     if (path.empty()) path = "/";
00279     else if (*path.rbegin() != '/' ) path += "/";
00280     // Remove "./" from begin of relative file name
00281     path += filename.asString().substr( 2, filename.asString().size() - 2 );
00282   }
00283   else
00284   {
00285     path += filename.asString();
00286   }
00287 
00288   newurl.setPathName(path);
00289   return newurl;
00290 }
00291 
00292 void MediaAria2c::getFile( const Pathname & filename ) const
00293 {
00294     // Use absolute file name to prevent access of files outside of the
00295     // hierarchy below the attach point.
00296     getFileCopy(filename, localPath(filename).absolutename());
00297 }
00298 
00299 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
00300 {
00301   callback::SendReport<DownloadProgressReport> report;
00302 
00303   Url fileurl(getFileUrl(_url, filename));
00304 
00305   bool retry = false;
00306 
00307   ExternalProgram::Arguments args;
00308 
00309   filesystem::TmpPath credentials;
00310   fillAriaCmdLine(_aria2cVersion, _settings, credentials, fileurl, target.dirname(), args);
00311 
00312   do
00313   {
00314     try
00315     {
00316       report->start(fileurl, target.asString() );
00317 
00318       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
00319 
00320       // extended regex for parsing of progress lines
00321       // progress line like: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:6899.88KiB/s]
00322       // but since 1.4.0:    [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:899.8KiBs]
00323       //       (bnc #513944) [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:3.8MiBs]
00324       //                     [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs]
00325       // later got also ETA: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:38Bs ETA:02s]
00326       static str::regex rxProgress(
00327           "^\\[#[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.*\\]$");
00328 
00329       // whether we received correct progress line before corresponding FILE line
00330       bool gotProgress = false;
00331       // download progress in %
00332       int progress = 0;
00333       // current download speed in bytes
00334       double current_speed = 0;
00335       // download speed in bytes
00336       double average_speed = 0;
00337       // number of speed measurements
00338       long average_speed_count = 0;
00339 
00340       // here we capture aria output exceptions
00341       vector<string> ariaExceptions;
00342 
00343       // whether it makes sense to retry with --continue
00344       bool partialDownload = false;
00345       // whether user request abort of the download
00346       bool userAbort = false;
00347 
00348       //Process response
00349       for(std::string ariaResponse( aria.receiveLine());
00350           ariaResponse.length();
00351           ariaResponse = aria.receiveLine())
00352       {
00353         string line = str::trim(ariaResponse);
00354         // INT << line << endl;
00355 
00356         // look for the progress line and save parsed values until we find
00357         // a string with FILE: later.
00358         if ( str::hasPrefix(line, "[#") )
00359         {
00360           str::smatch progressValues;
00361           if (( gotProgress = str::regex_match(line, progressValues, rxProgress) ))
00362           {
00363             // INT << "got: progress: '" << progressValues[3]
00364             //     << "' speed: '" << progressValues[4] << " "
00365             //     << progressValues[5] << "Bs'" << endl;
00366 
00367             // get the percentage (progress) data
00368             progress = std::atoi(progressValues[3].c_str());
00369 
00370             // get the speed
00371 
00372             int factor = 1; // B/KiB/MiB multiplication factor
00373             if (progressValues[5] == "Ki")
00374               factor = 1024;
00375             else if (progressValues[5] == "Mi")
00376               factor = 0x100000;
00377             else if (progressValues[5] == "Ti")
00378               factor = 0x40000000;
00379 
00380             try {
00381               current_speed = boost::lexical_cast<double>(progressValues[4]);
00382               // convert to and work with bytes everywhere (bnc #537870)
00383               current_speed *= factor;
00384             }
00385             catch (const std::exception&) {
00386               ERR << "Can't parse speed from '" << progressValues[4] << "'" << endl;
00387               current_speed = 0;
00388             }
00389           }
00390           else
00391             ERR << "Can't parse progress line '" << line << "'" << endl;
00392         }
00393         // save error messages for later
00394         else if ( str::hasPrefix(line, "Exception: ") )
00395         {
00396           // for auth exception, we throw
00397           if (!line.substr(0,31).compare("Exception: Authorization failed") )
00398           {
00399             ZYPP_THROW(MediaUnauthorizedException(
00400                        _url, "Login failed.", "Login failed", "auth hint"
00401             ));
00402           }
00403           // otherwise, remember the error
00404           string excpMsg = line.substr(10, line.size());
00405           DBG << "aria2c reported: '" << excpMsg << "'" << endl;
00406           ariaExceptions.push_back(excpMsg);
00407         }
00408         // The file line tells which file corresponds to the previous progress,
00409         // eg.: FILE: ./packages.FL.gz
00410         else if ( str::hasPrefix(line, "FILE: ") )
00411         {
00412           // get the FILE name
00413           string theFile(line.substr(6, line.size()));
00414           // is the report about the filename we are downloading?
00415           // aria may report progress about metalinks, torrent and
00416           // other stuff which is not the main transfer
00417           // the reported file is the url before the server emits a response
00418           // and then is reported as the target file
00419           if ( Pathname(theFile) == target || theFile == fileurl.asCompleteString() )
00420           {
00421             // once we find the FILE: line, progress has to be
00422             // non empty
00423             if ( gotProgress )
00424             {
00425               // we have a new average speed
00426               average_speed_count++;
00427 
00428               // this is basically A: average
00429               // ((n-1)A(n-1) + Xn)/n = A(n)
00430               average_speed =
00431                 (((average_speed_count - 1)*average_speed) + current_speed)
00432                 / average_speed_count;
00433 
00434               if (!partialDownload && progress > 0)
00435                 partialDownload = true;
00436 
00437               if ( ! report->progress ( progress, fileurl, average_speed, current_speed ) )
00438                 userAbort = true;
00439 
00440               // clear the flag to detect mismatches between [# and FILE: lines
00441               gotProgress = false;
00442             }
00443             else
00444             {
00445               WAR << "aria2c reported a file, but no progress data available" << endl;
00446             }
00447           }
00448           else
00449           {
00450             DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
00451           }
00452         }
00453         else
00454         {
00455             // other line type, just ignore it.
00456         }
00457       }
00458 
00459       int code;
00460       if (userAbort)
00461       {
00462         aria.kill();
00463         code = 7;
00464       }
00465       else
00466         code = aria.close();
00467 
00468       switch ( code )
00469       {
00470         case 0: // success?
00471           if ( ! PathInfo( target ).isExist() )
00472           {
00473             // bnc #564816: aria2 might return 0 if an error occurred
00474             // _before_ the download actually started.
00475 
00476             // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
00477             std::string msg( str::form(_("Failed to download %s from %s"),
00478                              filename.c_str(), _url.asString().c_str() ) );
00479 
00480             MediaException e( msg );
00481             for_( it, ariaExceptions.begin(), ariaExceptions.end() )
00482               e.addHistory( *it );
00483 
00484             ZYPP_THROW( e );
00485           }
00486           break;
00487 
00488         case 2: // timeout
00489         {
00490           MediaTimeoutException e(_url);
00491           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00492               e.addHistory(*it);
00493           ZYPP_THROW(e);
00494         }
00495         break;
00496 
00497         case 3: // not found
00498         case 4: // max notfound reached
00499         {
00500           MediaFileNotFoundException e(_url, filename);
00501           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00502               e.addHistory(*it);
00503           ZYPP_THROW(e);
00504         }
00505         break;
00506 
00507         case 5: // too slow
00508         case 6: // network problem
00509         case 7: // unfinished downloads (ctr-c)
00510         case 1: // unknown
00511         default:
00512         {
00513           if ( partialDownload )
00514           {
00515             // Ask for retry on partial downloads, when it makes sense to retry with --continue!
00516             // Other errors are handled by the layers above.
00517             MediaException e(str::form(_("Download interrupted at %d%%"), progress ));
00518             for_(it, ariaExceptions.begin(), ariaExceptions.end())
00519               e.addHistory(*it);
00520 
00521             DownloadProgressReport::Action action = report->problem( _url, DownloadProgressReport::ERROR, e.asUserHistory() );
00522             if ( action == DownloadProgressReport::RETRY )
00523             {
00524               retry = true;
00525               continue;
00526             }
00527           }
00528 
00529           string msg;
00530           if (userAbort)
00531             msg = _("Download interrupted by user");
00532           else
00533             // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
00534             msg = str::form(_("Failed to download %s from %s"),
00535                 filename.c_str(), _url.asString().c_str());
00536 
00537           MediaException e(msg);
00538           for_(it, ariaExceptions.begin(), ariaExceptions.end())
00539               e.addHistory(*it);
00540 
00541           ZYPP_THROW(e);
00542         }
00543         break;
00544       }
00545 
00546       retry = false;
00547     }
00548     // retry with proper authentication data
00549     catch (MediaUnauthorizedException & ex_r)
00550     {
00551       if(authenticate(ex_r.hint(), !retry))
00552         retry = true;
00553       else
00554       {
00555         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
00556         ZYPP_RETHROW(ex_r);
00557       }
00558 
00559     }
00560     // unexpected exception
00561     catch (MediaException & excpt_r)
00562     {
00563       // FIXME: error number fix
00564       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
00565       ZYPP_RETHROW(excpt_r);
00566     }
00567   }
00568   while (retry);
00569 
00570   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
00571 }
00572 
00573 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
00574 {
00575     return MediaCurl::getDoesFileExist(filename);
00576 }
00577 
00578 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
00579 {
00580     return MediaCurl::doGetDoesFileExist(filename);
00581 }
00582 
00583 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
00584 {
00585     MediaCurl::getDir(dirname, recurse_r);
00586 }
00587 
00588 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
00589 {
00590     return false;
00591 }
00592 
00593 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
00594                                const Pathname & dirname, bool dots ) const
00595 {
00596   getDirectoryYast( retlist, dirname, dots );
00597 }
00598 
00599 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
00600                             const Pathname & dirname, bool dots ) const
00601 {
00602   getDirectoryYast( retlist, dirname, dots );
00603 }
00604 
00605 std::string MediaAria2c::getAria2cVersion()
00606 {
00607     static const char* argv[] =
00608     {
00609       ARIA_BINARY,
00610       "--version",
00611       NULL
00612     };
00613     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
00614     std::string vResponse( str::trim( aria.receiveLine() ) );
00615     aria.close();
00616     return vResponse;
00617 }
00618 } // namespace media
00619 } // namespace zypp
00620 //
Generated on Fri Mar 2 09:45:52 2012 for libzypp by  doxygen 1.6.3