libzypp 17.38.15
RepoMirrorList.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8\---------------------------------------------------------------------*/
14#include <iostream>
15#include <fstream>
16#include <utility>
17#include <vector>
18#include <time.h>
20#include <zypp-curl/parser/MetaLinkParser>
21#include <zypp/MediaSetAccess.h>
23#include <zypp/ZConfig.h>
24#include <zypp/PathInfo.h>
26
33
35#include <zypp/media/MediaNetworkCommonHandler.h> // for the authentication workflow
36
38
39
41namespace zypp
42{
44 namespace repo
45 {
46
48 namespace
49 {
59 struct MirrorListRefused : public zypp::Exception
60 {
61 MirrorListRefused( const std::string & msg_r ) : zypp::Exception( msg_r ) {}
62 };
63
71 struct RepoMirrorListTempProvider
72 {
73 RepoMirrorListTempProvider()
74 {}
75
76 RepoMirrorListTempProvider( Pathname localfile_r )
77 : _localfile(std::move( localfile_r ))
78 {}
79
80 RepoMirrorListTempProvider( const Url & url_r )
81 {
82 if ( url_r.schemeIsDownloading()
84 && url_r.getQueryStringMap().count("mirrorlist") > 0 ) {
85
86 // Auth will probably never be triggered, but add it for completeness
87 const auto &authCb = [&]( const zypp::Url &, media::TransferSettings &settings, const std::string & availAuthTypes, bool firstTry, bool &canContinue ) {
88 media::CredentialManager cm(media::CredManagerOptions(ZConfig::instance().repoManagerRoot()));
89 if ( media::MediaNetworkCommonHandler::authenticate( url_r, cm, settings, availAuthTypes, firstTry ) ) {
90 canContinue = true;
91 return;
92 }
93 canContinue = false;
94 };
95
96 internal::MediaNetworkRequestExecutor executor;
97 executor.sigAuthRequired ().connect(authCb);
98
99 _tmpfile = filesystem::TmpFile();
100 _localfile = _tmpfile->path();
101
102 // prepare Url and Settings
103 auto url = url_r;
104 auto tSettings = media::TransferSettings();
105 ::internal::prepareSettingsAndUrl( url, tSettings );
106
107 auto req = std::make_shared<zyppng::NetworkRequest>( url_r, _localfile );
108 req->transferSettings () = tSettings;
109 try {
110 executor.executeRequest ( req, nullptr );
111 }
112 catch ( const zypp::Exception & ) {
113 switch ( req->error().type() ) {
116 case zyppng::NetworkRequestError::ServerReturnedError: // any other status, e.g. 425
117 // The server answered about this very resource. That is a
118 // definitive "no mirrorlist here", worth remembering.
119 ZYPP_THROW( MirrorListRefused( req->error().toString() ) );
120 default:
121 // name resolution, connection, TLS, timeout, cancel: says
122 // nothing about mirrors, so it must not be remembered.
123 throw;
124 }
125 }
126
127 // apply umask
128 if ( ::chmod( _localfile.c_str(), filesystem::applyUmaskTo( 0644 ) ) )
129 {
130 ERR << "Failed to chmod file " << _localfile << endl;
131 }
132
133 return;
134 }
135
136 // this will handle traditional media including URL resolver plugins
137 Url abs_url( url_r );
138 abs_url.setPathName( "/" );
139 _access.reset( new MediaSetAccess( zypp::MirroredOrigin{abs_url} ) );
140 _localfile = _access->provideFile( url_r.getPathName() );
141
142 }
143
144 const Pathname & localfile() const
145 { return _localfile; }
146 private:
147 shared_ptr<MediaSetAccess> _access;
148 Pathname _localfile;
149 std::optional<filesystem::TmpFile> _tmpfile;
150 };
151
152 enum class RepoMirrorListFormat {
153 Error,
154 Empty,
155 MirrorListTxt,
156 MirrorListJson,
157 MetaLink
158 };
159
160 static RepoMirrorListFormat detectRepoMirrorListFormat( const Pathname &localfile ) {
161 // a file starting with < is most likely a metalink file,
162 // a file starting with [ is most likely a json file,
163 // else we go for txt
164 MIL << "Detecting RepoMirrorlist Format based on file content" << std::endl;
165
166 if ( localfile.empty () )
167 return RepoMirrorListFormat::Empty;
168
169 InputStream tmpfstream (localfile);
170 auto &str = tmpfstream.stream();
171 auto c = str.get ();
172
173 // skip preceding whitespaces
174 while ( !str.eof () && !str.bad() && ( c == ' ' || c == '\t' || c == '\n' || c == '\r') )
175 c = str.get ();
176
177 if ( str.eof() ) {
178 ERR << "Failed to read RepoMirrorList file, stream hit EOF early." << std::endl;
179 return RepoMirrorListFormat::Empty;
180 }
181
182 if ( str.bad() ) {
183 ERR << "Failed to read RepoMirrorList file, stream became bad." << std::endl;
184 return RepoMirrorListFormat::Error;
185 }
186
187 switch ( c ) {
188 case '<': {
189 MIL << "Detected Metalink, file starts with <" << std::endl;
190 return RepoMirrorListFormat::MetaLink;
191 }
192 case '[': {
193 MIL << "Detected JSON, file starts with [" << std::endl;
194 return RepoMirrorListFormat::MirrorListJson;
195 }
196 default: {
197 MIL << "Detected TXT, file starts with " << c << std::endl;
198 return RepoMirrorListFormat::MirrorListTxt;
199 }
200 }
201 }
202
203 inline std::vector<Url> RepoMirrorListParseXML( const Pathname &tmpfile )
204 {
205 try {
206 media::MetaLinkParser metalink;
207 metalink.parse(tmpfile);
208 return metalink.getUrls();
209 } catch (...) {
210 ZYPP_CAUGHT( std::current_exception() );
211 zypp::parser::ParseException ex("Invalid repo metalink format.");
212 ex.remember ( std::current_exception () );
213 ZYPP_THROW(ex);
214 }
215 }
216
217 inline std::vector<Url> RepoMirrorListParseJSON( const Pathname &tmpfile )
218 {
219 InputStream tmpfstream (tmpfile);
220
221 try {
222 using namespace zyppng::operators;
223 using zyppng::operators::operator|;
224
225 auto res = json::parseDocumentExpected( tmpfstream )
226 | and_then([&]( json::Value data ) {
227
228 std::vector<Url> urls;
229 if ( data.isNull () ) {
230 MIL << "Empty mirrorlist received, no mirrors available." << std::endl;
232 }
233
234 if ( data.type() != json::Value::ArrayType ) {
235 MIL << "Unexpected JSON format, top level element must be an array." << std::endl;
236 return zyppng::expected<std::vector<Url>>::error( ZYPP_EXCPT_PTR( zypp::Exception("Unexpected JSON format, top level element must be an array.") ));
237 }
238 const auto &topArray = data.asArray ();
239 for ( const auto &val : topArray ) {
240 if ( val.type () != json::Value::ObjectType ) {
241 MIL << "Unexpected JSON element, array must contain only objects. Ignoring current element" << std::endl;
242 continue;
243 }
244
245 const auto &obj = val.asObject();
246 for ( const auto &key : obj ) {
247 if ( key.first == "url" ) {
248 const auto &elemValue = key.second;
249 if ( elemValue.type() != json::Value::StringType ) {
250 MIL << "Unexpected JSON element, element \"url\" must contain a string. Ignoring current element" << std::endl;
251 break;
252 }
253 try {
254 MIL << "Trying to parse URL: " << std::string(elemValue.asString()) << std::endl;
255 urls.push_back ( Url( elemValue.asString() ) );
256 } catch ( const url::UrlException &e ) {
257 ZYPP_CAUGHT(e);
258 MIL << "Invalid URL in mirrors file: "<< elemValue.asString() << ", ignoring" << std::endl;
259 }
260 }
261 }
262 }
264 });
265
266 if ( !res ) {
267 using zypp::operator<<;
268 MIL << "Error while parsing mirrorlist: (" << res.error() << "), no mirrors available" << std::endl;
269 ZYPP_RETHROW( res.error () );
270 }
271
272 return *res;
273
274 } catch (...) {
275 ZYPP_CAUGHT( std::current_exception() );
276 MIL << "Caught exception while parsing json" << std::endl;
277
278 zypp::parser::ParseException ex("Invalid repo mirror list format, valid JSON was expected.");
279 ex.remember ( std::current_exception () );
280 ZYPP_THROW(ex);
281 }
282 return {};
283 }
284
285 inline std::vector<Url> RepoMirrorListParseTXT( const Pathname &tmpfile )
286 {
287 InputStream tmpfstream (tmpfile);
288 std::vector<Url> my_urls;
289 std::string tmpurl;
290 while (getline(tmpfstream.stream(), tmpurl))
291 {
292 if ( tmpurl[0] == '#' )
293 continue;
294 try {
295 Url mirrUrl( tmpurl );
296 if ( !mirrUrl.schemeIsDownloading( ) ) {
297 MIL << "Ignoring non downloading URL " << tmpurl << std::endl;
298 }
299 my_urls.push_back(Url(tmpurl));
300 }
301 catch (...)
302 {
303 ZYPP_CAUGHT( std::current_exception() );
304
305 // fail on invalid URLs
306 ERR << "Invalid URL in mirrorlist file." << std::endl;
307
308 zypp::parser::ParseException ex("Invalid repo mirror list format, all Urls must be valid in a mirrorlist txt file.");
309 ex.remember ( std::current_exception () );
310 ZYPP_THROW(ex);
311 }
312 }
313 return my_urls;
314 }
315
317 inline std::vector<Url> RepoMirrorListParse( const Url & url_r, const Pathname & listfile_r )
318 {
319 MIL << "Parsing mirrorlist file: " << listfile_r << " originally received from " << url_r << endl;
320
321 std::vector<Url> mirrorurls;
322 switch( detectRepoMirrorListFormat (listfile_r) ) {
323 case RepoMirrorListFormat::Error:
324 // should not happen, except when the instr goes bad
325 ZYPP_THROW( zypp::parser::ParseException( str::Format("Unable to detect metalink file format for: %1%") % listfile_r ));
326 case RepoMirrorListFormat::Empty:
327 mirrorurls = {};
328 break;
329 case RepoMirrorListFormat::MetaLink:
330 mirrorurls = RepoMirrorListParseXML( listfile_r );
331 break;
332 case RepoMirrorListFormat::MirrorListJson:
333 mirrorurls = RepoMirrorListParseJSON( listfile_r );
334 break;
335 case RepoMirrorListFormat::MirrorListTxt:
336 mirrorurls = RepoMirrorListParseTXT( listfile_r );
337 break;
338 }
339
340 std::vector<Url> ret;
341 for ( auto & murl : mirrorurls )
342 {
343 if ( murl.getScheme() != "rsync" )
344 {
345 std::string pName = murl.getPathName();
346 size_t delpos = pName.find("repodata/repomd.xml");
347 if( delpos != std::string::npos )
348 {
349 murl.setPathName( pName.erase(delpos) );
350 }
351 ret.push_back( murl );
352 }
353 }
354 return ret;
355 }
356
357 } // namespace
359
360 RepoMirrorList::RepoMirrorList( const Url & url_r, const Pathname & metadatapath_r, bool cacheFailure_r )
361 {
362 PathInfo metaPathInfo( metadatapath_r);
363 std::exception_ptr errors; // we collect errors here
364 try {
365 if ( url_r.getScheme() == "file" )
366 {
367 // never cache for local mirrorlist
368 _urls = RepoMirrorListParse( url_r, url_r.getPathName() );
369 }
370 else if ( !metaPathInfo.isDir() )
371 {
372 // no cachedir or no access
373 RepoMirrorListTempProvider provider( url_r ); // RAII: lifetime of any downloaded files
374 _urls = RepoMirrorListParse( url_r, provider.localfile() );
375 }
376 else
377 {
378 // have cachedir
379 const Pathname cachefile = metadatapath_r / cacheFileName();
380 const Pathname cookiefile = metadatapath_r / cookieFileName();
381 zypp::filesystem::PathInfo cacheinfo( cachefile );
382
383 bool needRefresh = ( !cacheinfo.isFile()
384 // force a update on a old cache ONLY if the user can write the cache, otherwise we use an already existing cachefile
385 // it makes no sense to continously download the mirrors file if we can't store it
386 || ( cacheinfo.mtime() < time(NULL) - (long) ZConfig::instance().repo_refresh_delay() * 60 && metaPathInfo.userMayRWX () ) )
387 || ( makeCookie( url_r ) != readCookieFile( cookiefile ) );
388
389 // up to date: try to parse and use the URLs if sucessful
390 // otherwise fetch the URL again
391 if ( !needRefresh ) {
392 MIL << "Mirror cachefile cookie valid and cache is not too old, skipping download (" << cachefile << ")" << std::endl;
393 try {
394 _urls = RepoMirrorListParse( url_r, cachefile );
395 return;
396 } catch ( const zypp::Exception & e ) {
397 ZYPP_CAUGHT(e);
398 auto ex = e;
399 if ( errors )
400 ex.remember(errors);
401 errors = std::make_exception_ptr(ex);
402 MIL << "Invalid mirrorlist cachefile, deleting it and trying to fetch a new one" << std::endl;
403 }
404 }
405
406 // remove the old cache and its cookie, it's either broken, empty or outdated
407 if( cacheinfo.isFile() ) {
408 filesystem::unlink(cachefile);
409 }
410
411 if ( zypp::filesystem::PathInfo(cookiefile).isFile() ) {
412 filesystem::unlink(cookiefile);
413 }
414
415 MIL << "Getting MirrorList from URL: " << url_r << endl;
416 try {
417 RepoMirrorListTempProvider provider( url_r ); // RAII: lifetime of downloaded file
418 _urls = RepoMirrorListParse( url_r, provider.localfile() );
419
420 // removed the && !_urls.empty() condition , we need to remember "no URLs" as well
421 // otherwise RepoInfo keeps spamming the server with requests
422 if ( metaPathInfo.userMayRWX() ) {
423 // Create directory, if not existing
424 DBG << "Copy MirrorList file to " << cachefile << endl;
425 zypp::filesystem::assert_dir( metadatapath_r );
426 if( zypp::filesystem::hardlinkCopy( provider.localfile(), cachefile ) != 0 ) {
427 // remember empty file
429 }
430 saveToCookieFile ( cookiefile, url_r );
431 // NOTE: Now we copied the mirrorlist into the metadata directory, but
432 // in case of refresh going on, new metadata are prepared in a sibling
433 // temp dir. Upon success RefreshContext<>::saveToRawCache() exchanges
434 // temp and metadata dirs. There we move an existing mirrorlist file into
435 // the new metadata dir.
436 }
437 }
438 catch ( const MirrorListRefused & e ) {
439 // The server answered, and it does not serve a mirrorlist (for
440 // OBS project repos download.opensuse.org says 425). An empty
441 // cachefile parses as "no mirrors"; without remembering that,
442 // such a repo is asked again on every single call.
443 //
444 // Only for this exception: a transport failure says nothing
445 // about whether the repo has mirrors, and caching it would
446 // disable mirror usage for repo_refresh_delay after one run
447 // while offline.
448 if ( cacheFailure_r && metaPathInfo.userMayRWX() ) {
449 MIL << "No mirrorlist for this repo, remembering that in " << cachefile << endl;
450 zypp::filesystem::assert_dir( metadatapath_r );
452 saveToCookieFile ( cookiefile, url_r );
453 }
454 ZYPP_RETHROW( e );
455 }
456 }
457 } catch ( const zypp::Exception &e ) {
458 // Make a more user readable exception
459 ZYPP_CAUGHT(e);
460 parser::ParseException ex( str::Format("Failed to parse/receive mirror information for URL: %1%") % url_r );
461 ex.remember(e);
462 if ( errors ) ex.remember(errors);
463 ZYPP_THROW(ex);
464 }
465 }
466
468 {
469 static const std::vector<std::string> hosts{
470 "download.opensuse.org",
471 "cdn.opensuse.org"
472 };
473 return ( std::find( hosts.begin(), hosts.end(), str::toLower( url.getHost() )) != hosts.end() );
474 }
475
476 std::string RepoMirrorList::readCookieFile(const Pathname &path_r)
477 {
478 std::ifstream file( path_r.c_str() );
479 if ( not file ) {
480 WAR << "No cookie file " << path_r << endl;
481 return {};
482 }
483
484 return str::getline( file );
485 }
486
490 std::string RepoMirrorList::makeCookie( const Url &url_r )
491 {
493 }
494
495 void RepoMirrorList::saveToCookieFile(const Pathname &path_r, const Url &url_r )
496 {
497 std::ofstream file(path_r.c_str());
498 if (!file) {
499 ERR << str::Str() << "Can't open " << path_r.asString() << std::endl;
500 return;
501 }
502 MIL << "Saving mirrorlist cookie file " << path_r << std::endl;
503 file << makeCookie(url_r);
504 file.close();
505 }
506
508 } // namespace repo
511} // namespace zypp
#define ZYPP_RETHROW(EXCPT)
Drops a logline and rethrows, updating the CodeLocation.
Definition Exception.h:479
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition Exception.h:475
#define ZYPP_EXCPT_PTR(EXCPT)
Drops a logline and returns Exception as a std::exception_ptr.
Definition Exception.h:463
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:459
#define DBG
Definition Logger.h:129
#define MIL
Definition Logger.h:130
#define ERR
Definition Logger.h:132
#define WAR
Definition Logger.h:131
std::string checksum() const
Definition CheckSum.cc:170
static CheckSum sha256FromString(const std::string &input_r)
Definition CheckSum.h:107
Base class for Exception.
Definition Exception.h:153
void remember(const Exception &old_r)
Store an other Exception as history.
Definition Exception.cc:154
MediaSetAccess(Url url, Pathname prefered_attach_point="")
Creates a callback enabled media access for specified url.
Url manipulation class.
Definition Url.h:93
std::string getScheme() const
Returns the scheme name of the URL.
Definition Url.cc:560
std::string asCompleteString() const
Returns a complete string representation of the Url object.
Definition Url.cc:532
std::string getPathName(EEncoding eflag=zypp::url::E_DECODED) const
Returns the path name from the URL.
Definition Url.cc:631
Url()
Definition Url.cc:306
static ZConfig & instance()
Singleton ctor.
Definition ZConfig.cc:794
Wrapper class for stat/lstat.
Definition PathInfo.h:226
const char * c_str() const
String representation.
Definition Pathname.h:113
const std::string & asString() const
String representation.
Definition Pathname.h:94
bool isNull() const
Definition JsonValue.h:279
Type type() const
Definition JsonValue.h:283
const Array & asArray() const
Definition JsonValue.h:271
bool authenticate(const Url &url, TransferSettings &settings, const std::string &availAuthTypes, bool firstTry)
static std::string makeCookie(const zypp::Url &url_r)
Generates the cookie value, currently this is only derived from the Url.
static std::string readCookieFile(const Pathname &path_r)
RepoMirrorList(const Url &url_r, const Pathname &metadatapath_r, bool cacheFailure_r=false)
static void saveToCookieFile(const Pathname &path_r, const zypp::Url &url_r)
static bool urlSupportsMirrorLink(const zypp::Url &url)
static constexpr const char * cookieFileName()
static constexpr const char * cacheFileName()
std::vector< Url > _urls
Public JSON API.
void prepareSettingsAndUrl(zypp::Url &url_r, zypp::media::TransferSettings &s)
int assert_file(const Pathname &path, unsigned mode)
Create an empty file if it does not yet exist.
Definition PathInfo.cc:1205
mode_t applyUmaskTo(mode_t mode_r)
Modify mode_r according to the current umask ( mode_r & ~getUmask() ).
Definition PathInfo.h:813
int hardlinkCopy(const Pathname &oldpath, const Pathname &newpath)
Create newpath as hardlink or copy of oldpath.
Definition PathInfo.cc:902
int unlink(const Pathname &path)
Like 'unlink'.
Definition PathInfo.cc:719
int assert_dir(const Pathname &path, unsigned mode)
Like 'mkdir -p'.
Definition PathInfo.cc:338
@ Error
Definition IOTools.h:74
std::string getline(std::istream &str)
Read one line from stream.
Definition IOStream.cc:33
std::string toLower(const std::string &s)
Return lowercase version of s.
Definition String.cc:180
std::string getline(std::istream &str, const Trim trim_r)
Return stream content up to (but not returning) the next newline.
Definition String.cc:481
Url details namespace.
Definition UrlBase.cc:58
Easy-to use interface to the ZYPP dependency resolver.
static expected< std::decay_t< Type >, Err > make_expected_success(Type &&t)
Definition expected.h:470
ResultType and_then(const expected< T, E > &exp, Function &&f)
Definition expected.h:520
zypp::Url Url
Definition url.h:15
Convenient building of std::string with boost::format.
Definition String.h:254
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition String.h:213