libzypp  17.37.18
MediaCurl2.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
13 #include <iostream>
14 
15 #include <zypp-core/base/Logger.h>
17 #include <zypp-core/base/String.h>
18 #include <zypp-core/base/Gettext.h>
19 #include <zypp-core/parser/Sysconfig>
20 
21 #include <zypp/media/MediaCurl2.h>
22 
25 #include <zypp-curl/ProxyInfo>
26 #include <zypp-curl/CurlConfig>
28 #include <zypp/Target.h>
29 #include <zypp/ZYppFactory.h>
30 #include <zypp/ZConfig.h>
31 #include <zypp/zypp_detail/ZYppImpl.h> // for zypp_poll
32 
35 
36 #include <cstdlib>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/mount.h>
40 #include <dirent.h>
41 #include <unistd.h>
42 #include <glib.h>
43 
47 
48 #ifdef ENABLE_ZCHUNK_COMPRESSION
50 #endif
51 
52 
59 using std::endl;
60 
61 using namespace internal;
62 using namespace zypp::base;
63 
64 namespace zypp {
65 
66  namespace media {
67 
68  MediaCurl2::MediaCurl2(const MirroredOrigin origin_r,
69  const Pathname & attach_point_hint_r )
70  : MediaNetworkCommonHandler( origin_r, attach_point_hint_r,
71  "/", // urlpath at attachpoint
72  true ) // does_download
73  , _executor( std::make_shared<internal::MediaNetworkRequestExecutor>() )
74  {
75 
76  MIL << "MediaCurl2::MediaCurl2(" << origin_r.authority().url() << ", " << attach_point_hint_r << ")" << endl;
77 
78  if( !attachPoint().empty())
79  {
80  PathInfo ainfo(attachPoint());
81  Pathname apath(attachPoint() + "XXXXXX");
82  char *atemp = ::strdup( apath.asString().c_str());
83  char *atest = NULL;
84  if( !ainfo.isDir() || !ainfo.userMayRWX() ||
85  atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
86  {
87  WAR << "attach point " << ainfo.path()
88  << " is not useable for " << origin_r.authority().url().getScheme() << endl;
89  setAttachPoint("", true);
90  }
91  else if( atest != NULL)
92  ::rmdir(atest);
93 
94  if( atemp != NULL)
95  ::free(atemp);
96  }
97  }
98 
100 
101  void MediaCurl2::checkProtocol(const Url &url) const
102  {
103  if ( !zyppng::NetworkRequestDispatcher::supportsProtocol ( url ) )
104  {
105  std::string msg("Unsupported protocol '");
106  msg += url.getScheme();
107  msg += "'";
109  }
110  }
111 
113  {
114  // clear effective settings
116  }
117 
119 
120  void MediaCurl2::releaseFrom( const std::string & ejectDev )
121  {
122  disconnect();
123  }
124 
126 
127  void MediaCurl2::getFileCopy( const OnMediaLocation & srcFile , const Pathname & target ) const
128  {
129 
130  const auto &filename = srcFile.filename();
131 
132  // Optional files will send no report until data are actually received (we know it exists).
133  OptionalDownloadProgressReport reportfilter( srcFile.optional() );
134  callback::SendReport<DownloadProgressReport> report;
135 
136  const auto &mirrOrder = mirrorOrder (srcFile);
137  for ( unsigned mirr : mirrOrder ) {
138  MIL << "Trying to fetch file " << srcFile << " via URL: " << _origin[mirr].url() << std::endl;
139  Url fileurl(getFileUrl(mirr, filename));
140 
141  try
142  {
143  const auto &myOrigin = _origin[mirr];
144  if(!myOrigin.url().isValid())
145  ZYPP_THROW(MediaBadUrlException(myOrigin.url()));
146 
147  if(myOrigin.url().getHost().empty())
148  ZYPP_THROW(MediaBadUrlEmptyHostException(myOrigin.url()));
149 
150 
151  Pathname dest = target.absolutename();
152  if( assert_dir( dest.dirname() ) ) {
153  DBG << "assert_dir " << dest.dirname() << " failed" << endl;
154  ZYPP_THROW( MediaSystemException(fileurl, "System error on " + dest.dirname().asString()) );
155  }
156 
157  ManagedFile destNew { target.extend( ".new.zypp.XXXXXX" ) }; {
158  AutoFREE<char> buf { ::strdup( (*destNew).c_str() ) };
159  if( ! buf ) {
160  ERR << "out of memory for temp file name" << endl;
161  ZYPP_THROW(MediaSystemException(fileurl, "out of memory for temp file name"));
162  }
163 
164  AutoFD tmp_fd { ::mkostemp( buf, O_CLOEXEC ) };
165  if( tmp_fd == -1 ) {
166  ERR << "mkstemp failed for file '" << destNew << "'" << endl;
168  }
169  destNew = ManagedFile( (*buf), filesystem::unlink );
170  }
171 
172  DBG << "dest: " << dest << endl;
173  DBG << "temp: " << destNew << endl;
174  #if 0
175  Not implemented here yet because NetworkRequest can not do IFMODSINCE yet
176  // set IFMODSINCE time condition (no download if not modified)
177  if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) )
178  {
179  curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
180  curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime());
181  }
182  else
183  {
184  curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
185  curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
186  }
187  #endif
188 
189  DBG << srcFile.filename().asString() << endl;
190  DBG << "URL: " << fileurl.asString() << endl;
191 
192  // Use URL without options and without username and passwd
193  // (some proxies dislike them in the URL).
194  // Curl seems to need the just scheme, hostname and a path;
195  // the rest was already passed as curl options (in attachTo).
196  Url curlUrl( clearQueryString(fileurl) );
197 
198  RequestData r;
199  r._mirrorIdx = mirr;
200  r._req = std::make_shared<zyppng::NetworkRequest>( curlUrl, destNew, zyppng::NetworkRequest::WriteShared /*do not truncate*/ );
201  r._req->transferSettings() = myOrigin.getConfig<TransferSettings>( MIRR_SETTINGS_KEY.data() );
202  r._req->setExpectedFileSize ( srcFile.downloadSize () );
203 
204  bool done = false;
205  #ifdef ENABLE_ZCHUNK_COMPRESSION
206  done = const_cast<MediaCurl2*>(this)->tryZchunk(r, srcFile, destNew, report);
207  #endif
208  if ( !done ) {
209  r._req->resetRequestRanges();
210  const_cast<MediaCurl2 *>(this)->executeRequest ( r, &report );
211  }
212 
213  #if 0
214  Also disabled IFMODSINCE code, see above while not yet implemented here
215  #if CURLVERSION_AT_LEAST(7,19,4)
216  // bnc#692260: If the client sends a request with an If-Modified-Since header
217  // with a future date for the server, the server may respond 200 sending a
218  // zero size file.
219  // curl-7.19.4 introduces CURLINFO_CONDITION_UNMET to check this condition.
220  if ( ftell(file) == 0 && ret == 0 )
221  {
222  long httpReturnCode = 33;
223  if ( curl_easy_getinfo( _curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 )
224  {
225  long conditionUnmet = 33;
226  if ( curl_easy_getinfo( _curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet )
227  {
228  WAR << "TIMECONDITION unmet - retry without." << endl;
229  curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
230  curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
231  ret = executeCurl();
232  }
233  }
234  }
235  #endif
236  #endif
237 
238 
239  // apply umask
240  if ( ::chmod( destNew->c_str(), filesystem::applyUmaskTo( 0644 ) ) )
241  {
242  ERR << "Failed to chmod file " << destNew << endl;
243  }
244 
245  // move the temp file into dest
246  if ( rename( destNew, dest ) != 0 ) {
247  ERR << "Rename failed" << endl;
249  }
250  destNew.resetDispose(); // no more need to unlink it
251 
252  DBG << "done: " << PathInfo(dest) << endl;
253 
254  break; // success!
255  }
256  catch (MediaException & excpt_r)
257  {
258  // check if we can retry on the next mirror
259  if( !canTryNextMirror ( excpt_r ) || ( mirr == mirrOrder.back() ) ) {
260  // rewrite the exception to contain the correct pathname and url
261  // the executeRequest implementation just emits the full Url in the exception
262  if ( typeid(excpt_r) == typeid( MediaFileNotFoundException ) ) {
263  ZYPP_CAUGHT(excpt_r);
264  ZYPP_THROW( MediaFileNotFoundException( url(), filename ) );
265  }
266  ZYPP_RETHROW(excpt_r);
267  }
268  ZYPP_CAUGHT(excpt_r);
269  continue;
270  }
271  }
272  }
273 
274  bool MediaCurl2::getDoesFileExist( const Pathname & filename ) const
275  {
276  DBG << filename.asString() << endl;
277 
278  std::exception_ptr lastErr;
279  MIL << "Trying origin: " << _origin << std::endl;
280  for ( unsigned mirr : mirrorOrder( OnMediaLocation( filename ).setMirrorsAllowed(false) )) {
281 
282  const auto &myEndpoint = _origin[mirr];
283 
284  if( !myEndpoint.url().isValid() )
285  ZYPP_THROW(MediaBadUrlException(myEndpoint.url()));
286 
287  if( _origin[mirr].url().getHost().empty() )
288  ZYPP_THROW(MediaBadUrlEmptyHostException(myEndpoint.url()));
289 
290  Url url(getFileUrl(mirr, filename));
291 
292  DBG << "URL: " << url.asString() << endl;
293 
294  // Use URL without options and without username and passwd
295  // (some proxies dislike them in the URL).
296  // Curl seems to need the just scheme, hostname and a path;
297  // the rest was already passed as curl options (in attachTo).
298  Url curlUrl( clearQueryString(url) );
299 
300  RequestData r;
301  r._mirrorIdx = mirr;
302  r._req = std::make_shared<zyppng::NetworkRequest>( curlUrl, "/dev/null" );
303  r._req->setOptions ( zyppng::NetworkRequest::HeadRequest ); // just check for existance
304  r._req->transferSettings() = myEndpoint.getConfig<TransferSettings>(MIRR_SETTINGS_KEY.data());
305 
306  // as we are not having user interaction, the user can't cancel
307  // the file existence checking, a callback or timeout return code
308  // will be always a timeout.
309  try {
310  const_cast<MediaCurl2*>(this)->executeRequest ( r );
311 
312  } catch ( const MediaFileNotFoundException &e ) {
313  // if the file did not exist then we can return false
314  return false;
315  } catch ( const MediaException &e ) {
316  // check if we can retry on the next mirror
317  lastErr = ZYPP_FWD_CURRENT_EXCPT();
318  if( !canTryNextMirror ( e ) ) {
319  break;
320  }
321  continue;
322  }
323  // exists
324  return ( !r._req->hasError() );
325  }
326 
327  if ( lastErr ) {
328  try {
329  ZYPP_RETHROW (lastErr);
330  } catch ( const MediaFileNotFoundException &e ) {
331  // if the file did not exist then we can return false
332  return false;
333  }
334  }
335  // we probably never had mirrors, otherwise we either had an error or a success.
336  return false;
337  }
338 
339  bool MediaCurl2::tryZchunk( RequestData &reqData, const OnMediaLocation &srcFile, const Pathname &target, callback::SendReport<DownloadProgressReport> &report )
340  {
341 #ifdef ENABLE_ZCHUNK_COMPRESSION
342 
343  // HERE add zchunk logic if required
344  if ( !srcFile.deltafile().empty()
345  && zyppng::ZckLoader::isZchunkFile (srcFile.deltafile ()) ) {
346 
347  zyppng::Ref<zyppng::ZckLoader> zckHelper = std::make_shared<zyppng::ZckLoader>();
348 
349  const auto &fetchChunks = [&]( const std::vector<zyppng::ZckLoader::Block> &blocks ){
350 
351  reqData._req->resetRequestRanges();
352 
353  for ( const auto &block : blocks ) {
354  if ( block._checksum.size() && block._chksumtype.size() ) {
355  std::optional<zypp::Digest> dig = zypp::Digest();
356  if ( !dig->create( block._chksumtype ) ) {
357  WAR_MEDIA << "Trying to create Digest with chksum type " << block._chksumtype << " failed " << std::endl;
358  zckHelper->setFailed( str::Str() << "Trying to create Digest with chksum type " << block._chksumtype << " failed " );
359  return;
360  }
361 
363  DBG_MEDIA << "Starting block " << block._start << " with checksum " << zypp::Digest::digestVectorToString( block._checksum ) << "." << std::endl;
364  reqData._req->addRequestRange( block._start, block._len, std::move(dig), block._checksum, {}, block._relevantDigestLen, block._chksumPad );
365  } else {
367  DBG_MEDIA << "Starting block " << block._start << " without checksum!" << std::endl;
368  reqData._req->addRequestRange( block._start, block._len );
369  }
370  };
371 
372  executeRequest ( reqData, &report );
373  zckHelper->cont().unwrap();
374  };
375 
376 
378  const auto &fin = [&]( zyppng::ZckLoader::PrepareResult result ){
379  res = std::move(result);
380  };
381 
382  const auto &hdrSize = srcFile.headerSize();
383  const auto &dSize = srcFile.downloadSize();
384  zckHelper->connectFunc( &zyppng::ZckLoader::sigBlocksRequired, fetchChunks );
385  zckHelper->connectFunc( &zyppng::ZckLoader::sigFinished, fin );
386  zckHelper->buildZchunkFile( target, srcFile.deltafile(), dSize ? dSize : std::optional<zypp::ByteCount>{}, hdrSize ? hdrSize : std::optional<zypp::ByteCount>{} ).unwrap();
387 
388  switch(res._code) {
390  ERR << "Failed to setup zchunk because of: " << res._message << std::endl;
391  return false;
392  }
394  ZYPP_THROW( MediaFileSizeExceededException( reqData._req->url(), srcFile.downloadSize(), res._message ));
397  return true; //done
398  }
399  }
400 #endif
401  return false;
402  }
403 
404  void MediaCurl2::executeRequest( MediaCurl2::RequestData &reqData , callback::SendReport<DownloadProgressReport> *report )
405  {
406  const auto &authCb = [&]( const zypp::Url &, TransferSettings &settings, const std::string & availAuthTypes, bool firstTry, bool &canContinue ) {
407  auto &originEndpoint = _origin[reqData._mirrorIdx];
408  auto &epSettings = originEndpoint.getConfig<TransferSettings>(MIRR_SETTINGS_KEY.data());
409  if ( authenticate( _origin[reqData._mirrorIdx].url(), epSettings, availAuthTypes, firstTry ) ) {
410  settings = epSettings;
411  canContinue = true;
412  return;
413  }
414  canContinue = false;
415  };
416 
417  auto conn = _executor->sigAuthRequired().connect (authCb);
418  zypp_defer {
419  conn.disconnect ();
420  };
421 
422  _executor->executeRequest ( reqData._req, report );
423  }
424  } // namespace media
425 } // namespace zypp
426 //
std::string getScheme() const
Returns the scheme name of the URL.
Definition: Url.cc:551
#define MIL
Definition: Logger.h:100
#define zypp_defer
Definition: AutoDispose.h:293
#define DBG_MEDIA
Definition: mediadebug_p.h:28
void checkProtocol(const Url &url) const override
check the url is supported by the curl library
Definition: MediaCurl2.cc:101
std::vector< unsigned > mirrorOrder(const OnMediaLocation &loc) const
int assert_dir(const Pathname &path, unsigned mode)
Like &#39;mkdir -p&#39;.
Definition: PathInfo.cc:324
const ByteCount & headerSize() const
The size of the header prepending the resource (e.g.
const Pathname & path() const
Return current Pathname.
Definition: PathInfo.h:251
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:459
Implementation class for FTP, HTTP and HTTPS MediaHandler.
Definition: MediaCurl2.h:43
Describes a resource file located on a medium.
bool getDoesFileExist(const Pathname &filename) const override
Repeatedly calls doGetDoesFileExist() until it successfully returns, fails unexpectedly, or user cancels the operation.
Definition: MediaCurl2.cc:274
Compute Message Digests (MD5, SHA1 etc)
Definition: Digest.h:37
std::shared_ptr< T > Ref
Definition: zyppglobal.h:110
OriginEndpoint originEndpoint() const
Primary OriginEndpoint used.
Definition: MediaHandler.h:507
void getFileCopy(const OnMediaLocation &srcFile, const Pathname &targetFilename) const override
Definition: MediaCurl2.cc:127
static constexpr std::string_view MIRR_SETTINGS_KEY
static bool isZchunkFile(const zypp::Pathname &file)
Definition: zckhelper.cc:309
Holds transfer setting.
Pathname extend(const std::string &r) const
Append string r to the last component of the path.
Definition: Pathname.h:175
static bool canTryNextMirror(const Excpt &excpt_r)
int chmod(const Pathname &path, mode_t mode)
Like &#39;chmod&#39;.
Definition: PathInfo.cc:1097
void setAttachPoint(const Pathname &path, bool temp)
Set a new attach point.
Definition: ansi.h:854
const zypp::Url & url() const
void disconnectFrom() override
Definition: MediaCurl2.cc:112
bool authenticate(const Url &url, TransferSettings &settings, const std::string &availAuthTypes, bool firstTry)
AutoDispose<int> calling ::close
Definition: AutoDispose.h:309
SignalProxy< void(PrepareResult)> sigFinished()
Called once the zchunk build process is finished, either with error or success.
Definition: zckhelper.cc:299
#define ERR
Definition: Logger.h:102
bool tryZchunk(RequestData &reqData, const OnMediaLocation &srcFile, const Pathname &target, callback::SendReport< DownloadProgressReport > &report)
Definition: MediaCurl2.cc:339
AutoDispose< const Pathname > ManagedFile
A Pathname plus associated cleanup code to be executed when path is no longer needed.
Definition: ManagedFile.h:27
static std::string digestVectorToString(const UByteArray &vec)
get hex string representation of the digest vector given as parameter
Definition: Digest.cc:244
bool empty() const
Test for an empty path.
Definition: Pathname.h:116
#define ZYPP_RETHROW(EXCPT)
Drops a logline and rethrows, updating the CodeLocation.
Definition: Exception.h:479
std::string asString() const
Returns a default string representation of the Url object.
Definition: Url.cc:515
Bottleneck filtering all DownloadProgressReport issued from Media[Muli]Curl.
MirroredOrigin _origin
Contains the authority URL and mirrors.
Definition: MediaHandler.h:112
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition: String.h:212
Manages a data source characterized by an authoritative URL and a list of mirror URLs.
const std::string & asString() const
String representation.
Definition: Pathname.h:93
Just inherits Exception to separate media exceptions.
const ByteCount & downloadSize() const
The size of the resource on the server.
void disconnect()
Use concrete handler to isconnect media.
Pathname dirname() const
Return all but the last component od this path.
Definition: Pathname.h:126
#define WAR
Definition: Logger.h:101
const long & ZYPP_MEDIA_CURL_DEBUG()
const long& for setting CURLOPT_DEBUGDATA Returns a reference to a static variable, so it&#39;s safe to pass ...
Definition: curlhelper.cc:36
void executeRequest(RequestData &reqData, callback::SendReport< DownloadProgressReport > *report=nullptr)
Definition: MediaCurl2.cc:404
zyppng::NetworkRequestRef _req
Definition: MediaCurl2.h:95
const Pathname & filename() const
The path to the resource on the medium.
int unlink(const Pathname &path)
Like &#39;unlink&#39;.
Definition: PathInfo.cc:705
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition: Exception.h:475
const Pathname & deltafile() const
The existing deltafile that can be used to reduce download size ( zchunk or metalink ) ...
#define WAR_MEDIA
Definition: mediadebug_p.h:30
Pathname absolutename() const
Return this path, adding a leading &#39;/&#39; if relative.
Definition: Pathname.h:141
Pathname attachPoint() const
Return the currently used attach point.
Url url() const
Primary Url used.
Definition: MediaHandler.h:502
Url getFileUrl(int mirrorIdx, const Pathname &filename) const
concatenate the attach url and the filename to a complete download url
SignalProxy< void(const std::vector< Block > &)> sigBlocksRequired()
Signal to notify the caller about required blocks, once the blocks are downloaded call cont to contin...
Definition: zckhelper.cc:294
std::string getHost(EEncoding eflag=zypp::url::E_DECODED) const
Returns the hostname or IP from the URL authority.
Definition: Url.cc:606
Wrapper class for ::stat/::lstat.
Definition: PathInfo.h:225
mode_t applyUmaskTo(mode_t mode_r)
Modify mode_r according to the current umask ( mode_r & ~getUmask() ).
Definition: PathInfo.h:806
#define ZYPP_FWD_CURRENT_EXCPT()
Drops a logline and returns the current Exception as a std::exception_ptr.
Definition: Exception.h:471
int rename(const Pathname &oldpath, const Pathname &newpath)
Like &#39;rename&#39;.
Definition: PathInfo.cc:747
const OriginEndpoint & authority() const
int rmdir(const Pathname &path)
Like &#39;rmdir&#39;.
Definition: PathInfo.cc:371
bool optional() const
Whether this is an optional resource.
bool userMayRWX() const
Definition: PathInfo.h:361
Url manipulation class.
Definition: Url.h:92
void releaseFrom(const std::string &ejectDev) override
Call concrete handler to release the media.
Definition: MediaCurl2.cc:120
internal::MediaNetworkRequestExecutorRef _executor
Definition: MediaCurl2.h:102
#define DBG
Definition: Logger.h:99