1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
12 #include "zypp/base/LogTools.h"
13 #include "zypp/base/String.h"
14 #include "zypp/base/Regex.h"
16 #include "zypp/ZYppFactory.h"
17 #include "zypp/ZConfig.h"
18 #include "zypp/Target.h"
19 #include "zypp/Arch.h"
20 #include "zypp/repo/RepoVariables.h"
21 #include "zypp/base/NonCopyable.h"
23 #define ZYPP_DBG_VAREXPAND 0
24 #if ( ZYPP_DBG_VAREXPAND )
25 #warning ZYPP_DBG_VAREXPAND is on
27 #endif // ZYPP_DBG_VAREXPAND
29 ///////////////////////////////////////////////////////////////////
34 /** Use faked releasever (e.g. for 'zupper dup' to next distro version */
35 inline std::string ZYPP_REPO_RELEASEVER()
37 const char * env = getenv("ZYPP_REPO_RELEASEVER");
38 return( env ? env : "" );
42 ///////////////////////////////////////////////////////////////////
45 ///////////////////////////////////////////////////////////////////
47 ///////////////////////////////////////////////////////////////////
50 ///////////////////////////////////////////////////////////////////
52 /// \brief Helper scanning for variable definitions in a string
53 ///////////////////////////////////////////////////////////////////
56 bool _embedded; ///< A (formerly) embedded string may have esacped \c $, \c closebrace and \c backslash
57 const char * _sbeg; ///< start of string to scan
58 const char * _vbeg; ///< [$]{variable:-word} / [$]{variable} / if embedded also on [\\]
59 const char * _nbeg; ///< ${[v]ariable:-word} / ${[v]ariable}
60 const char * _nend; ///< ${variable[:]-word} / ${variable[}]
61 const char * _vend; ///< ${variable:-word}[] / ${variable}[]
62 const char * _send; ///< end of scan (next $ or nullptr if done)
64 FindVar( const std::string & str_r, bool embedded_r )
65 : _embedded( embedded_r )
66 , _sbeg( str_r.c_str() )
71 , _send( findVarStart( _sbeg ) )
74 /** Nullptr in _send indicates we scanned the whole string. */
78 /** Advance to first/next var if there is one */
85 if ( _vbeg && !_vend ) // loop internal: no findVarEnd at current $; skip it
86 _send = findVarStart( _vbeg+1 );
87 _vbeg = _send; // next $ or null if string end
88 _nbeg = _nend = _vend = _send = nullptr;
89 if ( ! _vbeg ) // done!
91 } while( ! findVarEnd() );
96 /** Valid _vend indicates valid var data in scan. */
101 // Methods below are only valid if hasVar() == true
104 /** Return the full var text */
105 std::string var() const
106 { return std::string( _vbeg, _vend ); }
108 /** Return the var name */
109 std::string varName() const
110 { return std::string( _nbeg, _nend ); }
112 /** Whether this is a conditional var (${..:[+-]...}) */
113 bool varIsConditional() const
114 { return( *(_vbeg+1) == '{' && *_nend == ':' ); }
116 /** The var type: \c \, \c $, \c - , \c +
117 * \li \c \ backslash escaped literal
118 * \li \c $ plain variable
119 * \li \c - conditional: default value
120 * \li \c + conditional: alternate value
123 { return( varIsConditional() ? *(_nend+1) : *_vbeg ); }
125 /** Return embedded value in conditional vars or empty string */
126 std::string varEmbedded() const
127 { return( varIsConditional() ? std::string( _nend+2, _vend-1 ) : std::string() ); }
130 /** Have unwritten data before var? */
131 bool hasVarPrefix() const
132 { return ( _sbeg != _vbeg ); }
134 /** Return unwritten data before var */
135 std::string varPrefix() const
136 { return std::string( _sbeg, _vbeg ); }
138 /** Indicate all data up to _vend were written */
143 /** Return next \c $ */
144 const char * findVarStart( const char * sbeg_r ) const
146 for ( ; *sbeg_r; ++sbeg_r )
147 if ( *sbeg_r == '$' || ( _embedded && *sbeg_r == '\\' ) )
152 /** Valid var name char */
153 bool isnamech( int ch ) const
154 { return ch == '_' || isalnum( ch ); }
156 /** Scan for a valid variable starting at _vbeg (storing the values) */
159 // asserted: *_vbeg == '$' || '\\'
160 if ( ! findVarEnd( _vbeg, _nbeg, _nend, _vend ) )
162 _send = findVarStart( _vend );
166 /** Skip over valid variable starting at vbeg (return end in \a vend). */
167 const char * findVarEnd( const char * vbeg ) const
169 // asserted: *_vbeg == '$'
170 const char * nbeg = nullptr;
171 const char * nend = nullptr;
172 const char * vend = nullptr;
173 findVarEnd( vbeg, nbeg, nend, vend );
177 /** Scan for a valid variable starting at vbeg (const version returning the values). */
178 bool findVarEnd( const char * vbeg, const char *& nbeg, const char *& nend, const char *& vend ) const
180 // embedded only: handle backslash escaped chars
181 if ( *_vbeg == '\\' )
188 nend = vend = vbeg+2;
194 // asserted: *vbeg == '$'
195 // vbeg: [$]{variable:-word} / [$]{variable}
196 // nbeg: ${[v]ariable:-word} / ${[v]ariable}
197 bool braced = ( *(vbeg+1) == '{' ); //}
198 nbeg = vbeg+( braced ? 2 : 1 );
199 if ( !isnamech( *nbeg ) ) // don't allow empty var name
201 for ( nend = nbeg+1; isnamech( *nend ); ++nend )
202 {;} // skip over var name
203 // nend: ${variable[:]-word} / ${variable[}]
205 // vend: ${variable:-word}[] / ${variable}[]
206 // stay with ( vend == nullptr ) until you know it's valid
213 else if ( *nend == ':' )
215 const char * scan = nend+1;
216 if ( *scan == '+' || *scan == '-' )
219 // find first not escaped '}'
224 ++scan; // next char is skipped
228 else if ( *scan == '$' )
231 if ( ! (scan = findVarEnd( scan )) )
234 else if ( *scan == '}' )
236 vend = scan+1; // ==> unesacped '}', we're done!
242 // ( ! *scan ) => end of string while looking for unesacped '}'
245 ; // err: ':' not followed by '+' or '-'
248 ; // err: braced name must end with '}' or ':'
252 vend = nend; // un-braced
254 return( vend != nullptr );
258 bool _expand( std::string &, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r );
260 inline std::string expand( const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
263 if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
268 inline std::string expand( std::string && value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
271 if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
272 ret = std::move(value_r);
276 /** Expand variables in \a value_r depending on \a level-r
277 * <tt>level_r > 0</tt> may have escaped chars outside braces.
279 inline bool _expand( std::string & result_r, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
281 #if ( ZYPP_DBG_VAREXPAND )
282 cout << std::string( 2*level_r, ' ' ) << "\033[7m>>" << value_r << "<<\033[27m" << endl;
283 std::ostringstream dbg;
284 const char * dbgsbeg = value_r.c_str(); // track vars we already added to dbg
285 unsigned dbgi = 0; // color 1-5 var / 6 moved value_r
286 dbg << std::string( 2*level_r, ' ' ) << ">>";
287 #endif // ZYPP_DBG_VAREXPAND
289 bool expanded = false;
291 if ( ! value_r.empty() )
293 FindVar scan( value_r, level_r ); // level_r > 0 is embedded
294 while ( scan.nextVar() )
296 static const std::string _emptyValue;
297 const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr );
298 const std::string & varValue( knownVar ? *knownVar : _emptyValue );
300 #if ( ZYPP_DBG_VAREXPAND )
301 dbg << std::string(dbgsbeg,scan._vbeg) << "\033[3" << ((dbgi%5)+1) << "m" << scan.var() << "\033[0m";
302 cout << dbg.str() << "|<< " << scan.varName() << " " << (knownVar?"("+varValue+")":"-") << " {" << scan.varEmbedded() << "}" << endl;
303 dbgsbeg = scan._vend;
305 #endif // ZYPP_DBG_VAREXPAND
307 bool mustSubstitute = false; // keep original text per default
308 std::string substitutionValue;
310 int varType = scan.varType();
311 if ( varType == '$' ) // plain var
315 mustSubstitute = true;
316 substitutionValue = varValue;
319 ; // keep original text per default
321 else if ( varType == '-' ) // ':-' default value
323 mustSubstitute = true;
324 if ( varValue.empty() )
325 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
327 substitutionValue = varValue;
329 else if ( varType == '+' ) // ':+' alternate value
331 mustSubstitute = true;
332 if ( ! varValue.empty() )
333 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
335 ; // empty substitutionValue
337 else if ( varType == '\\' ) // backslash escaped literal (in varName)
339 mustSubstitute = true;
340 substitutionValue = scan.varName();
343 ; // keep original text per default
345 if ( mustSubstitute )
347 if ( scan.hasVarPrefix() )
348 result_r += scan.varPrefix();
349 if ( ! substitutionValue.empty() )
350 result_r += substitutionValue;
351 scan.wroteVar(); // this moves scan._sbeg so we can later see what's already written
355 #if ( ZYPP_DBG_VAREXPAND )
356 dbg << std::string( dbgsbeg ) << (scan._sbeg == value_r.c_str() ? "<<\033[36m(moved)\033[0m" : "");
357 #endif // ZYPP_DBG_VAREXPAND
359 // handle unwritten data:
360 if ( scan._sbeg != value_r.c_str() )
364 result_r += std::string( scan._sbeg );
367 ; // no replacements at all
370 #if ( ZYPP_DBG_VAREXPAND )
372 cout << dbg.str() << endl;
373 cout << std::string( 2*level_r, ' ' ) << "\033[36m->" << result_r << "<-\033[0m" << endl;
374 #endif // ZYPP_DBG_VAREXPAND
378 ///////////////////////////////////////////////////////////////////
380 std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const
381 { return expand( value_r, 0, varRetriever_r ); }
383 std::string RepoVarExpand::operator()( std::string && value_r, VarRetriever varRetriever_r ) const
384 { return expand( std::move(value_r), 0, varRetriever_r ); }
386 ///////////////////////////////////////////////////////////////////
387 // RepoVariables*Replace
388 ///////////////////////////////////////////////////////////////////
391 class RepoVarsMap : public std::map<std::string,std::string>
394 static RepoVarsMap & instance()
395 { static RepoVarsMap _instance; return _instance; }
397 static const std::string * lookup( const std::string & name_r )
398 { return instance()._lookup( name_r ); }
401 const std::string * _lookup( const std::string & name_r )
403 if ( empty() ) // at init / after reset
405 // load user definitions from vars.d
406 filesystem::dirForEach( ZConfig::instance().systemRoot() / ZConfig::instance().varsPath(),
407 filesystem::matchNoDots(), bind( &RepoVarsMap::parse, this, _1, _2 ) );
408 // releasever_major/_minor are per default derived from releasever.
409 // If releasever is userdefined, inject missing _major/_minor too.
410 deriveFromReleasever( "releasever", /*dont't overwrite user defined values*/false );
413 // add builtin vars except for releasever{,_major,_minor} (see checkOverride)
415 const Arch & arch( ZConfig::instance().systemArchitecture() );
417 std::string & var( operator[]( "arch" ) );
418 if ( var.empty() ) var = arch.asString();
421 std::string & var( operator[]( "basearch" ) );
422 if ( var.empty() ) var = arch.baseArch().asString();
427 const std::string * ret = checkOverride( name_r );
430 // get value from map
431 iterator it = find( name_r );
439 std::ostream & dumpOn( std::ostream & str ) const
441 for ( auto && kv : *this )
443 str << '{' << kv.first << '=' << kv.second << '}' << endl;
449 /** Get first line from file */
450 bool parse( const Pathname & dir_r, const std::string & str_r )
452 std::ifstream file( (dir_r/str_r).c_str() );
453 operator[]( str_r ) = str::getline( file, /*trim*/false );
457 /** Derive \c releasever_major/_minor from \c releasever, keeping or overwrititing existing values. */
458 void deriveFromReleasever( const std::string & stem_r, bool overwrite_r )
460 if ( count( stem_r ) ) // releasever is defined..
462 const std::string & stem_major( stem_r+"_major" );
463 const std::string & stem_minor( stem_r+"_minor" );
465 splitReleaseverTo( operator[]( stem_r ), &operator[]( stem_major ), &operator[]( stem_minor ) );
467 splitReleaseverTo( operator[]( stem_r ),
468 count( stem_major ) ? nullptr : &operator[]( stem_major ),
469 count( stem_minor ) ? nullptr : &operator[]( stem_minor ) );
473 /** Split \c releasever at \c '.' and store major/minor parts as requested. */
474 void splitReleaseverTo( const std::string & releasever_r, std::string * major_r, std::string * minor_r ) const
476 if ( major_r || minor_r )
478 std::string::size_type pos = releasever_r.find( "." );
479 if ( pos == std::string::npos )
481 if ( major_r ) *major_r = releasever_r;
482 if ( minor_r ) minor_r->clear();
486 if ( major_r ) *major_r = releasever_r.substr( 0, pos );
487 if ( minor_r ) *minor_r = releasever_r.substr( pos+1 ) ;
492 /** Check for conditions overwriting the (user) defined values. */
493 const std::string * checkOverride( const std::string & name_r )
495 ///////////////////////////////////////////////////////////////////
496 // Always check for changing releasever{,_major,_minor} (bnc#943563)
497 if ( str::startsWith( name_r, "releasever" )
498 && ( name_r.size() == 10
499 || strcmp( name_r.c_str()+10, "_minor" ) == 0
500 || strcmp( name_r.c_str()+10, "_major" ) == 0 ) )
502 std::string val( env::ZYPP_REPO_RELEASEVER() );
505 // $ZYPP_REPO_RELEASEVER always overwrites any defined value
506 if ( val != operator[]( "$releasever" ) )
508 operator[]( "$releasever" ) = std::move(val);
509 deriveFromReleasever( "$releasever", /*overwrite previous values*/true );
511 return &operator[]( "$"+name_r );
513 else if ( !count( name_r ) )
515 // No user defined value, so we follow the target
516 Target_Ptr trg( getZYpp()->getTarget() );
518 val = trg->distributionVersion();
520 val = Target::distributionVersion( Pathname()/*guess*/ );
522 if ( val != operator[]( "$_releasever" ) )
524 operator[]( "$_releasever" ) = std::move(val);
525 deriveFromReleasever( "$_releasever", /*overwrite previous values*/true );
527 return &operator[]( "$_"+name_r );
530 return nullptr; // get user value from map
532 ///////////////////////////////////////////////////////////////////
534 return nullptr; // get user value from map
538 ///////////////////////////////////////////////////////////////////
540 std::string RepoVariablesStringReplacer::operator()( const std::string & value ) const
542 return RepoVarExpand()( value, RepoVarsMap::lookup );
544 std::string RepoVariablesStringReplacer::operator()( std::string && value ) const
546 return RepoVarExpand()( value, RepoVarsMap::lookup );
549 Url RepoVariablesUrlReplacer::operator()( const Url & value ) const
551 static const Url::ViewOptions toReplace = url::ViewOption::DEFAULTS - url::ViewOption::WITH_USERNAME - url::ViewOption::WITH_PASSWORD;
552 const std::string & replaced( RepoVarExpand()( value.asString( toReplace ), RepoVarsMap::lookup ) );
554 if ( !replaced.empty() )
557 newurl.setUsername( value.getUsername( url::E_ENCODED ), url::E_ENCODED );
558 newurl.setPassword( value.getPassword( url::E_ENCODED ), url::E_ENCODED );
563 ///////////////////////////////////////////////////////////////////
565 ///////////////////////////////////////////////////////////////////
566 ///////////////////////////////////////////////////////////////////
569 using namespace zypp;
570 // internal helper called when re-acquiring the lock
571 void repoVariablesReset()
572 { repo::RepoVarsMap::instance().clear(); }
574 } // namespace zyppintern
575 ///////////////////////////////////////////////////////////////////