Imported Upstream version 16.3.2
[platform/upstream/libzypp.git] / zypp / media / MediaCurl.cc
index d1db7f9..4859bcf 100644 (file)
@@ -156,101 +156,25 @@ namespace zypp {
   namespace {
     struct ProgressData
     {
-      ProgressData( CURL *_curl, time_t _timeout = 0, const Url & _url = Url(),
-                    ByteCount expectedFileSize_r = 0,
-                   callback::SendReport<DownloadProgressReport> *_report = nullptr )
-        : curl( _curl )
-       , url( _url )
-       , timeout( _timeout )
-        , reached( false )
-        , fileSizeExceeded ( false )
-        , report( _report )
-        , _expectedFileSize( expectedFileSize_r )
+      ProgressData(CURL *_curl, const long _timeout, const zypp::Url &_url = zypp::Url(),
+                   callback::SendReport<DownloadProgressReport> *_report=NULL)
+        : curl(_curl)
+        , timeout(_timeout)
+        , reached(false)
+        , report(_report)
+        , drate_period(-1)
+        , dload_period(0)
+        , secs(0)
+        , drate_avg(-1)
+        , ltime( time(NULL))
+        , dload( 0)
+        , uload( 0)
+        , url(_url)
       {}
-
-      CURL     *curl;
-      Url      url;
-      time_t   timeout;
-      bool     reached;
-      bool      fileSizeExceeded;
+      CURL                                         *curl;
+      long                                          timeout;
+      bool                                          reached;
       callback::SendReport<DownloadProgressReport> *report;
-      ByteCount _expectedFileSize;
-
-      time_t _timeStart        = 0;    ///< Start total stats
-      time_t _timeLast = 0;    ///< Start last period(~1sec)
-      time_t _timeRcv  = 0;    ///< Start of no-data timeout
-      time_t _timeNow  = 0;    ///< Now
-
-      double _dnlTotal = 0.0;  ///< Bytes to download or 0 if unknown
-      double _dnlLast  = 0.0;  ///< Bytes downloaded at period start
-      double _dnlNow   = 0.0;  ///< Bytes downloaded now
-
-      int    _dnlPercent= 0;   ///< Percent completed or 0 if _dnlTotal is unknown
-
-      double _drateTotal= 0.0; ///< Download rate so far
-      double _drateLast        = 0.0;  ///< Download rate in last period
-
-      void updateStats( double dltotal = 0.0, double dlnow = 0.0 )
-      {
-       time_t now = _timeNow = time(0);
-
-       // If called without args (0.0), recompute based on the last values seen
-       if ( dltotal && dltotal != _dnlTotal )
-         _dnlTotal = dltotal;
-
-       if ( dlnow && dlnow != _dnlNow )
-       {
-         _timeRcv = now;
-         _dnlNow = dlnow;
-       }
-       else if ( !_dnlNow && !_dnlTotal )
-       {
-         // Start time counting as soon as first data arrives.
-         // Skip the connection / redirection time at begin.
-         return;
-       }
-
-       // init or reset if time jumps back
-       if ( !_timeStart || _timeStart > now )
-         _timeStart = _timeLast = _timeRcv = now;
-
-       // timeout condition
-       if ( timeout )
-         reached = ( (now - _timeRcv) > timeout );
-
-        // check if the downloaded data is already bigger than what we expected
-       fileSizeExceeded = _expectedFileSize > 0 && _expectedFileSize < static_cast<ByteCount::SizeType>(_dnlNow);
-
-       // percentage:
-       if ( _dnlTotal )
-         _dnlPercent = int(_dnlNow * 100 / _dnlTotal);
-
-       // download rates:
-       _drateTotal = _dnlNow / std::max( int(now - _timeStart), 1 );
-
-       if ( _timeLast < now )
-       {
-         _drateLast = (_dnlNow - _dnlLast) / int(now - _timeLast);
-         // start new period
-         _timeLast  = now;
-         _dnlLast   = _dnlNow;
-       }
-       else if ( _timeStart == _timeLast )
-         _drateLast = _drateTotal;
-      }
-
-      int reportProgress() const
-      {
-        if ( fileSizeExceeded )
-          return 1;
-       if ( reached )
-         return 1;     // no-data timeout
-       if ( report && !(*report)->progress( _dnlPercent, url, _drateTotal, _drateLast ) )
-         return 1;     // user requested abort
-       return 0;
-      }
-
-
       // download rate of the last period (cca 1 sec)
       double                                        drate_period;
       // bytes downloaded at the start of the last period
@@ -265,6 +189,7 @@ namespace zypp {
       double                                        dload;
       // bytes uploaded at the moment the progress was last reported
       double                                        uload;
+      zypp::Url                                     url;
     };
 
     ///////////////////////////////////////////////////////////////////
@@ -867,14 +792,9 @@ void MediaCurl::attachTo (bool next)
     ZYPP_THROW(MediaBadUrlException(_url));
 
   checkProtocol(_url);
-  if( !isUseableAttachPoint(attachPoint()))
+  if( !isUseableAttachPoint( attachPoint() ) )
   {
-    std::string mountpoint = createAttachPoint().asString();
-
-    if( mountpoint.empty())
-      ZYPP_THROW( MediaBadAttachPointException(url()));
-
-    setAttachPoint( mountpoint, true);
+    setAttachPoint( createAttachPoint(), true );
   }
 
   disconnectFrom(); // clean _curl if needed
@@ -943,16 +863,16 @@ Url MediaCurl::getFileUrl( const Pathname & filename_r ) const
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
+void MediaCurl::getFile( const Pathname & filename ) const
 {
     // Use absolute file name to prevent access of files outside of the
     // hierarchy below the attach point.
-    getFileCopy(filename, localPath(filename).absolutename(), expectedFileSize_r);
+    getFileCopy(filename, localPath(filename).absolutename());
 }
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target, const ByteCount &expectedFileSize_r ) const
+void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
 {
   callback::SendReport<DownloadProgressReport> report;
 
@@ -964,7 +884,7 @@ void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target
   {
     try
     {
-      doGetFileCopy(filename, target, report, expectedFileSize_r);
+      doGetFileCopy(filename, target, report);
       retry = false;
     }
     // retry with proper authentication data
@@ -1024,9 +944,9 @@ bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::evaluateCurlCode(const Pathname &filename,
+void MediaCurl::evaluateCurlCode( const Pathname &filename,
                                   CURLcode code,
-                                  bool timeout_reached) const
+                                  bool timeout_reached ) const
 {
   if ( code != 0 )
   {
@@ -1035,7 +955,6 @@ void MediaCurl::evaluateCurlCode(const Pathname &filename,
       url = _url;
     else
       url = getFileUrl(filename);
-
     std::string err;
     try
     {
@@ -1073,7 +992,6 @@ void MediaCurl::evaluateCurlCode(const Pathname &filename,
                            ));
           }
 
-          case 502: // bad gateway (bnc #1070851)
           case 503: // service temporarily unavailable (bnc #462545)
             ZYPP_THROW(MediaTemporaryProblemException(url));
           case 504: // gateway timeout
@@ -1086,7 +1004,6 @@ void MediaCurl::evaluateCurlCode(const Pathname &filename,
             ZYPP_THROW(MediaForbiddenException(url, msg403));
           }
           case 404:
-          case 410:
               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
           }
 
@@ -1217,7 +1134,7 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
   }
 
-  AutoFILE file { ::fopen( "/dev/null", "w" ) };
+  FILE *file = ::fopen( "/dev/null", "w" );
   if ( !file ) {
       ERR << "fopen failed for /dev/null" << endl;
       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
@@ -1234,8 +1151,9 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
       ZYPP_THROW(MediaWriteException("/dev/null"));
   }
 
-  ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, (*file) );
+  ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
   if ( ret != 0 ) {
+      ::fclose(file);
       std::string err( _curlError);
       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
@@ -1282,6 +1200,10 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
     }
   }
 
+  // if the code is not zero, close the file
+  if ( ok != 0 )
+      ::fclose(file);
+
   // as we are not having user interaction, the user can't cancel
   // the file existence checking, a callback or timeout return code
   // will be always a timeout.
@@ -1345,40 +1267,40 @@ bool MediaCurl::detectDirIndex() const
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
+void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
 {
     Pathname dest = target.absolutename();
     if( assert_dir( dest.dirname() ) )
     {
       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
-      ZYPP_THROW( MediaSystemException(getFileUrl(filename), "System error on " + dest.dirname().asString()) );
+      Url url(getFileUrl(filename));
+      ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
     }
-
-    ManagedFile destNew { target.extend( ".new.zypp.XXXXXX" ) };
-    AutoFILE file;
+    string destNew = target.asString() + ".new.zypp.XXXXXX";
+    char *buf = ::strdup( destNew.c_str());
+    if( !buf)
     {
-      AutoFREE<char> buf { ::strdup( (*destNew).c_str() ) };
-      if( ! buf )
-      {
-       ERR << "out of memory for temp file name" << endl;
-       ZYPP_THROW(MediaSystemException(getFileUrl(filename), "out of memory for temp file name"));
-      }
-
-      AutoFD tmp_fd { ::mkostemp( buf, O_CLOEXEC ) };
-      if( tmp_fd == -1 )
-      {
-       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
-       ZYPP_THROW(MediaWriteException(destNew));
-      }
-      destNew = ManagedFile( (*buf), filesystem::unlink );
+      ERR << "out of memory for temp file name" << endl;
+      Url url(getFileUrl(filename));
+      ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
+    }
 
-      file = ::fdopen( tmp_fd, "we" );
-      if ( ! file )
-      {
-       ERR << "fopen failed for file '" << destNew << "'" << endl;
-       ZYPP_THROW(MediaWriteException(destNew));
-      }
-      tmp_fd.resetDispose();   // don't close it here! ::fdopen moved ownership to file
+    int tmp_fd = ::mkostemp( buf, O_CLOEXEC );
+    if( tmp_fd == -1)
+    {
+      free( buf);
+      ERR << "mkstemp failed for file '" << destNew << "'" << endl;
+      ZYPP_THROW(MediaWriteException(destNew));
+    }
+    destNew = buf;
+    free( buf);
+
+    FILE *file = ::fdopen( tmp_fd, "we" );
+    if ( !file ) {
+      ::close( tmp_fd);
+      filesystem::unlink( destNew );
+      ERR << "fopen failed for file '" << destNew << "'" << endl;
+      ZYPP_THROW(MediaWriteException(destNew));
     }
 
     DBG << "dest: " << dest << endl;
@@ -1397,10 +1319,12 @@ void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & targe
     }
     try
     {
-      doGetFileCopyFile(filename, dest, file, report, expectedFileSize_r, options);
+      doGetFileCopyFile(filename, dest, file, report, options);
     }
     catch (Exception &e)
     {
+      ::fclose( file );
+      filesystem::unlink( destNew );
       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
       ZYPP_RETHROW(e);
@@ -1434,20 +1358,22 @@ void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & targe
       {
         ERR << "Failed to chmod file " << destNew << endl;
       }
-
-      file.resetDispose();     // we're going to close it manually here
-      if ( ::fclose( file ) )
+      if (::fclose( file ))
       {
         ERR << "Fclose failed for file '" << destNew << "'" << endl;
         ZYPP_THROW(MediaWriteException(destNew));
       }
-
       // move the temp file into dest
       if ( rename( destNew, dest ) != 0 ) {
         ERR << "Rename failed" << endl;
         ZYPP_THROW(MediaWriteException(dest));
       }
-      destNew.resetDispose();  // no more need to unlink it
+    }
+    else
+    {
+      // close and remove the temp file
+      ::fclose( file );
+      filesystem::unlink( destNew );
     }
 
     DBG << "done: " << PathInfo(dest) << endl;
@@ -1455,7 +1381,7 @@ void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & targe
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
+void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
 {
     DBG << filename.asString() << endl;
 
@@ -1495,7 +1421,7 @@ void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & d
     }
 
     // Set callback and perform.
-    ProgressData progressData(_curl, _settings.timeout(), url, expectedFileSize_r, &report);
+    ProgressData progressData(_curl, _settings.timeout(), url, &report);
     if (!(options & OPTION_NO_REPORT_START))
       report->start(url, dest);
     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
@@ -1539,11 +1465,7 @@ void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & d
       // which holds whether the timeout was reached or not,
       // otherwise it would be a user cancel
       try {
-
-        if ( progressData.fileSizeExceeded )
-          ZYPP_THROW(MediaFileSizeExceededException(url, progressData._expectedFileSize));
-
-        evaluateCurlCode( filename, ret, progressData.reached );
+        evaluateCurlCode( filename, ret, progressData.reached);
       }
       catch ( const MediaException &e ) {
         // some error, we are not sure about file existence, rethrw
@@ -1573,7 +1495,7 @@ void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
       switch ( it->type ) {
       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
       case filesystem::FT_FILE:
-        getFile( filename, 0 );
+        getFile( filename );
         break;
       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
         if ( recurse_r ) {
@@ -1609,33 +1531,97 @@ void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
 }
 
 ///////////////////////////////////////////////////////////////////
-//
-int MediaCurl::aliveCallback( void *clientp, double /*dltotal*/, double dlnow, double /*ultotal*/, double /*ulnow*/ )
-{
-  ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
-  if( pdata )
-  {
-    // Do not propagate dltotal in alive callbacks. MultiCurl uses this to
-    // prevent a percentage raise while downloading a metalink file. Download
-    // activity however is indicated by propagating the download rate (via dlnow).
-    pdata->updateStats( 0.0, dlnow );
-    return pdata->reportProgress();
-  }
-  return 0;
-}
 
-int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow )
+int MediaCurl::progressCallback( void *clientp,
+                                 double dltotal, double dlnow,
+                                 double ultotal, double ulnow)
 {
-  ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
-  if( pdata )
+  ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
+  if( pdata)
   {
     // work around curl bug that gives us old data
     long httpReturnCode = 0;
-    if ( curl_easy_getinfo( pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 )
-      return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
+    if (curl_easy_getinfo(pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode) != CURLE_OK || httpReturnCode == 0)
+      return 0;
+
+    time_t now   = time(NULL);
+    if( now > 0)
+    {
+       // reset time of last change in case initial time()
+       // failed or the time was adjusted (goes backward)
+       if( pdata->ltime <= 0 || pdata->ltime > now)
+       {
+         pdata->ltime = now;
+       }
+
+       // start time counting as soon as first data arrives
+       // (skip the connection / redirection time at begin)
+       time_t dif = 0;
+       if (dlnow > 0 || ulnow > 0)
+       {
+         dif = (now - pdata->ltime);
+         dif = dif > 0 ? dif : 0;
+
+         pdata->secs += dif;
+       }
+
+       // update the drate_avg and drate_period only after a second has passed
+       // (this callback is called much more often than a second)
+       // otherwise the values would be far from accurate when measuring
+       // the time in seconds
+       //! \todo more accurate download rate computationn, e.g. compute average value from last 5 seconds, or work with milliseconds instead of seconds
+
+        if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
+          pdata->drate_avg = (dlnow / pdata->secs);
+
+       if ( dif > 0 )
+       {
+         pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
+         pdata->dload_period = dlnow;
+       }
+    }
 
-    pdata->updateStats( dltotal, dlnow );
-    return pdata->reportProgress();
+    // send progress report first, abort transfer if requested
+    if( pdata->report)
+    {
+      if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ),
+                                       pdata->url,
+                                       pdata->drate_avg,
+                                       pdata->drate_period))
+      {
+        return 1; // abort transfer
+      }
+    }
+
+    // check if we there is a timeout set
+    if( pdata->timeout > 0)
+    {
+      if( now > 0)
+      {
+        bool progress = false;
+
+        // update download data if changed, mark progress
+        if( dlnow != pdata->dload)
+        {
+          progress     = true;
+          pdata->dload = dlnow;
+          pdata->ltime = now;
+        }
+        // update upload data if changed, mark progress
+        if( ulnow != pdata->uload)
+        {
+          progress     = true;
+          pdata->uload = ulnow;
+          pdata->ltime = now;
+        }
+
+        if( !progress && (now >= (pdata->ltime + pdata->timeout)))
+        {
+          pdata->reached = true;
+          return 1; // aborts transfer
+        }
+      }
+    }
   }
   return 0;
 }
@@ -1663,18 +1649,6 @@ string MediaCurl::getAuthHint() const
   return "";
 }
 
-/**
- * MediaMultiCurl needs to reset the expected filesize in case a metalink file is downloaded
- * otherwise this function should not be called
- */
-void MediaCurl::resetExpectedFileSize(void *clientp, const ByteCount &expectedFileSize)
-{
-  ProgressData *data = reinterpret_cast<ProgressData *>(clientp);
-  if ( data ) {
-    data->_expectedFileSize = expectedFileSize;
-  }
-}
-
 ///////////////////////////////////////////////////////////////////
 
 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const